From 18e095d5b6ad3ffafecd0ad39f21ba21aee9ce5d Mon Sep 17 00:00:00 2001 From: RodCor Date: Fri, 4 Sep 2026 21:12:41 -0300 Subject: [PATCH 01/34] Make memory corrections durable and refresh ANN across writers --- crates/kimetsu-brain/src/ann.rs | 74 +++++++- crates/kimetsu-brain/src/bitemporal.rs | 72 ++++---- crates/kimetsu-brain/src/migrate.rs | 7 + crates/kimetsu-brain/src/project.rs | 128 ++++--------- crates/kimetsu-brain/src/projector.rs | 240 ++++++++++++++++++++++++- crates/kimetsu-brain/src/schema.rs | 16 ++ crates/kimetsu-brain/src/sync.rs | 3 +- crates/kimetsu-core/src/lib.rs | 2 +- 8 files changed, 401 insertions(+), 141 deletions(-) diff --git a/crates/kimetsu-brain/src/ann.rs b/crates/kimetsu-brain/src/ann.rs index 91610f1..5fadd5d 100644 --- a/crates/kimetsu-brain/src/ann.rs +++ b/crates/kimetsu-brain/src/ann.rs @@ -23,7 +23,7 @@ use kimetsu_core::KimetsuResult; /// makes an old sidecar unsafe to load — forces a rebuild. v2: the manifest /// gained a `quant` field AND the default index scalar changed f32→f16, so all /// pre-v2 (f32) sidecars must be rebuilt. -const SCHEMA_VERSION: u32 = 2; +const SCHEMA_VERSION: u32 = 3; /// HNSW graph degree (M). Higher = better recall, more memory. const CONNECTIVITY: usize = 16; @@ -42,6 +42,7 @@ struct Manifest { model_id: String, /// Highest `memories.rowid` already represented in the index. max_rowid_indexed: i64, + corpus_revision: i64, /// Number of active vectors in the index (sanity check vs SQLite). count: usize, /// Scalar quantization the sidecar was built with (`f16`/`i8`/`f32`). A @@ -175,6 +176,16 @@ pub fn handle_for_query(conn: &Connection, dim: usize, model_id: &str) -> Kimets )?))); }; let handle = get_or_build_handle(&key, conn, dim, model_id)?; + let model_changed = { + let index = handle.read().unwrap_or_else(|p| p.into_inner()); + index.dim != dim || index.model_id != model_id + }; + if model_changed { + let mut index = handle.write().unwrap_or_else(|p| p.into_inner()); + let mut fresh = AnnIndex::build_from_conn(conn, dim, model_id)?; + fresh.sidecar = Some(key); + *index = fresh; + } reconcile_if_stale(&handle, conn)?; Ok(handle) } @@ -224,7 +235,7 @@ fn get_or_build_handle( /// Step 2: reconcile a cached index that has fallen behind SQLite (rows added /// out-of-band of the warm add path). Cheap guard: only pay the write lock + -/// reconcile when MAX(rowid) shows new rows. Double-checked under the lock. +/// reconcile when the corpus revision changes. Double-checked under the lock. fn reconcile_if_stale(handle: &Handle, conn: &Connection) -> KimetsuResult<()> { let stale = { let idx = handle.read().unwrap_or_else(|p| p.into_inner()); @@ -312,6 +323,7 @@ pub struct AnnIndex { /// `None` for in-memory / pathless DBs (no sidecar). sidecar: Option, max_rowid_indexed: i64, + corpus_revision: i64, } impl AnnIndex { @@ -329,11 +341,11 @@ impl AnnIndex { /// detected here, but that's harmless — retrieval hydration already filters /// `invalidated_at IS NULL`, so a stale-invalidated candidate is dropped. pub fn is_stale(&self, conn: &Connection) -> KimetsuResult { - let max_rowid: i64 = - conn.query_row("SELECT COALESCE(MAX(rowid), 0) FROM memories", [], |r| { - r.get(0) - })?; - Ok(max_rowid > self.max_rowid_indexed) + Ok( + conn.query_row("SELECT revision FROM corpus_revision WHERE id=1", [], |r| { + r.get::<_, i64>(0) + })? != self.corpus_revision, + ) } /// Build a fresh index from every active, current-model embedding in SQLite. @@ -345,6 +357,11 @@ impl AnnIndex { model_id: model_id.to_string(), sidecar: None, max_rowid_indexed: 0, + corpus_revision: conn.query_row( + "SELECT revision FROM corpus_revision WHERE id=1", + [], + |r| r.get(0), + )?, }; me.reserve_and_load_active(conn)?; Ok(me) @@ -509,6 +526,7 @@ impl AnnIndex { dim: self.dim, model_id: self.model_id.clone(), max_rowid_indexed: self.max_rowid_indexed, + corpus_revision: self.corpus_revision, count: self.len(), quant: scalar_kind_id(ann_scalar_kind()).to_string(), } @@ -603,6 +621,7 @@ impl AnnIndex { model_id: model_id.to_string(), sidecar: Some(sidecar.to_path_buf()), max_rowid_indexed: manifest.max_rowid_indexed, + corpus_revision: manifest.corpus_revision, })) } @@ -612,6 +631,15 @@ impl AnnIndex { /// /// Cheap: rides the `idx_memories_scope_model_active` covering index. pub fn reconcile(&mut self, conn: &Connection) -> KimetsuResult<()> { + if self.is_stale(conn)? { + // Existing keys may have new vectors, a new model, or tombstones. + // Rebuild until a bounded change-log consumer is available. + let mut fresh = Self::build_from_conn(conn, self.dim, &self.model_id)?; + fresh.sidecar = self.sidecar.clone(); + *self = fresh; + return Ok(()); + } + // 3a. New active rows since last index. Stream the delta in chunks so a // bulk load (e.g. 500k rows) never materializes its BLOBs + decoded f32 // all at once (~1.5GB transient). COUNT once + reserve the full delta up @@ -793,6 +821,38 @@ mod tests { hit as f32 / total as f32 } + #[test] + fn warm_reader_refreshes_existing_vectors_from_another_connection() { + let dir = tempfile::tempdir().unwrap(); + let db = dir.path().join("brain.db"); + let reader = Connection::open(&db).unwrap(); + crate::schema::initialize(&reader).unwrap(); + for (id, vector) in [("a", vec![1.0f32, 0.0]), ("b", vec![0.0f32, 1.0])] { + reader.execute("INSERT INTO memories(memory_id,scope,kind,text,normalized_text,confidence,provenance_snapshot_json,created_at,embedding,embedding_model) VALUES (?1,'project','fact',?1,?1,1.0,'{}','2026-01-01T00:00:00Z',?2,'stub')",rusqlite::params![id,encode_embedding(&vector)]).unwrap(); + } + let mut index = AnnIndex::build_from_conn(&reader, 2, "stub").unwrap(); + assert_eq!(index.search(&[1.0, 0.0], 1).unwrap()[0].0, 1); + let writer = Connection::open(&db).unwrap(); + writer + .execute( + "UPDATE memories SET embedding=?1 WHERE memory_id='a'", + rusqlite::params![encode_embedding(&[-1.0, 0.0])], + ) + .unwrap(); + assert!(index.is_stale(&reader).unwrap()); + index.reconcile(&reader).unwrap(); + assert_eq!(index.search(&[1.0, 0.0], 1).unwrap()[0].0, 2); + assert!(!index.is_stale(&reader).unwrap()); + writer + .execute( + "UPDATE memories SET invalidated_at='2026-03-01T00:00:00Z' WHERE memory_id='b'", + [], + ) + .unwrap(); + index.reconcile(&reader).unwrap(); + assert_eq!(index.len(), 1); + } + #[test] fn default_quant_is_f16() { with_quant(None, || { diff --git a/crates/kimetsu-brain/src/bitemporal.rs b/crates/kimetsu-brain/src/bitemporal.rs index a624632..4e68bd5 100644 --- a/crates/kimetsu-brain/src/bitemporal.rs +++ b/crates/kimetsu-brain/src/bitemporal.rs @@ -21,27 +21,10 @@ //! contradicting fact there invalidates rather than overwrites: history stays //! answerable. //! -//! ## The shape here -//! -//! Kimetsu is already most of the way there without a schema change, because -//! **nothing is ever destroyed**. Supersession stamps `superseded_by`, -//! invalidation stamps `invalidated_at`, and automatic contradiction resolution -//! stamps the loser's `valid_to` — every one of them a tombstone with a -//! timestamp, not a delete. The rows to answer an as-of query are all present; -//! there was simply no query that read them that way. -//! -//! So [`as_of_predicate`] is a WHERE clause rather than a migration: -//! -//! ```text -//! created_at <= T -- the brain knew it by then -//! (invalidated_at IS NULL OR > T) -- and had not retracted it -//! (valid_from IS NULL OR <= T) -- and it had taken effect -//! (valid_to IS NULL OR > T) -- and had not expired -//! ``` -//! -//! Superseded memories are deliberately *included*: a memory merged into a -//! survivor last week was a live belief the week before, and excluding it would -//! misreport what the brain knew. +//! Corrections retain their text and kind in `memory_revisions`. The original +//! single-time API delegates to [`memories_at`], which separates effective and +//! known time. Retirement and validity still use the memory's tombstones; +//! temporal metadata edits are not yet independently revisioned. use kimetsu_core::KimetsuResult; use rusqlite::Connection; @@ -55,10 +38,10 @@ use crate::context::ContextCapsule; /// query so the several candidate paths can share exactly one definition of /// "believed at T"; two subtly different versions of this clause would be a /// bug nobody would ever notice. -pub const AS_OF_PREDICATE: &str = "created_at <= ?1 \ - AND (invalidated_at IS NULL OR invalidated_at > ?1) \ - AND (valid_from IS NULL OR valid_from <= ?1) \ - AND (valid_to IS NULL OR valid_to > ?1)"; +pub const AS_OF_PREDICATE: &str = "julianday(created_at) <= julianday(?1) \ + AND (invalidated_at IS NULL OR julianday(invalidated_at) > julianday(?1)) \ + AND (valid_from IS NULL OR julianday(valid_from) <= julianday(?1)) \ + AND (valid_to IS NULL OR julianday(valid_to) > julianday(?1))"; /// Human-readable form of [`AS_OF_PREDICATE`], for `--explain` output and docs. pub fn as_of_predicate() -> &'static str { @@ -87,12 +70,29 @@ pub fn memories_as_of( conn: &Connection, as_of: &str, limit: u32, +) -> KimetsuResult> { + memories_at(conn, as_of, as_of, limit) +} + +/// Query independently when a claim was effective and when it was known. +/// Corrections default effective time to their recording time; imported events +/// may supply an explicit RFC3339 `effective_at` for late-arriving corrections. +pub fn memories_at( + conn: &Connection, + valid_at: &str, + known_at: &str, + limit: u32, ) -> KimetsuResult> { let sql = format!( - "SELECT memory_id, scope, kind, text, created_at, + "SELECT memory_id, scope, + COALESCE((SELECT kind FROM memory_revisions r WHERE r.memory_id=m.memory_id AND julianday(r.known_at)<=julianday(?2) AND julianday(r.effective_at)<=julianday(?1) ORDER BY julianday(r.known_at) DESC,revision_id DESC LIMIT 1),kind), + COALESCE((SELECT text FROM memory_revisions r WHERE r.memory_id=m.memory_id AND julianday(r.known_at)<=julianday(?2) AND julianday(r.effective_at)<=julianday(?1) ORDER BY julianday(r.known_at) DESC,revision_id DESC LIMIT 1),text), created_at, invalidated_at, invalidated_reason, valid_to, superseded_by - FROM memories - WHERE {AS_OF_PREDICATE} + FROM memories m + WHERE julianday(created_at)<=julianday(?2) + AND (invalidated_at IS NULL OR julianday(invalidated_at)>julianday(?2)) + AND (valid_from IS NULL OR julianday(valid_from)<=julianday(?1)) + AND (valid_to IS NULL OR julianday(valid_to)>julianday(?1)) ORDER BY created_at DESC {}", if limit == 0 { @@ -103,7 +103,7 @@ pub fn memories_as_of( ); let mut stmt = conn.prepare(&sql)?; let rows = stmt - .query_map(rusqlite::params![as_of], |row| { + .query_map(rusqlite::params![valid_at, known_at], |row| { Ok(( row.get::<_, String>(0)?, row.get::<_, String>(1)?, @@ -198,18 +198,24 @@ pub fn belief_delta(conn: &Connection, from: &str, to: &str) -> KimetsuResult = before.iter().map(|m| m.memory_id.as_str()).collect(); - let after_ids: HashSet<&str> = after.iter().map(|m| m.memory_id.as_str()).collect(); + let before_ids: HashSet<_> = before + .iter() + .map(|m| (&m.memory_id, &m.text, &m.kind)) + .collect(); + let after_ids: HashSet<_> = after + .iter() + .map(|m| (&m.memory_id, &m.text, &m.kind)) + .collect(); Ok(BeliefDelta { learned: after .iter() - .filter(|m| !before_ids.contains(m.memory_id.as_str())) + .filter(|m| !before_ids.contains(&(&m.memory_id, &m.text, &m.kind))) .cloned() .collect(), retired: before .iter() - .filter(|m| !after_ids.contains(m.memory_id.as_str())) + .filter(|m| !after_ids.contains(&(&m.memory_id, &m.text, &m.kind))) .cloned() .collect(), }) diff --git a/crates/kimetsu-brain/src/migrate.rs b/crates/kimetsu-brain/src/migrate.rs index 61d93d6..eea8d58 100644 --- a/crates/kimetsu-brain/src/migrate.rs +++ b/crates/kimetsu-brain/src/migrate.rs @@ -104,6 +104,11 @@ fn migrations() -> &'static [Migration] { description: "add memory_entities projection (v2.6 first-class tags + ingest-time edges)", up: crate::schema::migrate_v10_to_v11, }, + Migration { + version: 12, + description: "durable correction revisions and corpus freshness", + up: crate::schema::migrate_v11_to_v12, + }, ] } @@ -504,6 +509,8 @@ mod tests { ('e2','r1','2024-01-02T00:00:00Z','memory.cited',1,'{}');", ) .expect("seed v7 events"); + // Newer migrations install corpus-change triggers on the baseline table. + conn.execute_batch("CREATE TABLE memories(memory_id TEXT PRIMARY KEY, text TEXT, embedding BLOB, embedding_model TEXT, invalidated_at TEXT, superseded_by TEXT);").unwrap(); let target = target_version(); let outcome = run_with(&conn, migrations(), target).expect("migrate v7->current"); diff --git a/crates/kimetsu-brain/src/project.rs b/crates/kimetsu-brain/src/project.rs index 949dd2e..21e72cc 100644 --- a/crates/kimetsu-brain/src/project.rs +++ b/crates/kimetsu-brain/src/project.rs @@ -2009,20 +2009,9 @@ pub struct UndoneMemory { pub kind: String, } -/// QoL: edit an existing active memory in-place, preserving its usefulness history. -/// -/// - `new_text`: if given, the text (and normalized_text) are updated, the FTS -/// index row is refreshed, and a new embedding is stored via the configured -/// embedder (no-op in lean builds). Secret-redaction is applied at the same -/// boundary as `add_memory`. -/// - `new_kind`: if given, the `kind` column is updated. -/// -/// At least one of `new_text` / `new_kind` must be `Some`; otherwise an error -/// is returned. `use_count`, `usefulness_score`, `confidence`, and `created_at` -/// are intentionally left unchanged — the whole point of edit-in-place is to -/// preserve the memory's learned history. -/// -/// Errors if the memory id is unknown or already invalidated. +/// Record a durable correction to an active memory. Text changes reset +/// claim-specific evidence and invalidate embeddings atomically with FTS. +/// Kind-only changes preserve evidence; all corrections retain text lineage. pub fn edit_memory( start: &Path, memory_id: &str, @@ -2034,92 +2023,35 @@ pub fn edit_memory( } let (paths, config, conn) = load_project(start)?; - - // Verify memory exists and is active (not invalidated). - let row: Option<(String, String, String)> = conn - .query_row( - "SELECT scope, kind, invalidated_at FROM memories WHERE memory_id = ?1", - params![memory_id], - |row| { - Ok(( - row.get::<_, String>(0)?, - row.get::<_, String>(1)?, - row.get::<_, String>(2).unwrap_or_default(), - )) - }, - ) - .optional()?; - - let (scope, current_kind, invalidated_at) = match row { - None => return Err(format!("memory not found: {memory_id}").into()), - Some(r) => r, - }; - if !invalidated_at.is_empty() { - return Err(format!("memory {memory_id} is already invalidated").into()); - } - let run_id = RunId::new(); let _lock = ProjectLock::acquire(&paths, "brain memory edit", Some(run_id))?; - - // Apply text update. - if let Some(raw_text) = new_text { - let redaction = redact::redact_secrets(raw_text); - if redaction.was_redacted() { - eprintln!("kimetsu-brain: {}", redaction.summary()); - } - let text = &redaction.text; - let normalized = normalize_memory_text(text); - - conn.execute( - "UPDATE memories SET text = ?1, normalized_text = ?2 WHERE memory_id = ?3", - params![text, normalized, memory_id], - )?; - - // Refresh the FTS index row. - conn.execute( - "DELETE FROM memories_fts WHERE memory_id = ?1", + let corrected = Event::new( + run_id, + "memory.corrected", + serde_json::json!({ + "memory_id": memory_id, + "text": new_text.map(|text| redact::redact_secrets(text).text), + "kind": new_kind.map(|kind| kind.to_string()), + }), + ); + projector::apply_events( + &conn, + &[ + admin_started_event(&paths, &config, run_id, "memory edit")?, + corrected, + admin_finished_event(run_id), + ], + )?; + // Correction and vector invalidation are committed together. Re-embedding + // is recoverable derived work and cannot leave an old vector on new text. + if new_text.is_some() { + let text: String = conn.query_row( + "SELECT text FROM memories WHERE memory_id=?1", params![memory_id], + |r| r.get(0), )?; - let kind_for_fts = new_kind - .as_ref() - .map(|k| k.to_string()) - .unwrap_or(current_kind.clone()); - conn.execute( - "INSERT INTO memories_fts (memory_id, text, kind, scope) VALUES (?1, ?2, ?3, ?4)", - params![memory_id, text, kind_for_fts, scope], - )?; - - // Re-embed so semantic retrieval reflects the corrected text. let embedder = embeddings::open_embedder_for(config.embedder.enabled); - embeddings::embed_and_persist(&conn, memory_id, text, embedder)?; - // (return value not needed here — no conflict scan after an edit) - } - - // Apply kind update (FTS row may need refreshing if text wasn't also changed). - if let Some(kind) = new_kind { - conn.execute( - "UPDATE memories SET kind = ?1 WHERE memory_id = ?2", - params![kind.to_string(), memory_id], - )?; - - // Only refresh FTS kind column if we didn't already rebuild it above. - if new_text.is_none() { - // Re-read the current text from DB to rebuild the FTS row with - // the new kind (text unchanged). - let current_text: String = conn.query_row( - "SELECT text FROM memories WHERE memory_id = ?1", - params![memory_id], - |row| row.get(0), - )?; - conn.execute( - "DELETE FROM memories_fts WHERE memory_id = ?1", - params![memory_id], - )?; - conn.execute( - "INSERT INTO memories_fts (memory_id, text, kind, scope) VALUES (?1, ?2, ?3, ?4)", - params![memory_id, current_text, kind.to_string(), scope], - )?; - } + embeddings::embed_and_persist(&conn, memory_id, &text, embedder)?; } Ok(()) @@ -5609,10 +5541,10 @@ max_total_cost_usd = 250.0 assert_eq!(text, "corrected text for edit test"); assert!(!normalized.is_empty(), "normalized_text must not be empty"); // History preserved. - assert_eq!(use_count, 7, "use_count must not be reset"); + assert_eq!(use_count, 0, "changed claim must reset evidence"); assert!( - (usefulness_score - 3.5).abs() < 0.01, - "usefulness_score must not be reset" + usefulness_score.abs() < 0.01, + "changed claim must reset usefulness" ); } diff --git a/crates/kimetsu-brain/src/projector.rs b/crates/kimetsu-brain/src/projector.rs index 70df0bd..6ab1f1c 100644 --- a/crates/kimetsu-brain/src/projector.rs +++ b/crates/kimetsu-brain/src/projector.rs @@ -192,6 +192,7 @@ fn reset_projection(conn: &Connection) -> KimetsuResult<()> { DELETE FROM runs; DELETE FROM sources; DELETE FROM memories; + DELETE FROM memory_revisions; DELETE FROM memory_proposals; DELETE FROM memories_fts; DELETE FROM memory_citations; @@ -253,6 +254,7 @@ fn project_event(conn: &Connection, event: &Event) -> KimetsuResult<()> { // the edge is re-derived by replaying this event. "memory.edge" => apply_memory_edge(conn, event), // Flagship 1 / Story 1.4: temporal validity — stamp valid_from / valid_to. + "memory.corrected" => apply_memory_corrected(conn, event), "memory.temporal" => apply_memory_temporal(conn, event), // Flagship 1 / Story 1.3: episodic work-resume. "work.episode" => crate::episode::project_work_episode(conn, event), @@ -263,7 +265,7 @@ fn project_event(conn: &Connection, event: &Event) -> KimetsuResult<()> { fn redact_memory_event(event: &Event) -> Cow<'_, Event> { if !matches!( event.kind.as_str(), - "memory.accepted" | "memory.proposed" | "memory.cited" + "memory.accepted" | "memory.proposed" | "memory.cited" | "memory.corrected" ) { return Cow::Borrowed(event); } @@ -2389,3 +2391,239 @@ mod tests { ); } } + +fn apply_memory_corrected(conn: &Connection, event: &Event) -> KimetsuResult<()> { + let id = event + .payload + .get("memory_id") + .and_then(|v| v.as_str()) + .ok_or("correction requires memory_id")?; + if conn.query_row( + "SELECT EXISTS(SELECT 1 FROM memory_revisions WHERE event_id=?1)", + params![event.event_id.to_string()], + |r| r.get::<_, bool>(0), + )? { + return Ok(()); + } + let old: Option<(String, String, Option)> = conn + .query_row( + "SELECT text, kind, invalidated_at FROM memories WHERE memory_id=?1", + params![id], + |r| Ok((r.get(0)?, r.get(1)?, r.get(2)?)), + ) + .optional()?; + let (old_text, old_kind, invalidated) = old.ok_or_else(|| format!("memory not found: {id}"))?; + if invalidated.is_some() { + return Err(format!("memory {id} is already invalidated").into()); + } + let text = event.payload.get("text").and_then(|v| v.as_str()); + let kind = event.payload.get("kind").and_then(|v| v.as_str()); + if text.is_none() && kind.is_none() { + return Err("correction requires text or kind".into()); + } + if text.is_some_and(|t| t.trim().is_empty()) { + return Err("correction text cannot be empty".into()); + } + if let Some(k) = kind { + k.parse::()?; + } + let now = ts_text(event)?; + let effective = event + .payload + .get("effective_at") + .and_then(|v| v.as_str()) + .unwrap_or(&now); + OffsetDateTime::parse(effective, &Rfc3339)?; + conn.execute("INSERT OR IGNORE INTO memory_revisions (memory_id,event_id,text,kind,known_at,effective_at,confidence,use_count,usefulness_score) + SELECT memory_id, 'baseline:' || memory_id, text,kind,created_at,COALESCE(valid_from,'0001-01-01T00:00:00Z'),confidence,use_count,usefulness_score FROM memories WHERE memory_id=?1", params![id])?; + // Freeze accumulated evidence on the retiring revision before resetting it. + conn.execute( + "UPDATE memory_revisions SET + confidence=(SELECT confidence FROM memories WHERE memory_id=?1), + use_count=(SELECT use_count FROM memories WHERE memory_id=?1), + usefulness_score=(SELECT usefulness_score FROM memories WHERE memory_id=?1) + WHERE revision_id=(SELECT MAX(revision_id) FROM memory_revisions WHERE memory_id=?1)", + params![id], + )?; + let changed = text.is_some_and(|t| t != old_text); + if changed { + conn.execute("UPDATE memories SET confidence=1.0,use_count=0,usefulness_score=0,last_used_at=NULL,last_useful_at=NULL,embedding=NULL,embedding_model=NULL WHERE memory_id=?1", params![id])?; + conn.execute( + "DELETE FROM memory_citations WHERE memory_id=?1", + params![id], + )?; + conn.execute("DELETE FROM query_routes WHERE memory_id=?1", params![id])?; + } + let text = text.unwrap_or(&old_text); + let kind = kind.unwrap_or(&old_kind); + conn.execute( + "UPDATE memories SET text=?2,normalized_text=?3,kind=?4 WHERE memory_id=?1", + params![ + id, + text, + kimetsu_core::memory::normalize_memory_text(text), + kind + ], + )?; + conn.execute("DELETE FROM memories_fts WHERE memory_id=?1", params![id])?; + conn.execute("INSERT INTO memories_fts(memory_id,text,kind,scope) SELECT memory_id,text,kind,scope FROM memories WHERE memory_id=?1", params![id])?; + crate::graph::project_entities(conn, id, text)?; + conn.execute("INSERT INTO memory_revisions (memory_id,event_id,text,kind,known_at,effective_at,confidence,use_count,usefulness_score) + SELECT memory_id,?2,text,kind,?3,?4,confidence,use_count,usefulness_score FROM memories WHERE memory_id=?1", params![id,event.event_id.to_string(),now,effective])?; + Ok(()) +} + +#[cfg(test)] +mod correction_regressions { + use super::*; + fn event(kind: &str, payload: serde_json::Value, at: &str) -> Event { + let mut e = Event::new(RunId::new(), kind, payload); + e.ts = OffsetDateTime::parse(at, &Rfc3339).unwrap(); + e + } + fn seed(c: &Connection) { + schema::initialize(c).unwrap(); + apply_events(c, &[event("memory.accepted", serde_json::json!({"memory_id":"m", "scope":"project", "kind":"fact", "text":"original quokka"}), "2026-01-01T00:00:00Z")]).unwrap(); + } + #[test] + fn correction_validation_rolls_back_events_text_and_fts() { + let c = Connection::open_in_memory().unwrap(); + seed(&c); + let before: i64 = c + .query_row("SELECT COUNT(*) FROM events", [], |r| r.get(0)) + .unwrap(); + let bad = event( + "memory.corrected", + serde_json::json!({"memory_id":"m", "text":"changed narwhal", "kind":"invalid-kind"}), + "2026-03-01T00:00:00Z", + ); + assert!(apply_events(&c, &[bad]).is_err()); + assert_eq!( + c.query_row("SELECT COUNT(*) FROM events", [], |r| r.get::<_, i64>(0)) + .unwrap(), + before + ); + assert_eq!( + c.query_row("SELECT text FROM memories", [], |r| r.get::<_, String>(0)) + .unwrap(), + "original quokka" + ); + assert_eq!( + c.query_row( + "SELECT COUNT(*) FROM memories_fts WHERE memories_fts MATCH 'quokka'", + [], + |r| r.get::<_, i64>(0) + ) + .unwrap(), + 1 + ); + // Force a failure after the text update: the entire projection must roll back. + c.execute_batch("CREATE TRIGGER fail_correction BEFORE INSERT ON memory_revisions WHEN NEW.event_id NOT LIKE 'baseline:%' BEGIN SELECT RAISE(ABORT,'injected failure'); END;").unwrap(); + let valid = event( + "memory.corrected", + serde_json::json!({"memory_id":"m", "text":"changed narwhal"}), + "2026-03-01T00:00:00Z", + ); + assert!(apply_events(&c, &[valid]).is_err()); + assert_eq!( + c.query_row("SELECT text FROM memories", [], |r| r.get::<_, String>(0)) + .unwrap(), + "original quokka" + ); + assert_eq!( + c.query_row( + "SELECT COUNT(*) FROM memories_fts WHERE memories_fts MATCH 'quokka'", + [], + |r| r.get::<_, i64>(0) + ) + .unwrap(), + 1 + ); + } + #[test] + fn correction_history_separates_known_and_effective_time_and_replays() { + let c = Connection::open_in_memory().unwrap(); + seed(&c); + apply_events(&c, &[event("memory.corrected", serde_json::json!({"memory_id":"m", "text":"corrected narwhal", "effective_at":"2026-02-01T00:00:00Z"}), "2026-03-01T00:00:00Z")]).unwrap(); + for _ in 0..2 { + assert_eq!( + crate::bitemporal::memories_at( + &c, + "2026-02-15T00:00:00Z", + "2026-02-15T00:00:00Z", + 0 + ) + .unwrap()[0] + .text, + "original quokka" + ); + assert_eq!( + crate::bitemporal::memories_at( + &c, + "2026-02-15T00:00:00Z", + "2026-04-01T00:00:00Z", + 0 + ) + .unwrap()[0] + .text, + "corrected narwhal" + ); + assert_eq!( + crate::bitemporal::memories_at( + &c, + "2026-01-15T00:00:00Z", + "2026-04-01T00:00:00Z", + 0 + ) + .unwrap()[0] + .text, + "original quokka" + ); + rebuild_in_place(&c).unwrap(); + } + let (_, jsonl) = crate::sync::export_events(&c, 0, None, false).unwrap(); + let imported = Connection::open_in_memory().unwrap(); + schema::initialize(&imported).unwrap(); + crate::sync::import_events(&imported, &jsonl.unwrap(), false).unwrap(); + rebuild_in_place(&imported).unwrap(); + assert_eq!( + imported + .query_row("SELECT text FROM memories WHERE memory_id='m'", [], |r| { + r.get::<_, String>(0) + }) + .unwrap(), + "corrected narwhal" + ); + } + #[test] + fn corpus_revision_observes_existing_embedding_updates_from_another_connection() { + let dir = tempfile::tempdir().unwrap(); + let db = dir.path().join("brain.db"); + let reader = Connection::open(&db).unwrap(); + seed(&reader); + let writer = Connection::open(&db).unwrap(); + let revision = || { + reader + .query_row("SELECT revision FROM corpus_revision", [], |r| { + r.get::<_, i64>(0) + }) + .unwrap() + }; + let before = revision(); + writer + .execute( + "UPDATE memories SET embedding=?1,embedding_model='stub' WHERE memory_id='m'", + params![vec![0u8; 8]], + ) + .unwrap(); + assert!(revision() > before); + let before = revision(); + writer + .execute( + "UPDATE memories SET embedding=?1 WHERE memory_id='m'", + params![vec![1u8; 8]], + ) + .unwrap(); + assert!(revision() > before); + } +} diff --git a/crates/kimetsu-brain/src/schema.rs b/crates/kimetsu-brain/src/schema.rs index 6dce008..c650a4f 100644 --- a/crates/kimetsu-brain/src/schema.rs +++ b/crates/kimetsu-brain/src/schema.rs @@ -1201,3 +1201,19 @@ mod tests { ); } } + +/// Retain correction lineage and invalidate indexes across connections. +pub fn migrate_v11_to_v12(conn: &Connection) -> KimetsuResult<()> { + conn.execute_batch("CREATE TABLE IF NOT EXISTS memory_revisions ( + revision_id INTEGER PRIMARY KEY, memory_id TEXT NOT NULL, + event_id TEXT NOT NULL UNIQUE, text TEXT NOT NULL, kind TEXT NOT NULL, + known_at TEXT NOT NULL, effective_at TEXT NOT NULL, + confidence REAL NOT NULL, use_count INTEGER NOT NULL, usefulness_score REAL NOT NULL); + CREATE INDEX IF NOT EXISTS idx_memory_revisions_time ON memory_revisions(memory_id, known_at, effective_at); + CREATE TABLE IF NOT EXISTS corpus_revision (id INTEGER PRIMARY KEY CHECK(id=1), revision INTEGER NOT NULL); + INSERT OR IGNORE INTO corpus_revision VALUES (1,0); + CREATE TRIGGER IF NOT EXISTS corpus_insert AFTER INSERT ON memories BEGIN UPDATE corpus_revision SET revision=revision+1 WHERE id=1; END; + CREATE TRIGGER IF NOT EXISTS corpus_delete AFTER DELETE ON memories BEGIN UPDATE corpus_revision SET revision=revision+1 WHERE id=1; END; + CREATE TRIGGER IF NOT EXISTS corpus_update AFTER UPDATE OF embedding, embedding_model, text, invalidated_at, superseded_by ON memories BEGIN UPDATE corpus_revision SET revision=revision+1 WHERE id=1; END;")?; + Ok(()) +} diff --git a/crates/kimetsu-brain/src/sync.rs b/crates/kimetsu-brain/src/sync.rs index 2c4b014..6e28c1d 100644 --- a/crates/kimetsu-brain/src/sync.rs +++ b/crates/kimetsu-brain/src/sync.rs @@ -72,6 +72,7 @@ const SYNC_ALLOWED_KINDS: &[&str] = &[ "memory.proposed", "memory.rejected", "memory.invalidated", + "memory.corrected", "memory.cited", "memory.superseded", ]; @@ -171,7 +172,7 @@ impl TryFrom for Event { fn redact_event_payload(event: &Event) -> serde_json::Value { if !matches!( event.kind.as_str(), - "memory.accepted" | "memory.proposed" | "memory.cited" + "memory.accepted" | "memory.proposed" | "memory.cited" | "memory.corrected" ) { return event.payload.clone(); } diff --git a/crates/kimetsu-core/src/lib.rs b/crates/kimetsu-core/src/lib.rs index c8a22c0..febb2d1 100644 --- a/crates/kimetsu-core/src/lib.rs +++ b/crates/kimetsu-core/src/lib.rs @@ -7,7 +7,7 @@ pub mod memory; pub mod paths; pub mod secret; -pub const KIMETSU_SCHEMA_VERSION: i64 = 11; +pub const KIMETSU_SCHEMA_VERSION: i64 = 12; /// The `project.toml` config-file format version. Deliberately decoupled /// from `KIMETSU_SCHEMA_VERSION` (the brain.db schema): the DB schema can /// advance via migrations without forcing every project.toml to be rewritten. From 84b0bd44db663069285ba109bc60fb46fe1c5b2c Mon Sep 17 00:00:00 2001 From: RodCor Date: Fri, 4 Sep 2026 21:26:40 -0300 Subject: [PATCH 02/34] Isolate delayed correction evidence and guard index publication --- crates/kimetsu-brain/src/ann.rs | 60 ++++++-- crates/kimetsu-brain/src/embeddings.rs | 121 ++++++++++++---- crates/kimetsu-brain/src/projector.rs | 184 +++++++++++++++++++++++++ 3 files changed, 329 insertions(+), 36 deletions(-) diff --git a/crates/kimetsu-brain/src/ann.rs b/crates/kimetsu-brain/src/ann.rs index 5fadd5d..0df47a8 100644 --- a/crates/kimetsu-brain/src/ann.rs +++ b/crates/kimetsu-brain/src/ann.rs @@ -23,7 +23,7 @@ use kimetsu_core::KimetsuResult; /// makes an old sidecar unsafe to load — forces a rebuild. v2: the manifest /// gained a `quant` field AND the default index scalar changed f32→f16, so all /// pre-v2 (f32) sidecars must be rebuilt. -const SCHEMA_VERSION: u32 = 3; +const SCHEMA_VERSION: u32 = 4; /// HNSW graph degree (M). Higher = better recall, more memory. const CONNECTIVITY: usize = 16; @@ -49,6 +49,7 @@ struct Manifest { /// sidecar must NOT be loaded under a different quantization (silent /// corruption), so `try_load` rejects a mismatch and forces a rebuild. quant: String, + index_digest: String, } /// Index scalar quantization. f16 is the default — it ~halves the index's @@ -529,18 +530,15 @@ impl AnnIndex { corpus_revision: self.corpus_revision, count: self.len(), quant: scalar_kind_id(ann_scalar_kind()).to_string(), + index_digest: String::new(), } } /// Serialize the index + manifest to the sidecar (no-op for in-memory DBs). /// - /// Concurrency-safe for fleet writers: each file is written to a - /// process-unique temp path and atomically `rename`d into place, so a - /// concurrent reader (another process opening the same brain) never observes - /// a torn `.usearch`. The manifest is renamed LAST — a reader that sees the - /// new manifest is guaranteed to also see the new index, and the reverse - /// (new index + old manifest) is caught by the `size != count` check on load - /// and degrades to a rebuild rather than serving stale hits. + /// A digest binds the manifest revision to the exact serialized index. + /// Concurrent two-file publications may mix generations; readers reject + /// those pairs and rebuild, including equal-count vector updates. pub fn save(&self) -> KimetsuResult<()> { let Some(sidecar) = &self.sidecar else { return Ok(()); @@ -551,6 +549,10 @@ impl AnnIndex { self.index .save(index_tmp.to_string_lossy().as_ref()) .map_err(|e| format!("usearch save: {e}"))?; + // Digest the exact generation before publishing either file. + let index_digest = blake3::hash(&std::fs::read(&index_tmp)?) + .to_hex() + .to_string(); std::fs::rename(&index_tmp, sidecar).map_err(|e| { let _ = std::fs::remove_file(&index_tmp); format!("usearch rename: {e}") @@ -559,8 +561,10 @@ impl AnnIndex { // 2. Manifest LAST → temp → atomic rename. let manifest_path = Self::manifest_path(sidecar); let manifest_tmp = Self::tmp_sibling(&manifest_path); + let mut generation = self.manifest(); + generation.index_digest = index_digest; let manifest = - serde_json::to_vec(&self.manifest()).map_err(|e| format!("manifest serialize: {e}"))?; + serde_json::to_vec(&generation).map_err(|e| format!("manifest serialize: {e}"))?; std::fs::write(&manifest_tmp, manifest).map_err(|e| format!("manifest write: {e}"))?; std::fs::rename(&manifest_tmp, &manifest_path).map_err(|e| { let _ = std::fs::remove_file(&manifest_tmp); @@ -609,8 +613,16 @@ impl AnnIndex { return Ok(None); } let index = Index::new(&index_options(dim)).map_err(|e| format!("usearch new: {e}"))?; - if index.load(sidecar.to_string_lossy().as_ref()).is_err() { - return Ok(None); // corrupt sidecar → rebuild + // Read once: validating a path and then reopening it for load would + // permit a concurrent rename between validation and use. + let bytes = match std::fs::read(sidecar) { + Ok(bytes) => bytes, + Err(_) => return Ok(None), + }; + if blake3::hash(&bytes).to_hex().as_str() != manifest.index_digest + || index.load_from_buffer(&bytes).is_err() + { + return Ok(None); // Mixed generation or corrupt sidecar. } if index.size() != manifest.count { return Ok(None); @@ -821,6 +833,32 @@ mod tests { hit as f32 / total as f32 } + #[test] + fn mixed_equal_count_sidecar_generations_are_rejected() { + let dir = tempfile::tempdir().unwrap(); + let db = dir.path().join("brain.db"); + let c = Connection::open(&db).unwrap(); + crate::schema::initialize(&c).unwrap(); + c.execute("INSERT INTO memories(memory_id,scope,kind,text,normalized_text,confidence,provenance_snapshot_json,created_at,embedding,embedding_model) VALUES ('m','project','fact','t','t',1,'{}','2026-01-01T00:00:00Z',?1,'stub')",rusqlite::params![encode_embedding(&[1.0,0.0])]).unwrap(); + let old = AnnIndex::open_or_build(&c, 2, "stub").unwrap(); + old.save().unwrap(); + let path = db.with_extension("usearch"); + let old_bytes = std::fs::read(&path).unwrap(); + c.execute( + "UPDATE memories SET embedding=?1", + rusqlite::params![encode_embedding(&[0.0, 1.0])], + ) + .unwrap(); + let new = AnnIndex::open_or_build(&c, 2, "stub").unwrap(); + new.save().unwrap(); + // Deterministically materialize older index + newer manifest, equal size. + std::fs::write(&path, old_bytes).unwrap(); + assert!(AnnIndex::try_load(&path, 2, "stub").unwrap().is_none()); + let repaired = AnnIndex::open_or_build(&c, 2, "stub").unwrap(); + assert!(!repaired.is_stale(&c).unwrap()); + assert!(repaired.search(&[0.0, 1.0], 1).unwrap()[0].1 < 0.01); + } + #[test] fn warm_reader_refreshes_existing_vectors_from_another_connection() { let dir = tempfile::tempdir().unwrap(); diff --git a/crates/kimetsu-brain/src/embeddings.rs b/crates/kimetsu-brain/src/embeddings.rs index 6c634d5..27f24da 100644 --- a/crates/kimetsu-brain/src/embeddings.rs +++ b/crates/kimetsu-brain/src/embeddings.rs @@ -974,6 +974,16 @@ pub fn embed_and_persist( if embedder.is_noop() { return Ok(None); } + use rusqlite::OptionalExtension; + // Capture the claim generation before expensive inference. Text alone is + // insufficient for A -> B -> A corrections. + let expected_revision: Option = conn.query_row( + "SELECT COALESCE((SELECT event_id FROM memory_revisions WHERE memory_id=?1 ORDER BY revision_id DESC LIMIT 1),'baseline:' || memory_id) + FROM memories WHERE memory_id=?1 AND text=?2 AND invalidated_at IS NULL AND superseded_by IS NULL", + rusqlite::params![memory_id,text], |r|r.get(0)).optional()?; + let Some(expected_revision) = expected_revision else { + return Ok(None); + }; let vec = match embedder.embed(text) { Ok(v) => v, // NotImplemented is the contract for "skip silently". Treat @@ -991,33 +1001,18 @@ pub fn embed_and_persist( .into()); } let blob = encode_embedding(&vec); - conn.execute( - "UPDATE memories SET embedding = ?1, embedding_model = ?2 WHERE memory_id = ?3", - rusqlite::params![blob, embedder.model_id(), memory_id], + let changed = conn.execute( + "UPDATE memories SET embedding=?1,embedding_model=?2 WHERE memory_id=?3 AND text=?4 + AND invalidated_at IS NULL AND superseded_by IS NULL + AND COALESCE((SELECT event_id FROM memory_revisions WHERE memory_id=?3 ORDER BY revision_id DESC LIMIT 1),'baseline:' || memory_id)=?5", + rusqlite::params![blob,embedder.model_id(),memory_id,text,expected_revision], )?; - - // Tier-3: keep the warm usearch index current at add time. For in-memory - // DBs there is no cached handle — the rebuild-on-query path picks the row - // up, so we safely skip. Best-effort: an index failure must not abort a - // successful memory write. - #[cfg(feature = "embeddings")] - if let Some(handle) = crate::ann::cached_handle(conn) { - let rowid: Option = conn - .query_row( - "SELECT rowid FROM memories WHERE memory_id = ?1", - rusqlite::params![memory_id], - |r| r.get(0), - ) - .ok(); - if let Some(rowid) = rowid { - let mut guard = handle.write().unwrap_or_else(|p| p.into_inner()); - if let Err(e) = guard.add(rowid, &vec) { - eprintln!( - "kimetsu-brain: ann add failed for memory {memory_id}: {e} (index will reconcile on next open)" - ); - } - } + if changed == 0 { + return Ok(None); } + // The corpus trigger makes cached ANN handles stale. Reconcile from the + // committed database on the next query; directly adding this vector could + // race a newer correction after the conditional write succeeded. Ok(Some(vec)) } @@ -1505,3 +1500,79 @@ mod tests { drop(lock); } } + +#[cfg(test)] +mod correction_race_tests { + use super::*; + #[test] + fn slow_embedding_cannot_overwrite_a_newer_correction() { + let dir = tempfile::tempdir().unwrap(); + let db = dir.path().join("brain.db"); + let writer = rusqlite::Connection::open(&db).unwrap(); + crate::schema::initialize(&writer).unwrap(); + let accepted = kimetsu_core::event::Event::new( + kimetsu_core::ids::RunId::new(), + "memory.accepted", + serde_json::json!({"memory_id":"m","scope":"project","kind":"fact","text":"claim A"}), + ); + crate::projector::apply_events(&writer, &[accepted]).unwrap(); + let (started_tx, started_rx) = std::sync::mpsc::channel(); + let (resume_tx, resume_rx) = std::sync::mpsc::channel(); + struct Blocking { + started: std::sync::mpsc::Sender<()>, + resume: std::sync::Mutex>, + } + impl Embedder for Blocking { + fn embed(&self, _: &str) -> Result, EmbedderError> { + self.started.send(()).unwrap(); + self.resume.lock().unwrap().recv().unwrap(); + Ok(vec![1.0, 0.0]) + } + fn model_id(&self) -> &str { + "stub" + } + fn dim(&self) -> usize { + 2 + } + } + let pending = std::thread::spawn(move || { + let conn = rusqlite::Connection::open(db).unwrap(); + embed_and_persist( + &conn, + "m", + "claim A", + &Blocking { + started: started_tx, + resume: std::sync::Mutex::new(resume_rx), + }, + ) + .unwrap() + }); + started_rx.recv().unwrap(); + let correction = kimetsu_core::event::Event::new( + kimetsu_core::ids::RunId::new(), + "memory.corrected", + serde_json::json!({"memory_id":"m","text":"claim B"}), + ); + crate::projector::apply_events(&writer, &[correction]).unwrap(); + writer + .execute( + "UPDATE memories SET embedding=?1,embedding_model='stub' WHERE memory_id='m'", + rusqlite::params![encode_embedding(&[0.0, 1.0])], + ) + .unwrap(); + resume_tx.send(()).unwrap(); + assert!( + pending.join().unwrap().is_none(), + "stale computation must not be published" + ); + let blob: Vec = writer + .query_row( + "SELECT embedding FROM memories WHERE memory_id='m'", + [], + |r| r.get(0), + ) + .unwrap(); + assert_eq!(decode_embedding(&blob, Some(2)).unwrap(), vec![0.0, 1.0]); + } +} diff --git a/crates/kimetsu-brain/src/projector.rs b/crates/kimetsu-brain/src/projector.rs index 6ab1f1c..135ab34 100644 --- a/crates/kimetsu-brain/src/projector.rs +++ b/crates/kimetsu-brain/src/projector.rs @@ -208,6 +208,7 @@ fn reset_projection(conn: &Connection) -> KimetsuResult<()> { fn apply_event(conn: &Connection, event: &Event) -> KimetsuResult<()> { let event = redact_memory_event(event); + let event = bind_injected_revisions(conn, event.as_ref())?; let event = event.as_ref(); // Persist the event after memory payload redaction so durable replay tables // never become a second secret store. @@ -326,6 +327,22 @@ fn apply_memory_cited(conn: &Connection, event: &Event) -> KimetsuResult<()> { // keeps the run from breaking. return Ok(()); }; + let current_revision = claim_revision_at(conn, memory_id, None)?; + let explicit = event + .payload + .get("revision_event_id") + .and_then(|v| v.as_str()); + let exposed = run_claim_revision(conn, &event.run_id.to_string(), memory_id)?; + // A citation without a revision or exposure is ambiguous after a text + // correction. Keep its durable event, but do not credit the new claim. + let evidence_revision = explicit.map(str::to_owned).or(exposed); + if evidence_revision + .as_ref() + .is_some_and(|r| r != ¤t_revision) + || (evidence_revision.is_none() && !current_revision.starts_with("baseline:")) + { + return Ok(()); + } let turn = event .payload .get("turn") @@ -621,6 +638,23 @@ fn apply_memory_usefulness_for_run(conn: &Connection, event: &Event) -> KimetsuR }; for memory_id in &retrieved { + if let Some(exposed_revision) = run_claim_revision(conn, &run_id, memory_id)? { + if exposed_revision != claim_revision_at(conn, memory_id, None)? { + // The run saw the old proposition. Its delayed outcome belongs + // to that retained revision, even if correction removed the + // old citation projection in the meantime. + let was_cited: bool = conn.query_row( + "SELECT EXISTS(SELECT 1 FROM events WHERE run_id=?1 AND kind='memory.cited' AND json_extract(payload_json,'$.memory_id')=?2 + AND (json_extract(payload_json,'$.revision_event_id') IS NULL OR json_extract(payload_json,'$.revision_event_id')=?3) + AND rowid <= (SELECT rowid FROM events WHERE event_id=?4))", + params![run_id,memory_id,exposed_revision,event.event_id.to_string()], |r| r.get(0))?; + let delta = if was_cited { strong } else { weak }; + conn.execute("UPDATE memory_revisions SET use_count=use_count+1,usefulness_score=usefulness_score+?2, + confidence=CASE WHEN ?3 THEN MAX(0.1,MIN(0.99,confidence+?4*(?5-confidence))) ELSE confidence END + WHERE event_id=?1 AND memory_id=?6", params![exposed_revision,delta,was_cited,CONF_ALPHA,conf_target.unwrap_or(1.0),memory_id])?; + continue; + } + } let is_cited = cited.contains(memory_id); let delta = if is_cited { if strong < 0.0 { @@ -2485,6 +2519,75 @@ mod correction_regressions { schema::initialize(c).unwrap(); apply_events(c, &[event("memory.accepted", serde_json::json!({"memory_id":"m", "scope":"project", "kind":"fact", "text":"original quokka"}), "2026-01-01T00:00:00Z")]).unwrap(); } + #[test] + fn delayed_run_evidence_stays_on_the_retiring_claim() { + let c = Connection::open_in_memory().unwrap(); + seed(&c); + let run = RunId::new(); + let mut events = vec![ + event( + "run.started", + serde_json::json!({"project_id":"p","task":"t"}), + "2026-01-02T00:00:00Z", + ), + event( + "context.injected", + serde_json::json!({"memory_ids":["m"]}), + "2026-01-03T00:00:00Z", + ), + event( + "memory.cited", + serde_json::json!({"memory_id":"m","turn":1}), + "2026-01-04T00:00:00Z", + ), + event( + "memory.corrected", + serde_json::json!({"memory_id":"m","text":"new claim"}), + "2026-01-05T00:00:00Z", + ), + event( + "memory.cited", + serde_json::json!({"memory_id":"m","turn":2}), + "2026-01-06T00:00:00Z", + ), + event( + "run.finished", + serde_json::json!({"total_cost_usd":0}), + "2026-01-07T00:00:00Z", + ), + ]; + for e in &mut events { + e.run_id = run; + e.ts = OffsetDateTime::parse("2026-01-02T00:00:00Z", &Rfc3339).unwrap(); + } + apply_events(&c, &events).unwrap(); + for _ in 0..2 { + let current: (i64, f64) = c + .query_row( + "SELECT use_count,usefulness_score FROM memories WHERE memory_id='m'", + [], + |r| Ok((r.get(0)?, r.get(1)?)), + ) + .unwrap(); + assert_eq!(current, (0, 0.0)); + let retired:(i64,f64)=c.query_row("SELECT use_count,usefulness_score FROM memory_revisions WHERE event_id='baseline:m'",[],|r|Ok((r.get(0)?,r.get(1)?))).unwrap(); + assert_eq!(retired, (1, 1.0)); + rebuild_in_place(&c).unwrap(); + } + // Pre-binding events also must not transfer evidence at equal times. + c.execute("UPDATE events SET payload_json=json_remove(payload_json,'$.memory_revisions') WHERE kind='context.injected'",[]).unwrap(); + rebuild_in_place(&c).unwrap(); + assert_eq!( + c.query_row( + "SELECT use_count FROM memories WHERE memory_id='m'", + [], + |r| r.get::<_, i64>(0) + ) + .unwrap(), + 0 + ); + } + #[test] fn correction_validation_rolls_back_events_text_and_fts() { let c = Connection::open_in_memory().unwrap(); @@ -2627,3 +2730,84 @@ mod correction_regressions { assert!(revision() > before); } } + +/// Stable claim identity: kind-only revisions do not start a new claim. +/// A -> B -> A does start a new claim, even though the text repeats. +pub(crate) fn claim_revision_at( + conn: &Connection, + memory_id: &str, + known_at: Option<&str>, +) -> KimetsuResult { + let revision = conn + .query_row( + "SELECT event_id FROM ( + SELECT event_id, known_at, revision_id, text, + LAG(text) OVER (ORDER BY revision_id) AS previous_text + FROM memory_revisions WHERE memory_id=?1) + WHERE (previous_text IS NULL OR text != previous_text) + AND (?2 IS NULL OR julianday(known_at)(0), + ) + .optional()?; + Ok(revision.unwrap_or_else(|| format!("baseline:{memory_id}"))) +} + +/// Legacy injections identify IDs only. Attribute an in-flight run to its +/// earliest exposure rather than silently transferring old evidence on edit. +/// For legacy unbound events, equal-time corrections are conservatively treated +/// as later than the exposure; new bound events have exact revision identity. +fn run_claim_revision( + conn: &Connection, + run_id: &str, + memory_id: &str, +) -> KimetsuResult> { + let exposure = conn + .query_row( + "SELECT ts,payload_json FROM events e WHERE run_id=?1 AND kind='context.injected' + AND EXISTS(SELECT 1 FROM json_each(e.payload_json,'$.memory_ids') WHERE value=?2) + ORDER BY julianday(ts),rowid LIMIT 1", + params![run_id, memory_id], + |r| Ok((r.get::<_, String>(0)?, r.get::<_, String>(1)?)), + ) + .optional()?; + match exposure { + None => Ok(None), + Some((at, payload)) => { + let payload: serde_json::Value = serde_json::from_str(&payload)?; + if let Some(revision) = payload + .get("memory_revisions") + .and_then(|v| v.get(memory_id)) + .and_then(|v| v.as_str()) + { + Ok(Some(revision.to_string())) + } else { + Ok(Some(claim_revision_at(conn, memory_id, Some(&at))?)) + } + } + } +} + +/// Bind exposures while their event is first persisted, not when a delayed +/// outcome is projected. This also disambiguates equal-timestamp corrections. +fn bind_injected_revisions<'a>( + conn: &Connection, + event: &'a Event, +) -> KimetsuResult> { + if event.kind != "context.injected" || event.payload.get("memory_revisions").is_some() { + return Ok(Cow::Borrowed(event)); + } + let mut bound = event.clone(); + let mut revisions = serde_json::Map::new(); + if let Some(ids) = event.payload.get("memory_ids").and_then(|v| v.as_array()) { + for id in ids.iter().filter_map(|v| v.as_str()) { + revisions.insert( + id.to_string(), + serde_json::Value::String(claim_revision_at(conn, id, None)?), + ); + } + } + bound.payload["memory_revisions"] = serde_json::Value::Object(revisions); + Ok(Cow::Owned(bound)) +} From e8e316e8f31c823a2ac82e6dac009463aecf89f3 Mon Sep 17 00:00:00 2001 From: RodCor Date: Fri, 4 Sep 2026 21:44:16 -0300 Subject: [PATCH 03/34] fix(brain): bound live retrieval and post-rerank usefulness --- crates/kimetsu-agent/src/pipeline.rs | 3 + crates/kimetsu-brain/src/backend.rs | 41 ++- crates/kimetsu-brain/src/benchmark.rs | 3 + crates/kimetsu-brain/src/bitemporal.rs | 3 + crates/kimetsu-brain/src/context.rs | 423 +++++++++++++++++++------ crates/kimetsu-brain/src/fusion.rs | 3 + crates/kimetsu-brain/src/ordering.rs | 3 + crates/kimetsu-brain/src/reinforce.rs | 82 +++-- crates/kimetsu-chat/src/ask.rs | 3 + 9 files changed, 442 insertions(+), 122 deletions(-) diff --git a/crates/kimetsu-agent/src/pipeline.rs b/crates/kimetsu-agent/src/pipeline.rs index b8f25e1..3d87ceb 100644 --- a/crates/kimetsu-agent/src/pipeline.rs +++ b/crates/kimetsu-agent/src/pipeline.rs @@ -3152,6 +3152,9 @@ mod tests { score: 0.75, superseded_hint: false, rerank_policy_tier: 0, + claim_revision: None, + rerank_usefulness: None, + rerank_trust: None, } } diff --git a/crates/kimetsu-brain/src/backend.rs b/crates/kimetsu-brain/src/backend.rs index 154d4a5..efe531f 100644 --- a/crates/kimetsu-brain/src/backend.rs +++ b/crates/kimetsu-brain/src/backend.rs @@ -396,7 +396,8 @@ fn fetch_graph_candidates( FROM memories WHERE invalidated_at IS NULL AND superseded_by IS NULL - AND (valid_to IS NULL OR valid_to > datetime('now')) + AND (valid_from IS NULL OR julianday(valid_from) <= julianday('now')) + AND (valid_to IS NULL OR julianday(valid_to) > julianday('now')) AND memory_id IN ({placeholders})" ); @@ -441,6 +442,7 @@ fn fetch_graph_candidates( let scope_weight = crate::context::scope_weight_pub(&scope); let token_estimate = crate::context::estimate_tokens(&text) + 8; + let claim_revision = Some(crate::projector::claim_revision_at(conn, &memory_id, None)?); candidates.push(Candidate { raw_relevance, embedding: None, @@ -464,6 +466,9 @@ fn fetch_graph_candidates( score: 0.0, superseded_hint: false, rerank_policy_tier: 0, + claim_revision, + rerank_usefulness: None, + rerank_trust: None, }, }); } @@ -862,6 +867,40 @@ mod tests { use crate::projector; use crate::schema; + #[test] + fn hardening_graph_hydration_checks_future_and_offset_expiry() { + let conn = make_conn(); + for id in ["live", "future", "expired"] { + insert_memory(&conn, id, "fact", "graph fact"); + } + conn.execute( + "UPDATE memories SET valid_from='2099-01-01T00:00:00Z' WHERE memory_id='future'", + [], + ) + .unwrap(); + let expired = (time::OffsetDateTime::now_utc() - time::Duration::seconds(2)) + .to_offset(time::UtcOffset::from_hms(12, 0, 0).unwrap()) + .format(&time::format_description::well_known::Rfc3339) + .unwrap(); + conn.execute( + "UPDATE memories SET valid_to=?1 WHERE memory_id='expired'", + rusqlite::params![expired], + ) + .unwrap(); + let ids = vec![ + ("live".into(), 1), + ("future".into(), 1), + ("expired".into(), 1), + ]; + let out = fetch_graph_candidates(&conn, &ids, &mut HashSet::new(), 1.0).unwrap(); + assert_eq!(out.len(), 1); + assert_eq!(out[0].capsule.expansion_handle, "memory:live"); + assert_eq!( + out[0].capsule.claim_revision.as_deref(), + Some("baseline:live") + ); + } + /// Helper: open an in-memory brain with the current schema. fn make_conn() -> Connection { let conn = Connection::open_in_memory().expect("open_in_memory"); diff --git a/crates/kimetsu-brain/src/benchmark.rs b/crates/kimetsu-brain/src/benchmark.rs index 82dde22..528520b 100644 --- a/crates/kimetsu-brain/src/benchmark.rs +++ b/crates/kimetsu-brain/src/benchmark.rs @@ -910,6 +910,9 @@ mod tests { score, superseded_hint: false, rerank_policy_tier: 0, + claim_revision: None, + rerank_usefulness: None, + rerank_trust: None, } } } diff --git a/crates/kimetsu-brain/src/bitemporal.rs b/crates/kimetsu-brain/src/bitemporal.rs index 4e68bd5..ec4e0a7 100644 --- a/crates/kimetsu-brain/src/bitemporal.rs +++ b/crates/kimetsu-brain/src/bitemporal.rs @@ -176,6 +176,9 @@ pub fn as_of_capsules(memories: &[AsOfMemory]) -> Vec { score: 0.0, superseded_hint: false, rerank_policy_tier: 0, + claim_revision: None, + rerank_usefulness: None, + rerank_trust: None, }) .collect() } diff --git a/crates/kimetsu-brain/src/context.rs b/crates/kimetsu-brain/src/context.rs index 99884a9..babad9e 100644 --- a/crates/kimetsu-brain/src/context.rs +++ b/crates/kimetsu-brain/src/context.rs @@ -252,12 +252,41 @@ pub struct ContextCapsule { /// 0.58 → 0.36 when reranking landed without this flag). #[serde(default)] pub superseded_hint: bool, - /// v2.7: learned-usefulness tier carried across final-stage reranking. - /// Cross-encoders score query relevance only and their sigmoid scale can be - /// arbitrarily extreme, so policy is lexicographic rather than multiplied: - /// cited (+1), neutral (0), regretted (-1). Superseded capsules ignore it. + /// Legacy serialized usefulness sign; bounded when reranking old capsules. #[serde(default)] pub rerank_policy_tier: i8, + /// Claim identity read in the same SQLite snapshot as the hydrated text. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub claim_revision: Option, + /// Decayed usefulness multiplier and provenance discount carried to reranking. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub rerank_usefulness: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub rerank_trust: Option, +} + +/// Revision bindings for the actual delivered capsules. Conflicting versions of +/// one ID are omitted because the legacy event map can represent only one claim. +pub fn memory_revision_bindings( + capsules: &[ContextCapsule], +) -> std::collections::BTreeMap { + let mut bindings = std::collections::BTreeMap::new(); + let mut ambiguous = std::collections::HashSet::new(); + for c in capsules { + if let (Some(id), Some(revision)) = ( + c.expansion_handle.strip_prefix("memory:"), + c.claim_revision.as_ref(), + ) { + if bindings.get(id).is_some_and(|old| old != revision) { + ambiguous.insert(id.to_string()); + } + bindings.insert(id.to_string(), revision.clone()); + } + } + for id in ambiguous { + bindings.remove(&id); + } + bindings } impl ContextCapsule { @@ -279,6 +308,9 @@ impl ContextCapsule { score, superseded_hint: false, rerank_policy_tier: 0, + claim_revision: None, + rerank_usefulness: None, + rerank_trust: None, } } } @@ -442,31 +474,7 @@ pub struct ContextBundle { /// So `df == 0` gets the maximal weight, and only `df == N` (present in every /// memory, e.g. the project name) is zeroed. fn coverage_token_idf(conn: &Connection, tokens: &[String]) -> KimetsuResult> { - let mut idf = HashMap::new(); - let n: i64 = conn - .query_row( - "SELECT COUNT(*) FROM memories WHERE invalidated_at IS NULL", - [], - |row| row.get(0), - ) - .unwrap_or(0); - if n == 0 { - return Ok(idf); - } - let mut stmt = conn.prepare_cached( - "SELECT COUNT(*) FROM memories \ - WHERE invalidated_at IS NULL AND lower(text) LIKE ?1 ESCAPE '\\'", - )?; - for token in tokens { - let pattern = format!("%{}%", escape_like(token)); - let df: i64 = stmt - .query_row(params![pattern], |row| row.get(0)) - .unwrap_or(0); - // ln((N+1)/(df+1)): maximal at df == 0, zero at df == N. - let weight = (((n + 1) as f32) / ((df + 1) as f32)).ln().max(0.0); - idf.insert(token.clone(), weight); - } - Ok(idf) + token_idf(conn, tokens, false) } /// Render the partial-evidence warning for a bundle, if it needs one. @@ -1115,9 +1123,6 @@ pub fn search_memories_including_expired( )) })?; let now_utc = OffsetDateTime::now_utc(); - let now_rfc3339 = now_utc - .format(&time::format_description::well_known::Rfc3339) - .unwrap_or_default(); let mut capsules = Vec::new(); for row in rows { let ( @@ -1136,7 +1141,9 @@ pub fn search_memories_including_expired( let scope_weight = scope_weight(&scope); // Annotate expired memories so callers can identify them in history output. let suffix = if let Some(ref vt) = valid_to { - if vt.as_str() < now_rfc3339.as_str() { + if OffsetDateTime::parse(vt, &time::format_description::well_known::Rfc3339) + .is_ok_and(|end| end <= now_utc) + { format!(" [expired valid_to={vt}]") } else { format!(" [valid_to={vt}]") @@ -1144,6 +1151,7 @@ pub fn search_memories_including_expired( } else { String::new() }; + let claim_revision = Some(crate::projector::claim_revision_at(conn, &memory_id, None)?); capsules.push(ContextCapsule { id: new_id().to_string(), kind: "memory".to_string(), @@ -1162,6 +1170,9 @@ pub fn search_memories_including_expired( score: 0.0, superseded_hint: false, rerank_policy_tier: 0, + claim_revision, + rerank_usefulness: None, + rerank_trust: None, }); } Ok(capsules) @@ -1239,7 +1250,8 @@ fn memory_ann_candidates( FROM memories WHERE invalidated_at IS NULL AND superseded_by IS NULL - AND (valid_to IS NULL OR valid_to > datetime('now')) + AND (valid_from IS NULL OR julianday(valid_from) <= julianday('now')) + AND (valid_to IS NULL OR julianday(valid_to) > julianday('now')) AND embedding_model = ?{model_param} AND rowid IN ({placeholders})", model_param = knn_rowids.len() + 1 @@ -1285,7 +1297,8 @@ fn memory_ann_candidates( ) = row?; let (cosine, row_vec) = compute_cosine_and_vec(Some(qe), embedding.as_deref(), embedding_model.as_deref()); - if let Some(candidate) = memory_row_to_candidate( + let claim_revision = crate::projector::claim_revision_at(conn, &memory_id, None)?; + if let Some(mut candidate) = memory_row_to_candidate( query_tokens, memory_id, scope, @@ -1302,6 +1315,7 @@ fn memory_ann_candidates( cosine, row_vec, ) { + candidate.capsule.claim_revision = Some(claim_revision); candidates.push(candidate); } } @@ -1413,7 +1427,8 @@ fn latest_memory_candidates( FROM memories WHERE invalidated_at IS NULL AND superseded_by IS NULL - AND (valid_to IS NULL OR valid_to > datetime('now')) + AND (valid_from IS NULL OR julianday(valid_from) <= julianday('now')) + AND (valid_to IS NULL OR julianday(valid_to) > julianday('now')) ORDER BY created_at DESC LIMIT ?1 ", @@ -1457,7 +1472,8 @@ fn latest_memory_candidates( embedding.as_deref(), embedding_model.as_deref(), ); - if let Some(candidate) = memory_row_to_candidate( + let claim_revision = crate::projector::claim_revision_at(conn, &memory_id, None)?; + if let Some(mut candidate) = memory_row_to_candidate( query_tokens, memory_id, scope, @@ -1474,6 +1490,7 @@ fn latest_memory_candidates( cosine, row_vec, ) { + candidate.capsule.claim_revision = Some(claim_revision); candidates.push(candidate); } } @@ -1499,7 +1516,8 @@ fn memory_fts_candidates( ON m.memory_id = memories_fts.memory_id WHERE m.invalidated_at IS NULL AND m.superseded_by IS NULL - AND (m.valid_to IS NULL OR m.valid_to > datetime('now')) + AND (m.valid_from IS NULL OR julianday(m.valid_from) <= julianday('now')) + AND (m.valid_to IS NULL OR julianday(m.valid_to) > julianday('now')) AND memories_fts MATCH ?1 ORDER BY rank LIMIT ?2 @@ -1547,7 +1565,8 @@ fn memory_fts_candidates( embedding.as_deref(), embedding_model.as_deref(), ); - if let Some(candidate) = memory_row_to_candidate( + let claim_revision = crate::projector::claim_revision_at(conn, &memory_id, None)?; + if let Some(mut candidate) = memory_row_to_candidate( query_tokens, memory_id, scope, @@ -1564,6 +1583,7 @@ fn memory_fts_candidates( cosine, row_vec, ) { + candidate.capsule.claim_revision = Some(claim_revision); candidates.push(candidate); } } @@ -1687,9 +1707,7 @@ fn memory_row_to_candidate( let decay = usefulness_decay(last_useful_at.as_deref(), &created_at, half_life_days); let multiplier = 1.0 + (raw_multiplier - 1.0) * decay; let biased_relevance = apply_usefulness_boost(raw_relevance, multiplier); - // Carry only the learned usefulness policy across reranking. It is a tier, - // not a score multiplier: cross-encoder probabilities can differ by 100x - // for equally useful paraphrases, so multiplication cannot preserve policy. + // Retain the sign for older serialized consumers. New capsules carry the full multiplier. let rerank_policy_tier = if multiplier > 1.0 + f32::EPSILON { 1 } else if multiplier < 1.0 - f32::EPSILON { @@ -1733,6 +1751,12 @@ fn memory_row_to_candidate( score: 0.0, superseded_hint: false, rerank_policy_tier, + claim_revision: None, + rerank_usefulness: Some(multiplier), + rerank_trust: Some(crate::trust::trust_multiplier( + provenance, + last_useful_at.is_some(), + )), }, }) } @@ -1875,6 +1899,9 @@ fn repo_file_candidates( score: 0.0, superseded_hint: false, rerank_policy_tier: 0, + claim_revision: None, + rerank_usefulness: None, + rerank_trust: None, }, }); } @@ -1945,6 +1972,9 @@ fn manifest_candidates( score: 0.0, superseded_hint: false, rerank_policy_tier: 0, + claim_revision: None, + rerank_usefulness: None, + rerank_trust: None, }, }); } @@ -2007,6 +2037,9 @@ fn manifest_fts_candidates( score: 0.0, superseded_hint: false, rerank_policy_tier: 0, + claim_revision: None, + rerank_usefulness: None, + rerank_trust: None, }, }); } @@ -2338,7 +2371,9 @@ fn freshness(created_at: &str) -> f32 { }; let age = OffsetDateTime::now_utc() - created_at; let age_days = age.whole_seconds().max(0) as f32 / 86_400.0; - (-age_days / 30.0).exp().clamp(0.0, 1.0) + (-std::f32::consts::LN_2 * age_days / 30.0) + .exp() + .clamp(0.0, 1.0) } /// v1.0.0: a memory whose cosine to the query clears this bar is kept by @@ -2412,28 +2447,36 @@ fn content_tokens(query: &str) -> Vec { /// Everything in between gets `idf = ln((N+1)/(df+1))` — rarer ⇒ larger. /// Best-effort: a query/count failure yields 0 for that token (fail-open). fn corpus_token_idf(conn: &Connection, tokens: &[String]) -> KimetsuResult> { + token_idf(conn, tokens, true) +} + +/// Prefix MATCH uses the same FTS tokenizer as candidate retrieval. Each matched +/// memory counts once even when its text repeats a token. N and df both refer to +/// non-invalidated indexed documents, including historical/superseded documents. +/// One indexed posting-list lookup per unique token replaces per-term text scans. +fn token_idf( + conn: &Connection, + tokens: &[String], + zero_absent: bool, +) -> KimetsuResult> { + let n: i64 = conn.query_row( + "SELECT COUNT(*) FROM memories_fts JOIN memories m USING(memory_id) WHERE m.invalidated_at IS NULL", + [], |r| r.get(0))?; let mut idf = HashMap::new(); - let n: i64 = conn - .query_row( - "SELECT COUNT(*) FROM memories WHERE invalidated_at IS NULL", - [], - |row| row.get(0), - ) - .unwrap_or(0); if n == 0 { return Ok(idf); } let mut stmt = conn.prepare_cached( - "SELECT COUNT(*) FROM memories \ - WHERE invalidated_at IS NULL AND lower(text) LIKE ?1 ESCAPE '\\'", + "SELECT COUNT(DISTINCT m.memory_id) FROM memories_fts JOIN memories m USING(memory_id) + WHERE memories_fts MATCH ?1 AND m.invalidated_at IS NULL", )?; for token in tokens { - let pattern = format!("%{}%", escape_like(token)); - let df: i64 = stmt - .query_row(params![pattern], |row| row.get(0)) - .unwrap_or(0); - // df == 0 → out-of-corpus, can't discriminate → weight 0. - let weight = if df == 0 { + if idf.contains_key(token) { + continue; + } + let pattern = format!("text : \"{}\"*", token.replace('"', "\"\"")); + let df: i64 = stmt.query_row(params![pattern], |r| r.get(0))?; + let weight = if zero_absent && df == 0 { 0.0 } else { (((n + 1) as f32) / ((df + 1) as f32)).ln().max(0.0) @@ -2443,15 +2486,6 @@ fn corpus_token_idf(conn: &Connection, tokens: &[String]) -> KimetsuResult String { - token - .replace('\\', "\\\\") - .replace('%', "\\%") - .replace('_', "\\_") -} - /// v1.0.0: the IDF-weighted fraction of the query's discriminating power that /// `summary` lexically covers, in `[0,1]`. Tokens present in the haystack /// contribute their IDF weight to the numerator; all tokens contribute to the @@ -2478,8 +2512,8 @@ fn weighted_coverage(content: &[String], idf: &HashMap, summary: &s /// v1.0.0: light query-side stemming — strip the common English inflection /// suffixes so "benchmarked"/"benchmarking" reduce to "benchmark". Because -/// downstream matching is substring (`lexical_relevance`, the IDF `LIKE` -/// document-frequency count) and FTS-prefix (`fts_query` appends `*`), the +/// downstream matching uses substring lexical hints and FTS-prefix document +/// frequencies (`fts_query` also appends `*`), the /// stem matches every variant in the corpus while the inflected form matches /// none of them — an unstemmed "benchmarked" gets df=0, loses all IDF /// weight, and the relevance floor goes blind on the query's one @@ -3257,17 +3291,19 @@ fn rerank_capsules_with_diagnostics( } }; - // Learned usefulness is a policy tier above cross-encoder relevance, not a - // multiplier on it. TinyBERT assigned two useful paraphrases 0.998 vs - // 0.0098 in BrainBench; no bounded multiplier can preserve a citation - // policy on that scale. Within each tier the cross-encoder owns ordering. - // Supersession is likewise reapplied here, after the cross-encoder, so all - // callers (including benchmark-only direct calls) get the same policy. + // Apply bounded usefulness, then trust and supersession; admission uses raw scores. let mut ranked: Vec<(ContextCapsule, f32)> = capsules .into_iter() .zip(scores) .map(|(mut c, s)| { - c.score = s; + let multiplier = if c.superseded_hint { + 1.0 + } else { + c.rerank_usefulness + .unwrap_or(1.0 + 0.5 * effective_rerank_policy_tier(&c) as f32) + }; + c.score = apply_usefulness_boost(s, multiplier.clamp(0.5, 1.5)) + * c.rerank_trust.unwrap_or(1.0).clamp(0.0, 1.0); if c.superseded_hint { c.score *= SUPERSESSION_PENALTY; } @@ -3275,15 +3311,7 @@ fn rerank_capsules_with_diagnostics( }) .collect(); - ranked.sort_by(|a, b| { - effective_rerank_policy_tier(&b.0) - .cmp(&effective_rerank_policy_tier(&a.0)) - .then_with(|| { - b.0.score - .partial_cmp(&a.0.score) - .unwrap_or(std::cmp::Ordering::Equal) - }) - }); + ranked.sort_by(|a, b| b.0.score.total_cmp(&a.0.score)); // This is the ordinary per-capsule retention floor. The separate // evidence-band decision reads `best_raw_score` before the supersession @@ -3328,6 +3356,9 @@ mod tests { score: 1.0, superseded_hint: false, rerank_policy_tier: 0, + claim_revision: None, + rerank_usefulness: None, + rerank_trust: None, } } @@ -4522,12 +4553,6 @@ mod tests { assert!(cov_topical > 0.6, "got {cov_topical}"); } - #[test] - fn escape_like_neutralizes_wildcards() { - assert_eq!(escape_like("a_b%c"), "a\\_b\\%c"); - assert_eq!(escape_like("plain"), "plain"); - } - /// The reported regression, reproduced end-to-end on the FTS-only path: /// a corpus of unrelated debugging war-stories that all happen to contain /// the project name "kimetsu", queried with a broad conceptual prompt. @@ -5263,6 +5288,9 @@ mod tests { score: 0.0, superseded_hint: false, rerank_policy_tier: 0, + claim_revision: None, + rerank_usefulness: None, + rerank_trust: None, }, raw_relevance: raw, embedding: None, @@ -5336,6 +5364,9 @@ mod tests { score, superseded_hint: false, rerank_policy_tier: 0, + claim_revision: None, + rerank_usefulness: None, + rerank_trust: None, }, raw_relevance: score, embedding: Some(embedding), @@ -6051,6 +6082,9 @@ mod tests { score, superseded_hint: false, rerank_policy_tier: 0, + claim_revision: None, + rerank_usefulness: None, + rerank_trust: None, } } @@ -6136,6 +6170,58 @@ mod tests { ); } + #[test] + fn hardening_usefulness_cannot_dominate_relevance() { + let neutral = make_capsule("neutral", 0.0); + let mut useful = make_capsule("useful", 0.0); + useful.rerank_policy_tier = 1; + let out = rerank_capsules( + "q", + vec![neutral, useful], + &TwoScoreReranker(0.99, 0.31), + 0.30, + 0, + ); + assert_eq!(out[0].summary, "neutral"); + assert!(out[1].score <= 0.410001); + } + + #[test] + fn hardening_rerank_preserves_decayed_usefulness_and_trust() { + let neutral = make_capsule("neutral", 0.0); + let mut stale_useful = make_capsule("stale", 0.0); + stale_useful.rerank_policy_tier = 1; + stale_useful.rerank_usefulness = Some(1.0001); + let out = rerank_capsules( + "q", + vec![neutral.clone(), stale_useful], + &TwoScoreReranker(0.9, 0.85), + 0.0, + 0, + ); + assert_eq!(out[0].summary, "neutral"); + let mut imported = make_capsule("imported", 0.0); + imported.rerank_usefulness = Some(1.5); + imported.rerank_trust = Some(0.5); + let out = rerank_capsules( + "q", + vec![neutral, imported], + &TwoScoreReranker(0.8, 0.9), + 0.0, + 0, + ); + assert_eq!(out[0].summary, "neutral"); + assert!((out[1].score - 0.5).abs() < 0.00001); + } + + #[test] + fn hardening_freshness_has_thirty_day_half_life() { + let past = (OffsetDateTime::now_utc() - time::Duration::days(30)) + .format(&time::format_description::well_known::Rfc3339) + .unwrap(); + assert!((freshness(&past) - 0.5).abs() < 0.0001); + } + #[test] fn rerank_reapplies_usefulness_but_not_to_superseded_capsules() { let neutral = make_capsule("neutral", 0.0); @@ -6145,7 +6231,7 @@ mod tests { let out = rerank_capsules( "q", vec![neutral.clone(), useful.clone()], - &TwoScoreReranker(0.60, 0.50), + &TwoScoreReranker(0.59, 0.50), 0.0, 0, ); @@ -6156,7 +6242,7 @@ mod tests { let out = rerank_capsules( "q", vec![neutral, useful], - &TwoScoreReranker(0.60, 0.50), + &TwoScoreReranker(0.59, 0.50), 0.0, 0, ); @@ -6300,8 +6386,7 @@ mod tests { } /// Admission evidence is computed before policy ordering and output cap. - /// A cited low-score capsule may own the only output slot, but a high raw - /// score elsewhere in the candidate pool still proves the bundle useful. + /// Bounded usefulness cannot displace clearly stronger raw evidence. #[test] fn band_arbitration_uses_raw_evidence_before_policy_cap() { let mut bundle = band_bundle(0.50); @@ -6314,7 +6399,7 @@ mod tests { let out = rerank_and_arbitrate("q", bundle, Some(&reranker), 0.55, 0.0, 1); assert!(!out.skipped, "raw evidence outside the cap must admit"); assert_eq!(out.capsules.len(), 1); - assert_eq!(out.capsules[0].summary, "a memory lesson"); + assert_eq!(out.capsules[0].summary, "high-confidence neutral"); } /// Above the band the cross-encoder reorders but never converts, even @@ -6536,6 +6621,8 @@ mod evidence_tests { rusqlite::params![format!("m{i}"), text], ) .expect("insert"); + conn.execute("INSERT INTO memories_fts(memory_id,text,kind,scope) VALUES (?1,?2,'fact','project')", + params![format!("m{i}"),text]).unwrap(); } conn } @@ -6555,6 +6642,9 @@ mod evidence_tests { score: 0.5, superseded_hint: false, rerank_policy_tier: 0, + claim_revision: None, + rerank_usefulness: None, + rerank_trust: None, } } @@ -6993,3 +7083,144 @@ mod evidence_tests { ); } } + +#[cfg(test)] +mod hardening_tests { + use super::*; + + fn corpus() -> Connection { + let conn = Connection::open_in_memory().unwrap(); + crate::schema::initialize(&conn).unwrap(); + for (id, text) in [ + ("live", "routing routing routes"), + ("future", "routing"), + ("expired", "routing"), + ("offset", "routing"), + ("other", "rerouting unrelated"), + ] { + conn.execute("INSERT INTO memories (memory_id,scope,kind,text,normalized_text,confidence,created_at,provenance_snapshot_json) + VALUES (?1,'project','fact',?2,?2,1,'2020-01-01T00:00:00Z','{}')", params![id,text]).unwrap(); + conn.execute("INSERT INTO memories_fts(memory_id,text,kind,scope) VALUES (?1,?2,'fact','project')",params![id,text]).unwrap(); + } + conn + } + + #[test] + fn hardening_live_lexical_and_recency_apply_both_time_bounds() { + let conn = corpus(); + let now = OffsetDateTime::now_utc(); + let fmt = &time::format_description::well_known::Rfc3339; + let future = (now + time::Duration::hours(1)).format(fmt).unwrap(); + let expired = (now - time::Duration::seconds(2)).format(fmt).unwrap(); + let offset = (now - time::Duration::seconds(2)) + .to_offset(time::UtcOffset::from_hms(12, 0, 0).unwrap()) + .format(fmt) + .unwrap(); + conn.execute( + "UPDATE memories SET valid_from=?1 WHERE memory_id='future'", + params![future], + ) + .unwrap(); + conn.execute( + "UPDATE memories SET valid_to=?1 WHERE memory_id='expired'", + params![expired], + ) + .unwrap(); + conn.execute( + "UPDATE memories SET valid_to=?1 WHERE memory_id='offset'", + params![offset], + ) + .unwrap(); + for candidates in [ + memory_fts_candidates(&conn, &["routing".into()], "routing*", 80, None, 30.0).unwrap(), + latest_memory_candidates(&conn, &["routing".into()], 200, None, 30.0).unwrap(), + ] { + let ids: Vec<_> = candidates + .iter() + .map(|c| c.capsule.expansion_handle.as_str()) + .collect(); + assert!(ids.contains(&"memory:live")); + for id in ["memory:future", "memory:expired", "memory:offset"] { + assert!(!ids.contains(&id), "returned {id}"); + } + } + } + + #[test] + fn hardening_hydration_binds_text_revision_before_later_correction() { + let conn = corpus(); + let candidates = + memory_fts_candidates(&conn, &["routing".into()], "routing*", 80, None, 30.0).unwrap(); + let capsules: Vec<_> = candidates.into_iter().map(|c| c.capsule).collect(); + assert_eq!(memory_revision_bindings(&capsules)["live"], "baseline:live"); + conn.execute("INSERT INTO memory_revisions(memory_id,event_id,text,kind,known_at,effective_at,confidence,use_count,usefulness_score) + VALUES ('live','corrected','changed claim','fact','2026-01-01T00:00:00Z','2026-01-01T00:00:00Z',1,0,0)",[]).unwrap(); + conn.execute( + "UPDATE memories SET text='changed claim' WHERE memory_id='live'", + [], + ) + .unwrap(); + assert_eq!( + crate::projector::claim_revision_at(&conn, "live", None).unwrap(), + "corrected" + ); + assert_eq!(memory_revision_bindings(&capsules)["live"], "baseline:live"); + assert!( + capsules + .iter() + .find(|c| c.expansion_handle == "memory:live") + .unwrap() + .summary + .contains("routing routing") + ); + } + + #[cfg(feature = "embeddings")] + #[test] + fn hardening_ann_hydration_filters_time_bounds() { + let conn = corpus(); + let blob = crate::embeddings::encode_embedding(&[1.0, 0.0]); + conn.execute( + "UPDATE memories SET embedding=?1,embedding_model='test'", + params![blob], + ) + .unwrap(); + conn.execute( + "UPDATE memories SET valid_from='2099-01-01T00:00:00Z' WHERE memory_id='future'", + [], + ) + .unwrap(); + let expired = (OffsetDateTime::now_utc() - time::Duration::seconds(2)) + .to_offset(time::UtcOffset::from_hms(12, 0, 0).unwrap()) + .format(&time::format_description::well_known::Rfc3339) + .unwrap(); + conn.execute( + "UPDATE memories SET valid_to=?1 WHERE memory_id IN ('expired','offset')", + params![expired], + ) + .unwrap(); + let qe = QueryEmbedding { + vector: vec![1.0, 0.0], + model_id: "test".into(), + }; + let out = memory_ann_candidates(&conn, &qe, 80, &["routing".into()], 30.0).unwrap(); + assert_eq!(out.len(), 2); + for c in out { + assert!(matches!( + c.capsule.expansion_handle.as_str(), + "memory:live" | "memory:other" + )); + } + } + + #[test] + fn hardening_idf_counts_prefix_documents_not_occurrences_or_substrings() { + let conn = corpus(); + let tokens = vec!["rout".into(), "absent".into()]; + let coverage = coverage_token_idf(&conn, &tokens).unwrap(); + // Four prefix-matching documents out of five; repeated terms count once. + assert!((coverage["rout"] - (6.0_f32 / 5.0).ln()).abs() < 0.00001); + assert!((coverage["absent"] - 6.0_f32.ln()).abs() < 0.00001); + assert_eq!(corpus_token_idf(&conn, &tokens).unwrap()["absent"], 0.0); + } +} diff --git a/crates/kimetsu-brain/src/fusion.rs b/crates/kimetsu-brain/src/fusion.rs index e1a7b8e..c8b8dba 100644 --- a/crates/kimetsu-brain/src/fusion.rs +++ b/crates/kimetsu-brain/src/fusion.rs @@ -210,6 +210,9 @@ mod tests { score: 0.0, superseded_hint: false, rerank_policy_tier: 0, + claim_revision: None, + rerank_usefulness: None, + rerank_trust: None, }, raw_relevance: relevance, embedding: None, diff --git a/crates/kimetsu-brain/src/ordering.rs b/crates/kimetsu-brain/src/ordering.rs index a8ea30c..fdbdd46 100644 --- a/crates/kimetsu-brain/src/ordering.rs +++ b/crates/kimetsu-brain/src/ordering.rs @@ -167,6 +167,9 @@ mod tests { score: 0.5, superseded_hint: false, rerank_policy_tier: 0, + claim_revision: None, + rerank_usefulness: None, + rerank_trust: None, } } diff --git a/crates/kimetsu-brain/src/reinforce.rs b/crates/kimetsu-brain/src/reinforce.rs index 72bfb93..d075172 100644 --- a/crates/kimetsu-brain/src/reinforce.rs +++ b/crates/kimetsu-brain/src/reinforce.rs @@ -19,7 +19,7 @@ //! activation budget and power-law decay. //! //! Both run OFFLINE via `kimetsu brain reinforce` (never in the retrieval -//! hot path); the boost lookup at retrieval time is one indexed SQL read. +//! hot path); retrieval reads a bounded shortlist through indexed query/candidate keys. use std::collections::BTreeMap; use std::path::Path; @@ -318,35 +318,39 @@ pub(crate) fn apply_query_routing( query_embedding: Option<&QueryEmbedding>, candidates: &mut [crate::context::Candidate], ) { - // Table may not exist on old brains mid-migration; treat errors as "no routes". - let Ok(mut stmt) = conn.prepare( - "SELECT query_norm, memory_id, cites, last_cited_at, query_embedding - FROM query_routes WHERE cites >= ?1", - ) else { - return; - }; - let rows: Vec = match stmt.query_map(params![ROUTE_MIN_CITES], |row| { - Ok(( - row.get(0)?, - row.get(1)?, - row.get(2)?, - row.get(3)?, - row.get(4)?, - )) - }) { - Ok(mapped) => mapped.flatten().collect(), - Err(_) => return, - }; - if rows.is_empty() { - return; - } - let query_norm = query.trim().to_lowercase(); + // Bounded shortlist: exact query routes first (primary key), then at most + // 32 indexed routes for each of 64 deterministic candidate IDs. This is + // candidate-local semantic routing, not global nearest-neighbor search. + let mut ids: Vec<_> = candidates + .iter() + .filter_map(|c| c.capsule.expansion_handle.strip_prefix("memory:")) + .collect(); + ids.sort_unstable(); + ids.dedup(); + ids.truncate(64); + let mut rows: BTreeMap<(String, String), RouteRow> = BTreeMap::new(); + for (sql, keys) in [ + ("SELECT query_norm,memory_id,cites,last_cited_at,query_embedding FROM query_routes + WHERE query_norm=?1 ORDER BY memory_id LIMIT 64", vec![query_norm.as_str()]), + ("SELECT query_norm,memory_id,cites,last_cited_at,query_embedding FROM query_routes INDEXED BY idx_query_routes_memory + WHERE memory_id=?1 ORDER BY rowid LIMIT 32", ids), + ] { + let Ok(mut stmt) = conn.prepare_cached(sql) else { return; }; + for key in keys { + let Ok(mapped) = stmt.query_map(params![key], |row| Ok((row.get::<_,String>(0)?, + row.get::<_,String>(1)?,row.get::<_,i64>(2)?,row.get::<_,String>(3)?, + row.get::<_,Option>>(4)?))) else { continue; }; + for row in mapped.flatten() { + if row.2 >= i64::from(ROUTE_MIN_CITES) { rows.insert((row.0.clone(),row.1.clone()),row); } + } + } + } let now = OffsetDateTime::now_utc(); // Aggregate weight per memory across all matching routes. let mut weights: BTreeMap = BTreeMap::new(); - for (route_q, memory_id, cites, last_cited_at, blob) in rows { + for (route_q, memory_id, cites, last_cited_at, blob) in rows.into_values() { let sim = if route_q == query_norm { 1.0 } else { @@ -431,10 +435,38 @@ mod tests { score: 0.0, superseded_hint: false, rerank_policy_tier: 0, + claim_revision: None, + rerank_usefulness: None, + rerank_trust: None, }, } } + #[test] + fn hardening_semantic_routes_ignore_unrelated_candidate_ids() { + let conn = Connection::open_in_memory().unwrap(); + crate::schema::initialize(&conn).unwrap(); + let now = OffsetDateTime::now_utc() + .format(&time::format_description::well_known::Rfc3339) + .unwrap(); + let blob = encode_embedding(&[1.0, 0.0]); + for i in 0..200 { + conn.execute( + "INSERT INTO query_routes(query_norm,memory_id,cites,last_cited_at,query_embedding) + VALUES (?1,?2,3,?3,?4)", + params![format!("route{i}"), format!("m{i}"), now, blob], + ) + .unwrap(); + } + let qe = QueryEmbedding { + vector: vec![1.0, 0.0], + model_id: "test".into(), + }; + let mut candidates = vec![mem_candidate("m0")]; + apply_query_routing(&conn, "different paraphrase", Some(&qe), &mut candidates); + assert!((candidates[0].raw_relevance - (0.5 + ROUTING_BOOST_CAP)).abs() < 0.00001); + } + /// Two memories cited together twice -> ONE staple containing both texts, /// originals kept, provenance carries the part ids, re-run is a no-op. #[test] diff --git a/crates/kimetsu-chat/src/ask.rs b/crates/kimetsu-chat/src/ask.rs index e4644b0..25899f3 100644 --- a/crates/kimetsu-chat/src/ask.rs +++ b/crates/kimetsu-chat/src/ask.rs @@ -409,6 +409,9 @@ mod tests { score: 0.9, superseded_hint: false, rerank_policy_tier: 0, + claim_revision: None, + rerank_usefulness: None, + rerank_trust: None, } } From bf34021ca5619774b55bdfc7a34ef096836ef89c Mon Sep 17 00:00:00 2001 From: RodCor Date: Fri, 4 Sep 2026 21:46:00 -0300 Subject: [PATCH 04/34] Preserve distinct claims and make lifecycle decisions conservative --- crates/kimetsu-brain/src/conflict.rs | 186 +++++--------- crates/kimetsu-brain/src/consolidate.rs | 319 ++++++++++++++---------- crates/kimetsu-brain/src/lifecycle.rs | 99 ++++++-- crates/kimetsu-brain/src/project.rs | 113 +++++---- crates/kimetsu-brain/src/projector.rs | 13 + crates/kimetsu-cli/src/distiller.rs | 227 ++++++++++------- crates/kimetsu-core/src/config.rs | 40 ++- 7 files changed, 554 insertions(+), 443 deletions(-) diff --git a/crates/kimetsu-brain/src/conflict.rs b/crates/kimetsu-brain/src/conflict.rs index 033fffb..cc45f40 100644 --- a/crates/kimetsu-brain/src/conflict.rs +++ b/crates/kimetsu-brain/src/conflict.rs @@ -556,25 +556,10 @@ pub(crate) fn detect_and_record_with_vec( recorded } -/// Story 1.3 / Pass B: detect conflicts AND attempt auto-resolution. -/// -/// For each conflict hit: -/// 1. Read confidence + created_at from the existing memory row. -/// 2. Compute `resolution_score` for both sides. -/// 3. When |Δ| ≥ `NEAR_TIE_BAND`: stamp the loser's `valid_to` to now via -/// `mark_memory_temporal` (event-sourced, rebuild-safe). Also record the -/// conflict row with a pre-filled `resolution` label so the operator can -/// see it was auto-resolved. -/// 4. When |Δ| < `NEAR_TIE_BAND`: record to `memory_conflicts` for operator -/// review (same as v0.5.2 behavior). Nothing auto-stamped. -/// -/// `new_confidence`: the confidence of the newly-added memory (0-1). -/// `new_created_at`: RFC 3339 timestamp of the newly-added memory. -/// -/// Returns `(auto_resolved, queued)` counts. -/// -/// Best-effort: errors inside resolution are downgraded to a stderr line — -/// never fail an otherwise-valid memory write. +/// Queue similarity candidates for explicit review. Similarity and a score gap +/// cannot establish a contradiction or which claim is correct. The legacy +/// confidence/time arguments and `(auto_resolved, queued)` return shape remain +/// compatible, but auto_resolved is always zero. Recording is best-effort. #[allow(clippy::too_many_arguments)] pub(crate) fn detect_record_and_resolve_with_vec( conn: &Connection, @@ -604,117 +589,19 @@ pub(crate) fn detect_record_and_resolve_with_vec( } }; - let mut auto_resolved = 0usize; - let mut queued = 0usize; - + // Cosine and confidence/age gaps cannot establish a contradiction. Preserve + // the legacy entry point for callers/config compatibility, but leave destructive + // resolution to explicit corrections or operator decisions until structured + // claim identity and independent contradiction evidence are available. + let _ = (new_confidence, new_created_at); + let mut queued = 0; for hit in &hits { - // Fetch existing memory's confidence + created_at for scoring. - let existing_row: Option<(f64, String)> = conn - .query_row( - "SELECT confidence, created_at FROM memories WHERE memory_id = ?1", - params![hit.existing_memory_id], - |row| Ok((row.get::<_, f64>(0)?, row.get::<_, String>(1)?)), - ) - .optional() - .unwrap_or(None); - - let outcome = if let Some((existing_conf, existing_created_at)) = existing_row { - let new_score = resolution_score(new_confidence, new_created_at); - let existing_score = resolution_score(existing_conf as f32, &existing_created_at); - let delta = (new_score - existing_score).abs(); - - if delta >= NEAR_TIE_BAND { - // Clear winner: stamp the loser's valid_to to now. - let now_str = match OffsetDateTime::now_utc().format(&Rfc3339) { - Ok(s) => s, - Err(e) => { - eprintln!("kimetsu-brain: timestamp format error: {e}"); - // Fall back to queue on timestamp error. - if let Err(e) = record_conflict(conn, new_memory_id, scope, kind, hit) { - eprintln!( - "kimetsu-brain: failed to record near-tie conflict {} <-> {}: {e}", - new_memory_id, hit.existing_memory_id - ); - } - queued += 1; - continue; - } - }; - - let (loser_id, resolution_label) = if new_score >= existing_score { - // New memory wins; existing loses. - (hit.existing_memory_id.as_str(), "auto_resolved:new_won") - } else { - // Existing memory wins; new memory loses. - (new_memory_id, "auto_resolved:existing_won") - }; - - // Stamp valid_to on the loser (event-sourced via mark_memory_temporal). - if let Err(e) = - crate::projector::mark_memory_temporal(conn, loser_id, None, Some(&now_str)) - { - eprintln!("kimetsu-brain: auto-resolution stamp failed for {loser_id}: {e}"); - // Fall back to queue. - if let Err(e) = record_conflict(conn, new_memory_id, scope, kind, hit) { - eprintln!( - "kimetsu-brain: fallback queue failed {} <-> {}: {e}", - new_memory_id, hit.existing_memory_id - ); - } - queued += 1; - continue; - } - - // Record in memory_conflicts with resolution pre-filled so the - // operator can audit auto-resolved pairs. - match record_conflict(conn, new_memory_id, scope, kind, hit) { - Ok(conflict_id) => { - // Stamp resolved_at + resolution label. - conn.execute( - "UPDATE memory_conflicts \ - SET resolved_at = ?2, resolution = ?3 \ - WHERE conflict_id = ?1 AND resolved_at IS NULL", - params![conflict_id, now_str, resolution_label], - ) - .unwrap_or(0); - auto_resolved += 1; - } - Err(e) => { - eprintln!( - "kimetsu-brain: failed to record auto-resolved conflict {} <-> {}: {e}", - new_memory_id, hit.existing_memory_id - ); - } - } - - if new_score >= existing_score { - ResolutionOutcome::AutoResolvedNewWon - } else { - ResolutionOutcome::AutoResolvedExistingWon - } - } else { - // Near-tie: queue for operator review. - ResolutionOutcome::NearTieQueued - } - } else { - // Existing memory row not found (race/deleted): fall back to queue. - ResolutionOutcome::NearTieQueued - }; - - if outcome == ResolutionOutcome::NearTieQueued { - match record_conflict(conn, new_memory_id, scope, kind, hit) { - Ok(_) => queued += 1, - Err(e) => { - eprintln!( - "kimetsu-brain: failed to record near-tie conflict {} <-> {}: {e}", - new_memory_id, hit.existing_memory_id - ); - } - } + match record_conflict(conn, new_memory_id, scope, kind, hit) { + Ok(_) => queued += 1, + Err(e) => eprintln!("kimetsu-brain: could not queue related claims: {e}"), } } - - (auto_resolved, queued) + (0, queued) } /// List open (unresolved) conflicts ordered by most recent first, @@ -1762,6 +1649,51 @@ mod tests { } } + #[test] + fn high_similarity_and_score_gap_do_not_prove_contradiction() { + let conn = open_test_brain(); + let stub = StubEmbedder::new(); + let old = "The development service uses SQLite."; + let new = "The production service uses SQLite."; + let now = OffsetDateTime::now_utc().format(&Rfc3339).unwrap(); + insert_memory_with_meta( + &conn, + "old", + "global_user", + "fact", + old, + 0.1, + "2020-01-01T00:00:00Z", + &stub, + ); + insert_memory_with_meta(&conn, "new", "global_user", "fact", new, 1.0, &now, &stub); + let vector = stub.embed(old).unwrap(); + let (resolved, queued) = detect_record_and_resolve_with_vec( + &conn, + "new", + &MemoryScope::GlobalUser, + "fact", + new, + Some(&vector), + &stub, + 1.0, + &now, + ); + assert_eq!( + resolved, 0, + "no automatic retirement without a proven conflicting claim" + ); + assert_eq!(queued, 1, "related claims remain reviewable"); + let retired: i64 = conn + .query_row( + "SELECT COUNT(*) FROM memories WHERE valid_to IS NOT NULL", + [], + |r| r.get(0), + ) + .unwrap(); + assert_eq!(retired, 0); + } + /// Pass B: auto-resolved stamped valid_to survives rebuild_in_place /// (replay-safe via the event log). #[test] diff --git a/crates/kimetsu-brain/src/consolidate.rs b/crates/kimetsu-brain/src/consolidate.rs index c490c6d..974aa5b 100644 --- a/crates/kimetsu-brain/src/consolidate.rs +++ b/crates/kimetsu-brain/src/consolidate.rs @@ -3,19 +3,12 @@ //! //! # Near-duplicate merge (Story 3.1) //! -//! For each memory with a stored embedding, find other memories (same -//! `embedding_model`) whose cosine similarity exceeds a threshold (default -//! 0.92). Union-find clusters the pairs; the survivor of each cluster is the -//! memory with the highest `(usefulness_score × recency rank)`. Merge plan: -//! - Survivor keeps its text/id; `use_count` and `usefulness_score` become -//! cluster sums. -//! - Citations are reassigned to the survivor (`UPDATE memory_citations`). -//! - Members get `superseded_by = survivor_id` via a `memory.superseded` -//! event (so `brain rebuild` reproduces the merge). -//! -//! The cosine scan is brute-force O(N²) over decoded embeddings within the -//! same `model_id`. This is intentionally simple and correct for the current -//! scale (< 10k memories). A future optimisation would reuse the ANN index. +//! Only identical text within the same scope, kind and embedding model can +//! consolidate. Each member must meet the cosine threshold against its survivor; +//! similarity chains cannot bridge unrelated vectors. Each pass performs at most +//! 100,000 cosine comparisons. Survivor evidence counts stay unchanged: duplicate +//! storage is not independent support. An atomic, revalidated event batch retires +//! members and reassigns citations, preserving the operation on rebuild. //! //! # Cluster distillation (Story 3.2) //! @@ -209,6 +202,7 @@ pub fn load_embeddable_rows( FROM memories WHERE invalidated_at IS NULL AND superseded_by IS NULL + AND valid_from IS NULL AND valid_to IS NULL AND embedding IS NOT NULL AND embedding_model IS NOT NULL ORDER BY created_at DESC", @@ -330,51 +324,55 @@ fn pick_survivor(cluster: &[usize], rows: &[ConsolidateRow]) -> usize { /// Build merge clusters from `rows` with the given cosine threshold. /// Returns only clusters with ≥ 2 members (i.e. at least one merge needed). pub fn find_merge_clusters(rows: &[ConsolidateRow], threshold: f32) -> Vec { - let n = rows.len(); - if n < 2 { + if rows.len() < 2 || !threshold.is_finite() || !(0.0..=1.0).contains(&threshold) { return Vec::new(); } - - let mut uf = UnionFind::new(n); - - // Brute-force pairwise cosine — O(N²) fine for N < 10k. - // Future: replace with ANN index search for larger corpora. - for i in 0..n { - for j in (i + 1)..n { - // Only cluster within same model_id. - if rows[i].model_id != rows[j].model_id { - continue; - } - let sim = cosine(&rows[i].embedding, &rows[j].embedding); - if sim >= threshold { - uf.union(i, j); - } + // Similarity proposes related claims, not interchangeable text. Until claim + // identity is explicit, only identical text with identical applicability may + // be destructively consolidated. Do not normalize case/inner whitespace: code + // identifiers and quoted values may distinguish claims. + let mut buckets = std::collections::BTreeMap::new(); + for (i, row) in rows.iter().enumerate() { + if row.text.trim().is_empty() { + continue; } + buckets + .entry((&row.scope, &row.kind, &row.model_id, row.text.trim())) + .or_insert_with(Vec::new) + .push(i); } - - // Collect root → members mapping. - let mut root_to_members: HashMap> = HashMap::new(); - for i in 0..n { - let root = uf.find(i); - root_to_members.entry(root).or_default().push(i); - } - let mut clusters = Vec::new(); - for (_, members) in root_to_members { - if members.len() < 2 { - continue; // singleton — nothing to merge + // Bound pathological embedding-drift buckets. Unexamined rows remain intact. + let mut comparisons_left = 100_000usize; + for (_, mut pending) in buckets { + pending.sort_by(|&a, &b| rows[a].memory_id.cmp(&rows[b].memory_id)); + while pending.len() > 1 && comparisons_left > 0 { + let survivor_idx = pick_survivor(&pending, rows); + let mut remaining = Vec::new(); + let mut members = Vec::new(); + for i in pending { + if i == survivor_idx { + continue; + } + if comparisons_left == 0 { + remaining.push(i); + continue; + } + comparisons_left -= 1; + if cosine(&rows[survivor_idx].embedding, &rows[i].embedding) >= threshold { + members.push(rows[i].clone()); + } else { + remaining.push(i); + } + } + if !members.is_empty() { + clusters.push(MergeCluster { + survivor: rows[survivor_idx].clone(), + members, + }); + } + pending = remaining; } - let survivor_idx = pick_survivor(&members, rows); - let survivor = rows[survivor_idx].clone(); - let member_rows: Vec = members - .iter() - .filter(|&&i| i != survivor_idx) - .map(|&i| rows[i].clone()) - .collect(); - clusters.push(MergeCluster { - survivor, - members: member_rows, - }); } // Stable order for deterministic dry-run output. @@ -388,37 +386,56 @@ pub fn find_merge_clusters(rows: &[ConsolidateRow], threshold: f32) -> Vec KimetsuResult { - // Emit one enriched memory.superseded event per member. The projector - // arm handles: stamp, stat accumulation, citation reassignment, FTS/ANN - // removal. No direct UPDATE on the survivor here — everything flows - // through apply_events so live path == replay path. - for member in &cluster.members { - let event = kimetsu_core::event::Event::new( - run_id, - "memory.superseded", - serde_json::json!({ - "memory_id": member.memory_id, - "survivor_id": cluster.survivor.memory_id, - "use_count_delta": member.use_count, - "score_delta": member.usefulness_score as f64, - }), - ); - crate::projector::apply_events(conn, &[event])?; - } - + let events: Vec<_> = cluster + .members + .iter() + .map(|member| { + kimetsu_core::event::Event::new( + run_id, + "memory.superseded", + serde_json::json!({ + "memory_id": member.memory_id, + "survivor_id": cluster.survivor.memory_id, + "use_count_delta": 0, + "score_delta": 0.0, + }), + ) + }) + .collect(); + crate::projector::apply_events_checked(conn, &events, |c| { + let mut ids = std::collections::HashSet::new(); + for planned in std::iter::once(&cluster.survivor).chain(cluster.members.iter()) { + if !ids.insert(&planned.memory_id) { + return Err("merge plan repeats a memory ID".into()); + } + let current: (String, String, String, bool) = c.query_row( + "SELECT scope, kind, text, invalidated_at IS NULL AND superseded_by IS NULL + AND valid_from IS NULL AND valid_to IS NULL + FROM memories WHERE memory_id = ?1", + [&planned.memory_id], + |r| Ok((r.get(0)?, r.get(1)?, r.get(2)?, r.get(3)?)), + )?; + if !current.3 + || current.0 != planned.scope + || current.1 != planned.kind + || current.2 != planned.text + || current.0 != cluster.survivor.scope + || current.1 != cluster.survivor.kind + || current.2.trim() != cluster.survivor.text.trim() + { + return Err("merge plan is stale or combines distinct claims".into()); + } + } + Ok(()) + })?; Ok(cluster.members.len()) } @@ -897,7 +914,7 @@ mod tests { memory_id: id.to_string(), scope: "project".to_string(), kind: "fact".to_string(), - text: format!("text {id}"), + text: "Identical stored claim".to_string(), use_count: 1, usefulness_score: 1.0, last_useful_at: None, @@ -924,6 +941,43 @@ mod tests { ); } + #[test] + fn merge_preserves_scope_kind_and_distinct_claims() { + let mut base = make_row("a", vec![1.0, 0.0]); + base.text = "The development port is 4317.".into(); + for variant in 0..3 { + let mut other = base.clone(); + other.memory_id = "b".into(); + match variant { + 0 => other.scope = "user".into(), + 1 => other.kind = "constraint".into(), + _ => other.text = "The production port is 4317.".into(), + } + assert!( + find_merge_clusters(&[base.clone(), other], 0.92).is_empty(), + "similarity must not erase distinct applicability or a unique claim" + ); + } + } + + #[test] + fn merge_similarity_chain_cannot_bridge_distant_members() { + let mut rows = vec![ + make_row("a", vec![1.0, 0.0]), + make_row("b", vec![0.9396926, 0.3420201]), + make_row("c", vec![0.7660444, 0.6427876]), + ]; + for row in &mut rows { + row.text = "Identical claim with embedding drift".into(); + } + let clusters = find_merge_clusters(&rows, 0.92); + assert!(clusters.iter().all(|c| { + c.members + .iter() + .all(|m| cosine(&c.survivor.embedding, &m.embedding) >= 0.92) + })); + } + #[test] fn find_merge_clusters_orthogonal_no_clusters() { let rows = vec![ @@ -1011,7 +1065,7 @@ mod tests { // apply_merge (against in-memory SQLite) // ------------------------------------------------------------------ #[test] - fn apply_merge_supersedes_members_and_updates_survivor_stats() { + fn apply_merge_preserves_evidence_without_counting_copies_as_independent() { use kimetsu_core::ids::RunId; let conn = rusqlite::Connection::open_in_memory().expect("open"); @@ -1024,7 +1078,7 @@ mod tests { (memory_id, scope, kind, text, normalized_text, confidence, provenance_snapshot_json, created_at, use_count, usefulness_score) VALUES (?1,'project','fact',?2,?2,0.9,'{}','2026-01-01T00:00:00Z',?3,?4)", - params![id, format!("text {id}"), use_count, score], + params![id, "Identical stored claim", use_count, score], ) .expect("insert"); } @@ -1033,7 +1087,7 @@ mod tests { memory_id: "survivor".to_string(), scope: "project".to_string(), kind: "fact".to_string(), - text: "text survivor".to_string(), + text: "Identical stored claim".to_string(), use_count: 3, usefulness_score: 5.0, last_useful_at: None, @@ -1045,7 +1099,7 @@ mod tests { memory_id: "member".to_string(), scope: "project".to_string(), kind: "fact".to_string(), - text: "text member".to_string(), + text: "Identical stored claim".to_string(), use_count: 2, usefulness_score: 2.0, last_useful_at: None, @@ -1070,8 +1124,14 @@ mod tests { |r| Ok((r.get(0)?, r.get(1)?)), ) .expect("query survivor"); - assert_eq!(use_count, 5, "use_count = 3 + 2"); - assert!((score - 7.0).abs() < 0.01, "score = 5.0 + 2.0, got {score}"); + assert_eq!( + use_count, 3, + "copying a claim cannot create independent observations" + ); + assert!( + (score - 5.0).abs() < 0.01, + "keep survivor's evidence, got {score}" + ); // Member superseded. let superseded_by: Option = conn @@ -1084,6 +1144,40 @@ mod tests { assert_eq!(superseded_by.as_deref(), Some("survivor")); } + #[test] + fn stale_merge_plan_does_not_retire_any_member() { + let conn = rusqlite::Connection::open_in_memory().unwrap(); + crate::schema::initialize(&conn).unwrap(); + for id in ["a", "b", "c"] { + conn.execute("INSERT INTO memories (memory_id,scope,kind,text,normalized_text,confidence, + provenance_snapshot_json,created_at) VALUES (?1,'project','fact','Identical stored claim', + 'identical stored claim',0.5,'{}','2026-01-01T00:00:00Z')", params![id]).unwrap(); + } + let cluster = MergeCluster { + survivor: make_row("a", vec![1.0, 0.0]), + members: vec![make_row("b", vec![1.0, 0.0]), make_row("c", vec![1.0, 0.0])], + }; + conn.execute( + "UPDATE memories SET text='A corrected distinct claim' WHERE memory_id='c'", + [], + ) + .unwrap(); + assert!(apply_merge(&conn, &cluster, kimetsu_core::ids::RunId::new()).is_err()); + let retired: i64 = conn + .query_row( + "SELECT COUNT(*) FROM memories WHERE superseded_by IS NOT NULL", + [], + |r| r.get(0), + ) + .unwrap(); + assert_eq!(retired, 0, "the entire stale plan must roll back"); + conn.execute("UPDATE memories SET text='Identical stored claim', valid_from='2099-01-01T00:00:00Z' WHERE memory_id='c'", []).unwrap(); + assert!( + apply_merge(&conn, &cluster, kimetsu_core::ids::RunId::new()).is_err(), + "identical text with different applicability must not consolidate" + ); + } + #[test] fn citations_reassigned_on_merge() { use kimetsu_core::ids::RunId; @@ -1098,7 +1192,7 @@ mod tests { (memory_id, scope, kind, text, normalized_text, confidence, provenance_snapshot_json, created_at, use_count, usefulness_score) VALUES (?1,'project','fact',?2,?2,0.9,'{}','2026-01-01T00:00:00Z',1,1.0)", - params![id, format!("text {id}")], + params![id, "Identical stored claim"], ) .expect("insert memory"); } @@ -1116,7 +1210,7 @@ mod tests { memory_id: "survivor".to_string(), scope: "project".to_string(), kind: "fact".to_string(), - text: "text survivor".to_string(), + text: "Identical stored claim".to_string(), use_count: 1, usefulness_score: 1.0, last_useful_at: None, @@ -1128,7 +1222,7 @@ mod tests { memory_id: "member".to_string(), scope: "project".to_string(), kind: "fact".to_string(), - text: "text member".to_string(), + text: "Identical stored claim".to_string(), use_count: 1, usefulness_score: 1.0, last_useful_at: None, @@ -1342,7 +1436,10 @@ mod tests { ) .expect("run.started"); - for (mid, text) in [("survivor", "text survivor"), ("member", "text member")] { + for (mid, text) in [ + ("survivor", "Identical stored claim"), + ("member", "Identical stored claim"), + ] { projector::apply_events( &conn, &[kimetsu_core::event::Event::new( @@ -1399,7 +1496,7 @@ mod tests { memory_id: "survivor".to_string(), scope: "project".to_string(), kind: "fact".to_string(), - text: "text survivor".to_string(), + text: "Identical stored claim".to_string(), use_count: 3, usefulness_score: 5.0, last_useful_at: None, @@ -1411,7 +1508,7 @@ mod tests { memory_id: "member".to_string(), scope: "project".to_string(), kind: "fact".to_string(), - text: "text member".to_string(), + text: "Identical stored claim".to_string(), use_count: 2, usefulness_score: 2.0, last_useful_at: None, @@ -1422,16 +1519,8 @@ mod tests { }; apply_merge(&conn, &cluster, RunId::new()).expect("apply_merge"); - // Capture what the live path produced. After consolidation: - // survivor.use_count = 3 (initial) + 2 (delta) = 5 — BUT only - // the delta (2) is event-sourced; the initial 3 was set by - // direct SQL and is wiped by rebuild. So post-rebuild we expect - // exactly the deltas contributed by the superseded members. - // - // The invariant we check: whatever consolidation produces MUST - // match what rebuild produces. We capture from the DB rather than - // hard-coding so the test stays valid even if the initial SQL seeds - // change. + // SQL-seeded counters are intentionally not durable. Consolidation + // must neither pool those counters nor manufacture replay evidence. let (pre_uc, pre_score): (i64, f64) = conn .query_row( "SELECT use_count, usefulness_score FROM memories \ @@ -1465,20 +1554,8 @@ mod tests { ) .expect("query survivor after rebuild"); - // The member's delta (use_count=2, score=2.0) must survive rebuild. - // pre_uc includes the direct-SQL initial value (3) which rebuild - // cannot restore (not event-sourced); we only assert the delta: - // post_uc ≥ member.use_count (2) - // post_score ≥ member.usefulness_score (2.0) - // And more precisely, post_uc == member delta applied to 0 == 2. - assert_eq!( - post_uc, 2, - "post-rebuild: survivor use_count must contain member delta 2 (got {post_uc})" - ); - assert!( - (post_score - 2.0).abs() < 0.01, - "post-rebuild: survivor score must contain member delta 2.0 (got {post_score})" - ); + assert_eq!(post_uc, 0, "copies cannot manufacture replay evidence"); + assert_eq!(post_score, 0.0); let post_cited: String = conn .query_row( @@ -1492,16 +1569,8 @@ mod tests { "post-rebuild: citation must still point at survivor (got {post_cited:?})" ); - // Bonus: pre_uc/pre_score must also contain the delta (live path - // sanity-check so the test still catches regressions there). - assert!( - pre_uc >= 2, - "pre-rebuild: survivor use_count must include member delta ≥2 (got {pre_uc})" - ); - assert!( - pre_score >= 2.0, - "pre-rebuild: survivor score must include member delta ≥2.0 (got {pre_score})" - ); + assert_eq!(pre_uc, 3); + assert_eq!(pre_score, 5.0); } // ------------------------------------------------------------------ diff --git a/crates/kimetsu-brain/src/lifecycle.rs b/crates/kimetsu-brain/src/lifecycle.rs index 69157da..2156cb5 100644 --- a/crates/kimetsu-brain/src/lifecycle.rs +++ b/crates/kimetsu-brain/src/lifecycle.rs @@ -236,27 +236,29 @@ fn query_forget_candidates( cutoff_iso: &str, protect_use_count: u32, ) -> KimetsuResult> { - // A memory qualifies when: - // - active (not invalidated, not superseded) - // - use_count < protect_use_count - // - usefulness is low: score / max(use_count,1) <= floor - // - stale: it has not been RETRIEVED, proven useful, or created within the - // age window. The staleness reference is the most recent of - // `last_used_at` (bumped on every retrieval), `last_useful_at` (bumped on - // a successful citation), and `created_at`. Including `last_used_at` is - // the v2.6 fix for recall-preservation: a memory that is still being - // surfaced is in active use, so it must not be forgotten just because it - // has a low usefulness score and was never explicitly cited. + // Popularity protects non-negative evidence only. Negative evidence cannot + // refresh its own lifetime by being repeatedly exposed. Preferences and + // conventions require explicit correction, not automatic forgetting. + // Compare actual instants and take the latest timestamp, not first non-null. let mut stmt = conn.prepare( - "SELECT memory_id, scope, kind, text, use_count, usefulness_score, - COALESCE(last_used_at, last_useful_at, created_at) AS ref_ts - FROM memories - WHERE invalidated_at IS NULL - AND superseded_by IS NULL - AND use_count < ?1 + "WITH candidates AS ( + SELECT *, MAX( + COALESCE(julianday(created_at), julianday('now')), + COALESCE(julianday(last_useful_at), 0), + CASE WHEN usefulness_score < 0 THEN 0 + ELSE COALESCE(julianday(last_used_at), 0) END + ) AS ref_day + FROM memories + WHERE invalidated_at IS NULL AND superseded_by IS NULL + AND kind NOT IN ('preference', 'convention') + ) + SELECT memory_id, scope, kind, text, use_count, usefulness_score, + strftime('%Y-%m-%dT%H:%M:%fZ', ref_day) AS ref_ts + FROM candidates + WHERE (use_count < ?1 OR usefulness_score < 0) AND (CAST(usefulness_score AS REAL) / MAX(CAST(use_count AS REAL), 1.0)) <= ?2 - AND COALESCE(last_used_at, last_useful_at, created_at) <= ?3 - ORDER BY (CAST(usefulness_score AS REAL) / MAX(CAST(use_count AS REAL), 1.0)) ASC", + AND ref_day <= julianday(?3) + ORDER BY (CAST(usefulness_score AS REAL) / MAX(CAST(use_count AS REAL), 1.0)) ASC, memory_id", )?; let now = OffsetDateTime::now_utc(); @@ -679,8 +681,8 @@ mod tests { // Story 3.1: forget_brain dry-run identifies noise, not signal // ------------------------------------------------------------------------- - /// Helper to directly set usefulness_score + last_useful_at on a memory - /// row (bypasses the event system for test speed). + /// Seed an aged memory and its usefulness (the fixtures represent memories + /// already existing at their last-useful timestamp, not created today). fn set_memory_usefulness( conn: &rusqlite::Connection, memory_id: &str, @@ -689,7 +691,8 @@ mod tests { last_useful_at: Option<&str>, ) { conn.execute( - "UPDATE memories SET use_count=?2, usefulness_score=?3, last_useful_at=?4 WHERE memory_id=?1", + "UPDATE memories SET use_count=?2, usefulness_score=?3, last_useful_at=?4, + created_at=COALESCE(?4,created_at) WHERE memory_id=?1", rusqlite::params![memory_id, use_count, usefulness_score, last_useful_at], ) .expect("set_memory_usefulness"); @@ -759,6 +762,58 @@ mod tests { }); } + #[test] + fn forgetting_uses_meaningful_recency_and_not_harmful_popularity() { + let conn = rusqlite::Connection::open_in_memory().unwrap(); + crate::schema::initialize(&conn).unwrap(); + for (id, kind, created, used, useful, count, score) in [ + ( + "harmful-popular", + "fact", + "2020-01-01T00:00:00Z", + "2026-01-01T00:00:00Z", + "2020-01-01T00:00:00Z", + 20, + -10.0, + ), + ( + "recently-useful", + "fact", + "2020-01-01T00:00:00Z", + "2020-01-01T00:00:00Z", + "2026-01-01T00:00:00Z", + 1, + -0.5, + ), + ( + "recently-created", + "fact", + "2026-01-01T00:00:00Z", + "2020-01-01T00:00:00Z", + "2020-01-01T00:00:00Z", + 1, + -0.5, + ), + ( + "durable-preference", + "preference", + "2020-01-01T00:00:00Z", + "2020-01-01T00:00:00Z", + "2020-01-01T00:00:00Z", + 1, + -0.5, + ), + ] { + conn.execute("INSERT INTO memories (memory_id,scope,kind,text,normalized_text,confidence, + provenance_snapshot_json,created_at,last_used_at,last_useful_at,use_count,usefulness_score) + VALUES (?1,'project',?2,?1,?1,0.5,'{}',?3,?4,?5,?6,?7)", + params![id,kind,created,used,useful,count,score]).unwrap(); + } + let candidates = query_forget_candidates(&conn, -0.1, "2025-01-01T00:00:00Z", 10).unwrap(); + let ids: Vec<_> = candidates.iter().map(|c| c.memory_id.as_str()).collect(); + assert_eq!(ids, vec!["harmful-popular"]); + } + // v2.6 recall-preservation fix: a memory that was RETRIEVED recently // (`last_used_at` set, e.g. injected into a recent run) is in active use and // must NOT be forgotten just because it has low usefulness and was never diff --git a/crates/kimetsu-brain/src/project.rs b/crates/kimetsu-brain/src/project.rs index 21e72cc..7c63ff6 100644 --- a/crates/kimetsu-brain/src/project.rs +++ b/crates/kimetsu-brain/src/project.rs @@ -1271,16 +1271,10 @@ pub enum ProposeResult { Duplicate(String), // memory_id of the identical existing memory } -/// v0.7: capture a lesson, automatically deduplicating against the existing brain. -/// -/// Decision tree: -/// 1. Exact normalized-text match → `Duplicate` (no write). -/// 2. Cosine similarity ≥ 0.85 with an existing memory → `Merged` (append & re-embed). -/// 3. confidence ≥ 0.7 and no close match → `Added` (direct acceptance). -/// 4. confidence < 0.7 → `Proposed` (pending for human review). -/// -/// Step 2 only fires when the embedder is active (bge-small or similar). In lean builds -/// the cosine scan returns nothing and the function falls through to step 3/4. +/// Capture a lesson without combining semantically similar claims. Exact +/// duplicates reuse an ID; confidence >= 0.7 accepts a distinct claim, while +/// lower-confidence lessons remain proposals. Similarity candidates are queued +/// by the ordinary ingestion path for explicit review. pub fn propose_or_merge_memory( start: &Path, scope: MemoryScope, @@ -1296,9 +1290,8 @@ pub fn propose_or_merge_memory( let text = redaction.text.as_str(); // Step 1: exact normalized-text dedup (same as add_memory). - // W3.1: load config here so Step 2 can use open_embedder_for. - let (_, config, _) = { - let (paths, config, ro_conn) = load_project_readonly(start)?; + { + let (_, _, ro_conn) = load_project_readonly(start)?; let normalized = normalize_memory_text(text); let existing: Option = ro_conn .query_row( @@ -1314,48 +1307,9 @@ pub fn propose_or_merge_memory( if let Some(id) = existing { return Ok(ProposeResult::Duplicate(id)); } - (paths, config, ro_conn) - }; - - // Step 2: semantic dedup — look for a high-cosine existing memory. - // W3.1: route through open_embedder_for so `[embedder] enabled = false` - // skips cosine dedup (NoopEmbedder → find_potential_conflicts returns 0). - // v1.0: honor the [ingestion] detect_conflicts off-switch so bulk-seeding - // skips the cosine scan (find_potential_conflicts returns empty → no merge). - let embedder = embeddings::open_embedder_for(config.embedder.enabled); - { - let (_, _, ro_conn) = load_project_readonly(start)?; - let conflicts = if conflict::conflict_detection_enabled(config.ingestion.detect_conflicts) { - conflict::find_potential_conflicts(&ro_conn, &scope, text, embedder, 1, 0.85)? - } else { - Vec::new() - }; - if let Some(hit) = conflicts.into_iter().next() { - // Append the new lesson to the existing memory and re-embed it. - let (paths, _config, conn) = load_project(start)?; - let run_id = RunId::new(); - let _lock = ProjectLock::acquire(&paths, "memory merge", Some(run_id))?; - let merged_text = format!("{}\n\nAlso: {text}", hit.existing_text); - let new_normalized = normalize_memory_text(&merged_text); - conn.execute( - "UPDATE memories - SET text = ?1, normalized_text = ?2, use_count = use_count + 1 - WHERE memory_id = ?3", - rusqlite::params![merged_text, new_normalized, hit.existing_memory_id], - )?; - // Return value not needed — no conflict scan after a merge. - embeddings::embed_and_persist(&conn, &hit.existing_memory_id, &merged_text, embedder)?; - // v2.6: the merged text may carry entities the survivor did not - // have, so reproject and re-link. Skipping this would leave the - // absorbed lesson unreachable through the graph even though its - // words are now in the corpus. - let _ = crate::graph::project_entities(&conn, &hit.existing_memory_id, &merged_text); - link_memory_into_graph(&conn, &hit.existing_memory_id); - return Ok(ProposeResult::Merged(hit.existing_memory_id)); - } } - // Step 3/4: no close match found — accept or propose based on confidence. + // Related claims can disagree. Never append them or inflate use counts. if confidence >= 0.7 { let memory_id = add_memory(start, scope, kind, text)?; Ok(ProposeResult::Added(memory_id)) @@ -2343,6 +2297,59 @@ mod tests { /// (e.g. a developer's `$HOME` git repo) — which would otherwise /// make parallel tests share one brain.db + project.lock. Without /// this, tests pass only when `TMP` points outside any git repo. + #[cfg(feature = "embeddings")] + #[test] + #[ignore = "requires a cached local embedding model"] + fn similar_ingested_correction_preserves_both_claims_and_rebuild() { + with_user_brain_disabled(|| { + let root = test_root(); + init_project(&root, false).unwrap(); + let old = "For the Atlas integration service in the local staging environment, the HTTP listener uses port 4317 and binds to localhost."; + let new = "For the Atlas integration service in the local staging environment, the HTTP listener uses port 4318 and binds to localhost."; + let id = add_memory(&root, MemoryScope::Project, MemoryKind::Fact, old).unwrap(); + let (_, config, conn) = load_project(&root).unwrap(); + let embedder = embeddings::open_embedder_for(config.embedder.enabled); + assert!( + !embedder.is_noop(), + "this regression requires real semantic candidates" + ); + let hits = conflict::find_potential_conflicts( + &conn, + &MemoryScope::Project, + new, + embedder, + 1, + 0.85, + ) + .unwrap(); + assert!( + !hits.is_empty(), + "fixture must trigger the former semantic merge" + ); + assert!(matches!( + propose_or_merge_memory( + &root, + MemoryScope::Project, + MemoryKind::Fact, + new, + 0.9, + "port correction" + ) + .unwrap(), + ProposeResult::Added(_) + )); + let stored: String = conn + .query_row("SELECT text FROM memories WHERE memory_id=?1", [&id], |r| { + r.get(0) + }) + .unwrap(); + assert_eq!(stored, old); + projector::rebuild_in_place(&conn).unwrap(); + let count: i64 = conn.query_row("SELECT count(*) FROM memories WHERE text IN (?1,?2) AND invalidated_at IS NULL", [old,new], |r| r.get(0)).unwrap(); + assert_eq!(count, 2); + }); + } + fn test_root() -> std::path::PathBuf { let root = std::env::temp_dir().join(format!("kimetsu-test-{}", Ulid::new())); kimetsu_core::paths::git_init_boundary(&root); diff --git a/crates/kimetsu-brain/src/projector.rs b/crates/kimetsu-brain/src/projector.rs index 135ab34..1009f08 100644 --- a/crates/kimetsu-brain/src/projector.rs +++ b/crates/kimetsu-brain/src/projector.rs @@ -176,7 +176,20 @@ fn read_events_ordered(conn: &Connection) -> KimetsuResult> { } pub fn apply_events(conn: &Connection, events: &[Event]) -> KimetsuResult<()> { + apply_events_checked(conn, events, |_| Ok(())) +} + +/// Validate a read-derived plan under the same write lock as its events. +pub(crate) fn apply_events_checked( + conn: &Connection, + events: &[Event], + mut validate: F, +) -> KimetsuResult<()> +where + F: FnMut(&Connection) -> KimetsuResult<()>, +{ with_write_txn(conn, |c| { + validate(c)?; for event in events { apply_event(c, event)?; } diff --git a/crates/kimetsu-cli/src/distiller.rs b/crates/kimetsu-cli/src/distiller.rs index 86eca37..4e185a4 100644 --- a/crates/kimetsu-cli/src/distiller.rs +++ b/crates/kimetsu-cli/src/distiller.rs @@ -125,8 +125,6 @@ pub fn parse_lessons(text: &str) -> Vec { /// Configuration for the quality gate applied to distilled lessons. #[derive(Debug, Clone)] pub struct QualityGateConfig { - /// Cosine similarity ≥ this threshold → DROP (near-duplicate). Default 0.9. - pub novelty_threshold: f32, /// Minimum lesson length in chars after trim. Default 10. pub min_len: usize, /// Maximum lesson length in chars after trim. Default 500. @@ -136,7 +134,6 @@ pub struct QualityGateConfig { impl Default for QualityGateConfig { fn default() -> Self { Self { - novelty_threshold: 0.9, min_len: 10, max_len: 500, } @@ -165,14 +162,14 @@ static TRANSIENCE_MARKERS: &[&str] = &[ /// /// Checks (in order): /// 1. Length: < min_len or > max_len → DROP. -/// 2. Transience: contains a transience marker → DROP. -/// 3. Novelty: cosine to corpus ≥ novelty_threshold → DROP. -/// Skipped when no embedder is active (graceful degradation). +/// 2. Transience: markers require a future expiry. +/// 3. Exact duplicates within scope and kind are dropped. Similarity alone +/// cannot prove that a lesson duplicates an existing claim. pub fn quality_gate( lesson: &Lesson, conn: Option<&rusqlite::Connection>, scope: &MemoryScope, - embedder: &dyn kimetsu_brain::embeddings::Embedder, + _embedder: &dyn kimetsu_brain::embeddings::Embedder, config: &QualityGateConfig, ) -> QualityGateVerdict { let text = lesson.lesson.trim(); @@ -193,91 +190,51 @@ pub fn quality_gate( // 2. Transience check. let lower = text.to_ascii_lowercase(); for marker in TRANSIENCE_MARKERS { - if lower.contains(marker) { + let expires = lesson + .valid_to + .as_deref() + .and_then(|s| { + time::OffsetDateTime::parse(s, &time::format_description::well_known::Rfc3339).ok() + }) + .is_some_and(|end| end > time::OffsetDateTime::now_utc()); + if lower.contains(marker) && !expires { return QualityGateVerdict::Drop { reason: format!("transient marker found: {marker:?}"), }; } } - // 3. Novelty check (requires embedder + DB connection). - if !embedder.is_noop() { - if let Some(conn) = conn { - if let Ok(vec) = embedder.embed(text) { - if !vec.is_empty() { - // Check against corpus memories of the same scope. - let scope_str = scope.to_string(); - let max_cos = - max_cosine_to_scope(conn, &vec, &scope_str, config.novelty_threshold); - if max_cos >= config.novelty_threshold { - return QualityGateVerdict::Drop { - reason: format!( - "near-duplicate (cosine {max_cos:.3} ≥ threshold {:.3})", - config.novelty_threshold - ), - }; - } - } - } + // Similarity does not establish duplicate meaning. An exact stored claim is + // the only deterministic reason to drop a candidate here; corrections pass. + if let Some(conn) = conn { + let normalized = kimetsu_core::memory::normalize_memory_text(text); + let duplicate = conn.query_row( + "SELECT EXISTS(SELECT 1 FROM memories WHERE scope=?1 AND normalized_text=?2 + AND text=?3 AND kind=?4 AND invalidated_at IS NULL AND superseded_by IS NULL)", + rusqlite::params![ + scope.to_string(), + normalized, + text, + lesson_memory_kind(&lesson.kind).to_string() + ], + |r| r.get::<_, bool>(0), + ); + if matches!(duplicate, Ok(true)) { + return QualityGateVerdict::Drop { + reason: "exact duplicate".into(), + }; } } QualityGateVerdict::Pass } -/// Scan the corpus for the highest cosine similarity to `query_vec` within -/// `scope`. Returns 0.0 on any error or when no embeddings exist. -/// Stops early once a value ≥ `threshold` is found (short-circuit). -fn max_cosine_to_scope( - conn: &rusqlite::Connection, - query_vec: &[f32], - scope: &str, - threshold: f32, -) -> f32 { - let mut stmt = match conn.prepare( - "SELECT embedding FROM memories - WHERE scope = ?1 - AND invalidated_at IS NULL - AND superseded_by IS NULL - AND embedding IS NOT NULL - ORDER BY created_at DESC - LIMIT 500", - ) { - Ok(s) => s, - Err(_) => return 0.0, - }; - let rows = match stmt.query_map(rusqlite::params![scope], |row| row.get::<_, Vec>(0)) { - Ok(r) => r, - Err(_) => return 0.0, - }; - let mut max_cos: f32 = 0.0; - for row in rows.flatten() { - if let Ok(vec) = kimetsu_brain::embeddings::decode_embedding(&row, None) { - if vec.len() == query_vec.len() { - let cos = cosine_for_gate(query_vec, &vec); - if cos > max_cos { - max_cos = cos; - } - if max_cos >= threshold { - return max_cos; // short-circuit - } - } - } +fn lesson_memory_kind(kind: &str) -> MemoryKind { + match kind { + "anti_pattern" => MemoryKind::FailurePattern, + "convention" => MemoryKind::Convention, + _ => MemoryKind::Fact, } - max_cos -} - -fn cosine_for_gate(a: &[f32], b: &[f32]) -> f32 { - if a.len() != b.len() || a.is_empty() { - return 0.0; - } - let dot: f32 = a.iter().zip(b.iter()).map(|(x, y)| x * y).sum(); - let na: f32 = a.iter().map(|x| x * x).sum::().sqrt(); - let nb: f32 = b.iter().map(|x| x * x).sum::().sqrt(); - if na < f32::EPSILON || nb < f32::EPSILON { - return 0.0; - } - (dot / (na * nb)).clamp(-1.0, 1.0) } /// Ask the model to distill lessons from a transcript view. Returns empty @@ -466,7 +423,7 @@ pub fn distill_and_record( // Flagship 2 / Story 2.2: load config + open project DB for quality gate. // Best-effort: if config/DB can't be opened, quality gate runs in // degraded mode (no novelty check, only length + transience). - let (gate_config, gate_conn) = { + let (gate_config, gate_conn, transient_ttl_days) = { let paths_ok = kimetsu_core::paths::ProjectPaths::discover(start).ok(); let cfg_opt = paths_ok .as_ref() @@ -474,14 +431,12 @@ pub fn distill_and_record( let gate_config = cfg_opt .as_ref() .map_or_else(QualityGateConfig::default, |cfg| QualityGateConfig { - novelty_threshold: cfg.ingestion.quality_filter_novelty_threshold, min_len: cfg.ingestion.quality_filter_min_len, max_len: cfg.ingestion.quality_filter_max_len, }); let quality_enabled = cfg_opt .as_ref() .is_none_or(|cfg| cfg.ingestion.quality_filter_enabled); - let embedder_enabled = cfg_opt.as_ref().is_none_or(|cfg| cfg.embedder.enabled); let conn_opt: Option = if quality_enabled { paths_ok .as_ref() @@ -489,22 +444,48 @@ pub fn distill_and_record( } else { None }; - let embedder = kimetsu_brain::embeddings::open_embedder_for(embedder_enabled); + let transient_ttl_days = cfg_opt + .as_ref() + .map_or(7, |cfg| cfg.ingestion.transient_ttl_days); ( if quality_enabled { - Some((gate_config, embedder)) + Some(gate_config) } else { None }, conn_opt, + transient_ttl_days, ) }; let mut recorded = 0; - for lesson in distill_lessons(view, provider) { + for mut lesson in distill_lessons(view, provider) { + // Temporary evidence is retained with an explicit lifetime. Existing + // authored bounds win; zero disables automatic TTL assignment. + if lesson.valid_to.is_none() + && transient_ttl_days > 0 + && TRANSIENCE_MARKERS + .iter() + .any(|m| lesson.lesson.to_ascii_lowercase().contains(m)) + { + let now = time::OffsetDateTime::now_utc(); + lesson.valid_from.get_or_insert_with(|| { + now.format(&time::format_description::well_known::Rfc3339) + .unwrap() + }); + lesson.valid_to = (now + time::Duration::days(i64::from(transient_ttl_days.min(365)))) + .format(&time::format_description::well_known::Rfc3339) + .ok(); + } // Flagship 2 / Story 2.2: apply quality gate. - if let Some((ref qcfg, embedder)) = gate_config { - let verdict = quality_gate(&lesson, gate_conn.as_ref(), &scope, embedder, qcfg); + if let Some(ref qcfg) = gate_config { + let verdict = quality_gate( + &lesson, + gate_conn.as_ref(), + &scope, + &kimetsu_brain::embeddings::NoopEmbedder, + qcfg, + ); if let QualityGateVerdict::Drop { reason } = verdict { eprintln!("kimetsu-distiller: quality gate dropped lesson: {reason}"); continue; @@ -512,11 +493,7 @@ pub fn distill_and_record( } // Mirror kimetsu_brain_record's MCP kind mapping; semantic_operator + default store as Fact. - let kind = match lesson.kind.as_str() { - "anti_pattern" => MemoryKind::FailurePattern, - "convention" => MemoryKind::Convention, - _ => MemoryKind::Fact, - }; + let kind = lesson_memory_kind(&lesson.kind); let text = lesson.lesson.trim(); // Capture temporal fields before moving `lesson`. let valid_from = lesson.valid_from.clone(); @@ -537,7 +514,8 @@ pub fn distill_and_record( .ok() .and_then(|r| match r { project::ProposeResult::Added(id) | project::ProposeResult::Merged(id) => Some(id), - project::ProposeResult::Duplicate(id) => Some(id), + // A repeated observation must not rewrite an existing claim's validity. + project::ProposeResult::Duplicate(_) => None, project::ProposeResult::Proposed(_) => None, }), }; @@ -1310,7 +1288,8 @@ mod tests { kimetsu_brain::user_brain::with_user_brain_disabled(|| { kimetsu_brain::project::init_project(&root, true).expect("init brain"); let mut provider = MockProvider::new([text_response( - "[{\"lesson\":\"Set USERPROFILE for global installs\",\"tags\":[\"cargo\",\"windows\"],\"confidence\":0.9}]", + r#"[{"lesson":"Set USERPROFILE for global installs","tags":["cargo","windows"],"confidence":0.9}, + {"lesson":"Temporarily disable the cache for integration tests on version 3.1","confidence":0.9}]"#, )]); let n = distill_and_record( &root, @@ -1318,12 +1297,30 @@ mod tests { &mut provider, MemoryScope::Project, ); - assert_eq!(n, 1); + assert_eq!(n, 2); let memories = kimetsu_brain::project::list_memories(&root).expect("list"); assert!( memories.iter().any(|m| m.text.contains("USERPROFILE")), "distilled lesson was recorded" ); + let (_, _, conn) = project::load_project(&root).unwrap(); + let expiry: String = conn + .query_row( + "SELECT valid_to FROM memories WHERE text LIKE 'Temporarily%'", + [], + |r| r.get(0), + ) + .unwrap(); + let end = time::OffsetDateTime::parse( + &expiry, + &time::format_description::well_known::Rfc3339, + ) + .unwrap(); + let remaining = (end - time::OffsetDateTime::now_utc()).whole_days(); + assert!( + (0..=30).contains(&remaining), + "temporary lesson needs a bounded lifetime" + ); }); std::fs::remove_dir_all(root).ok(); @@ -1685,4 +1682,44 @@ mod tests { "novel lesson must pass the novelty gate" ); } + + #[test] + fn quality_gate_preserves_similar_corrections_and_bounded_workarounds() { + let conn = rusqlite::Connection::open_in_memory().unwrap(); + kimetsu_brain::projector::ensure_schema(&conn).unwrap(); + seed_embedded_memory(&conn, "old", "project", "Service alpha calls service beta."); + let stub = kimetsu_brain::embeddings::StubEmbedder::new(); + let corrected = lesson_with("Service beta calls service alpha."); + // Force identical vectors for opposite-direction claims: high cosine + // must never be the sole reason to discard a correction. + let vector = kimetsu_brain::embeddings::Embedder::embed(&stub, &corrected.lesson).unwrap(); + conn.execute( + "UPDATE memories SET embedding=?1 WHERE memory_id='old'", + rusqlite::params![kimetsu_brain::embeddings::encode_embedding(&vector)], + ) + .unwrap(); + assert_eq!( + quality_gate( + &corrected, + Some(&conn), + &MemoryScope::Project, + &stub, + &QualityGateConfig::default() + ), + QualityGateVerdict::Pass + ); + let mut temporary = + lesson_with("Temporarily disable the cache for this version's integration tests."); + temporary.valid_to = Some("2099-01-01T00:00:00Z".into()); + assert_eq!( + quality_gate( + &temporary, + None, + &MemoryScope::Project, + &stub, + &QualityGateConfig::default() + ), + QualityGateVerdict::Pass + ); + } } diff --git a/crates/kimetsu-core/src/config.rs b/crates/kimetsu-core/src/config.rs index c8c3a7e..f7fc37b 100644 --- a/crates/kimetsu-core/src/config.rs +++ b/crates/kimetsu-core/src/config.rs @@ -1179,20 +1179,10 @@ pub struct IngestionSection { /// Precedence: `KIMETSU_DETECT_CONFLICTS` env > this field > default. #[serde(default = "default_true")] pub detect_conflicts: bool, - /// v2.5 Pass B (Story 1.3): enable automatic contradiction resolution. - /// - /// When true (default), conflicting memory pairs are scored by - /// `confidence × recency`. Clear winners (score gap ≥ 0.15) have the - /// loser's `valid_to` stamped to now via `mark_memory_temporal` - /// (event-sourced, rebuild-safe). Near-ties are queued in - /// `memory_conflicts` for operator review, same as the v0.5.2 behavior. - /// - /// Set to false (or set env `KIMETSU_RESOLVE_CONFLICTS=0`) to revert to - /// detect-only mode: all conflicts are queued for the operator. - /// - /// Precedence: `KIMETSU_RESOLVE_CONFLICTS` env > this field > default. - /// Resolution only runs when `detect_conflicts` is also enabled. - #[serde(default = "default_true")] + /// Legacy resolution switch, default false. Both settings now queue + /// similarity candidates for explicit review when detection is enabled. + /// Neither similarity nor confidence/recency automatically retires a claim. + #[serde(default)] pub resolve_conflicts: bool, /// Flagship 2 / Story 2.1: seed a non-zero initial usefulness_score for @@ -1208,15 +1198,14 @@ pub struct IngestionSection { pub initial_importance_scoring: bool, /// Flagship 2 / Story 2.2: quality-control filter in the distiller. - /// Drop lessons that are near-duplicates (cosine ≥ threshold), too long, - /// too short, or contain transience markers. Default true. + /// Drop exact duplicates, overlong/short lessons and unbounded temporary + /// lessons. Similar corrections and temporally bounded workarounds pass. /// `#[serde(default = "default_true")]` keeps older configs loading cleanly. #[serde(default = "default_true")] pub quality_filter_enabled: bool, - /// Flagship 2 / Story 2.2: novelty threshold — cosine ≥ this value → DROP. - /// Default 0.9. `#[serde(default)]` keeps older configs loading cleanly - /// (they get the default via the `Default` impl). + /// Legacy field retained for config compatibility; ignored. Cosine + /// similarity cannot safely prove a lesson duplicates an existing claim. #[serde(default = "default_quality_filter_novelty_threshold")] pub quality_filter_novelty_threshold: f32, @@ -1229,6 +1218,14 @@ pub struct IngestionSection { /// Lessons longer than this are dropped. Default 500. #[serde(default = "default_quality_filter_max_len")] pub quality_filter_max_len: usize, + /// Lifetime assigned to temporary lessons without an explicit expiry. + /// Default seven days; zero disables assignment, maximum applied is 365 days. + #[serde(default = "default_transient_ttl_days")] + pub transient_ttl_days: u32, +} + +fn default_transient_ttl_days() -> u32 { + 7 } fn default_quality_filter_novelty_threshold() -> f32 { @@ -1248,12 +1245,13 @@ impl Default for IngestionSection { extra_skip_dirs: Vec::new(), max_total_files: 50_000, detect_conflicts: true, - resolve_conflicts: true, + resolve_conflicts: false, initial_importance_scoring: true, quality_filter_enabled: true, quality_filter_novelty_threshold: default_quality_filter_novelty_threshold(), quality_filter_min_len: default_quality_filter_min_len(), quality_filter_max_len: default_quality_filter_max_len(), + transient_ttl_days: default_transient_ttl_days(), } } } @@ -1355,7 +1353,7 @@ pub struct LifecycleSection { pub forget_usefulness_floor: f32, /// Evergreen protection threshold. Memories with - /// `use_count >= forget_protect_use_count` are NEVER archived regardless + /// Non-negative memories with `use_count >= forget_protect_use_count` are protected regardless /// of their usefulness ratio. Default 10. #[serde(default = "default_forget_protect_use_count")] pub forget_protect_use_count: u32, From 3316112ecdfeb18ca2765024aa350f64547b03a1 Mon Sep 17 00:00:00 2001 From: RodCor Date: Fri, 4 Sep 2026 21:53:48 -0300 Subject: [PATCH 05/34] fix(brain): fail closed on unbound exposures and preserve graph policy --- crates/kimetsu-brain/src/backend.rs | 147 +++++++++++++++++++------- crates/kimetsu-brain/src/context.rs | 20 +--- crates/kimetsu-brain/src/projector.rs | 128 ++++++++++++++++++++-- 3 files changed, 226 insertions(+), 69 deletions(-) diff --git a/crates/kimetsu-brain/src/backend.rs b/crates/kimetsu-brain/src/backend.rs index efe531f..7a868e0 100644 --- a/crates/kimetsu-brain/src/backend.rs +++ b/crates/kimetsu-brain/src/backend.rs @@ -73,7 +73,7 @@ use rusqlite::Connection; use kimetsu_core::KimetsuResult; -use crate::context::{Candidate, ContextCapsule, ProvenanceRef, QueryEmbedding}; +use crate::context::{Candidate, QueryEmbedding}; // ─── Trait ─────────────────────────────────────────────────────────────────── @@ -258,8 +258,13 @@ impl RetrievalBackend for GraphLiteBackend { // 4. Fetch the graph-reachable memories as candidates, marking their // provenance so the broker/caller can distinguish them from flat hits. - let graph_candidates = - fetch_graph_candidates(conn, &new_ids, &mut seen_ids, max_flat_relevance)?; + let graph_candidates = fetch_graph_candidates( + conn, + &new_ids, + &mut seen_ids, + max_flat_relevance, + half_life_days, + )?; // 5. Concatenate: flat hits first (they have real relevance signals), // graph-reachable hits appended (raw_relevance = 0.0 → ranked last @@ -381,6 +386,7 @@ fn fetch_graph_candidates( new_ids: &[(String, usize)], seen_ids: &mut HashSet, seed_relevance: f32, + half_life_days: f32, ) -> KimetsuResult> { if new_ids.is_empty() { return Ok(Vec::new()); @@ -392,7 +398,8 @@ fn fetch_graph_candidates( .join(", "); let sql = format!( - "SELECT memory_id, scope, kind, text, confidence, created_at + "SELECT memory_id, scope, kind, text, confidence, created_at, + use_count, usefulness_score, last_useful_at, provenance_snapshot_json FROM memories WHERE invalidated_at IS NULL AND superseded_by IS NULL @@ -415,12 +422,27 @@ fn fetch_graph_candidates( row.get::<_, String>(3)?, row.get::<_, f32>(4)?, row.get::<_, String>(5)?, + row.get::<_, i64>(6)?, + row.get::<_, f64>(7)?, + row.get::<_, Option>(8)?, + row.get::<_, Option>(9)?, )) })?; let mut candidates = Vec::new(); for row in rows { - let (memory_id, scope, kind, text, confidence, created_at) = row?; + let ( + memory_id, + scope, + kind, + text, + confidence, + created_at, + use_count, + usefulness_score, + last_useful_at, + provenance, + ) = row?; // Skip if already in the seen set (shouldn't happen given the CTE's // NOT IN guard, but be defensive). @@ -438,39 +460,32 @@ fn fetch_graph_candidates( .unwrap_or(1); let raw_relevance = seed_relevance * HOP_DECAY.powi(hop as i32); - let freshness = crate::context::freshness_pub(&created_at); - let scope_weight = crate::context::scope_weight_pub(&scope); - let token_estimate = crate::context::estimate_tokens(&text) + 8; - + // Keep the graph's hop-derived query signal, but use exactly the same + // usefulness decay and provenance policy as FTS/ANN hydration. let claim_revision = Some(crate::projector::claim_revision_at(conn, &memory_id, None)?); - candidates.push(Candidate { - raw_relevance, - embedding: None, - cosine: None, - created_at: Some(created_at), - capsule: ContextCapsule { - id: kimetsu_core::ids::new_id().to_string(), - kind: "memory".to_string(), - summary: format!("{scope}:{kind} - {text}"), - token_estimate, - expansion_handle: format!("memory:{memory_id}"), - provenance: vec![ProvenanceRef { - source: "graph".to_string(), - id: memory_id, - excerpt: Some(crate::context::excerpt_pub(&text)), - }], - confidence, - freshness, - relevance: 0.0, - scope_weight, - score: 0.0, - superseded_hint: false, - rerank_policy_tier: 0, - claim_revision, - rerank_usefulness: None, - rerank_trust: None, - }, - }); + if let Some(mut candidate) = crate::context::memory_row_to_candidate( + &[], + memory_id, + scope, + kind, + text, + confidence, + created_at, + use_count, + usefulness_score, + last_useful_at, + provenance, + half_life_days, + Some(raw_relevance), + None, + None, + ) { + candidate.capsule.claim_revision = claim_revision; + for source in &mut candidate.capsule.provenance { + source.source = "graph".into(); + } + candidates.push(candidate); + } } Ok(candidates) } @@ -744,8 +759,13 @@ impl RetrievalBackend for PetgraphBackend { } // 4. Fetch graph-reached candidates from SQLite (active memories only). - let graph_candidates = - fetch_graph_candidates(conn, &new_ids, &mut seen_ids, max_flat_relevance)?; + let graph_candidates = fetch_graph_candidates( + conn, + &new_ids, + &mut seen_ids, + max_flat_relevance, + half_life_days, + )?; // 5. Flat first (real relevance signals), graph-reached appended. let mut combined = flat; @@ -892,7 +912,7 @@ mod tests { ("future".into(), 1), ("expired".into(), 1), ]; - let out = fetch_graph_candidates(&conn, &ids, &mut HashSet::new(), 1.0).unwrap(); + let out = fetch_graph_candidates(&conn, &ids, &mut HashSet::new(), 1.0, 30.0).unwrap(); assert_eq!(out.len(), 1); assert_eq!(out[0].capsule.expansion_handle, "memory:live"); assert_eq!( @@ -901,6 +921,53 @@ mod tests { ); } + #[test] + fn graph_rerank_retains_trust_and_decayed_usefulness() { + struct Scores; + impl crate::embeddings::Reranker for Scores { + fn rerank( + &self, + _query: &str, + docs: &[&str], + ) -> Result, crate::embeddings::EmbedderError> { + Ok(docs + .iter() + .map(|d| if d.contains("imported") { 0.9 } else { 0.8 }) + .collect()) + } + fn model_id(&self) -> &str { + "graph-policy-test" + } + } + let conn = make_conn(); + insert_memory(&conn, "pack", "fact", "imported claim"); + insert_memory(&conn, "local", "fact", "local claim"); + conn.execute("UPDATE memories SET provenance_snapshot_json='{\"source\":\"pack\"}' WHERE memory_id='pack'",[]).unwrap(); + let ids = vec![("pack".into(), 1), ("local".into(), 1)]; + let out = fetch_graph_candidates(&conn, &ids, &mut HashSet::new(), 1.0, 30.0).unwrap(); + let ranked = crate::context::rerank_capsules( + "q", + out.into_iter().map(|c| c.capsule).collect(), + &Scores, + 0.0, + 0, + ); + assert_eq!( + ranked[0].expansion_handle, "memory:local", + "graph imports must preserve provenance discount" + ); + let past = (time::OffsetDateTime::now_utc() - time::Duration::days(30)) + .format(&time::format_description::well_known::Rfc3339) + .unwrap(); + conn.execute("UPDATE memories SET use_count=5,usefulness_score=-5,last_useful_at=?1 WHERE memory_id='local'",[past]).unwrap(); + let out = fetch_graph_candidates(&conn, &ids, &mut HashSet::new(), 1.0, 30.0).unwrap(); + let local = out + .iter() + .find(|c| c.capsule.expansion_handle == "memory:local") + .unwrap(); + assert!((local.capsule.rerank_usefulness.unwrap_or(1.0) - 0.75).abs() < 0.0001); + } + /// Helper: open an in-memory brain with the current schema. fn make_conn() -> Connection { let conn = Connection::open_in_memory().expect("open_in_memory"); diff --git a/crates/kimetsu-brain/src/context.rs b/crates/kimetsu-brain/src/context.rs index babad9e..f8ec55e 100644 --- a/crates/kimetsu-brain/src/context.rs +++ b/crates/kimetsu-brain/src/context.rs @@ -1642,7 +1642,7 @@ fn compute_cosine_and_vec( } #[allow(clippy::too_many_arguments)] -fn memory_row_to_candidate( +pub(crate) fn memory_row_to_candidate( query_tokens: &[String], memory_id: String, scope: String, @@ -2341,12 +2341,6 @@ fn weights_for_stage(weights: &BrokerWeights, stage: &str) -> StageWeights { }) } -/// S5.2: `pub(crate)` so `backend.rs` (GraphLiteBackend) can build graph- -/// reached candidates without duplicating the scope weight logic. -pub(crate) fn scope_weight_pub(scope: &str) -> f32 { - scope_weight(scope) -} - fn scope_weight(scope: &str) -> f32 { match scope.parse::() { Ok(MemoryScope::Run) => 1.0, @@ -2357,12 +2351,6 @@ fn scope_weight(scope: &str) -> f32 { } } -/// S5.2: `pub(crate)` so `backend.rs` (GraphLiteBackend) can build graph- -/// reached candidates without duplicating the freshness logic. -pub(crate) fn freshness_pub(created_at: &str) -> f32 { - freshness(created_at) -} - fn freshness(created_at: &str) -> f32 { let Ok(created_at) = OffsetDateTime::parse(created_at, &time::format_description::well_known::Rfc3339) @@ -2999,12 +2987,6 @@ fn cap_sentences(text: &str, n: usize) -> &str { text.trim_end() } -/// S5.2: `pub(crate)` so `backend.rs` (GraphLiteBackend) can build graph- -/// reached candidates without duplicating the excerpt logic. -pub(crate) fn excerpt_pub(text: &str) -> String { - excerpt(text) -} - fn excerpt(text: &str) -> String { let value = one_line(text); value.chars().take(256).collect() diff --git a/crates/kimetsu-brain/src/projector.rs b/crates/kimetsu-brain/src/projector.rs index 1009f08..d214f81 100644 --- a/crates/kimetsu-brain/src/projector.rs +++ b/crates/kimetsu-brain/src/projector.rs @@ -348,6 +348,11 @@ fn apply_memory_cited(conn: &Connection, event: &Event) -> KimetsuResult<()> { let exposed = run_claim_revision(conn, &event.run_id.to_string(), memory_id)?; // A citation without a revision or exposure is ambiguous after a text // correction. Keep its durable event, but do not credit the new claim. + let exposed = match exposed { + ClaimExposure::Unbound => return Ok(()), + ClaimExposure::Absent => None, + ClaimExposure::Bound(revision) => Some(revision), + }; let evidence_revision = explicit.map(str::to_owned).or(exposed); if evidence_revision .as_ref() @@ -651,7 +656,13 @@ fn apply_memory_usefulness_for_run(conn: &Connection, event: &Event) -> KimetsuR }; for memory_id in &retrieved { - if let Some(exposed_revision) = run_claim_revision(conn, &run_id, memory_id)? { + let exposure = run_claim_revision(conn, &run_id, memory_id)?; + if matches!(exposure, ClaimExposure::Unbound) { + // An explicit delivery map is authoritative: missing/ambiguous IDs + // have no safely attributable claim, including the baseline claim. + continue; + } + if let ClaimExposure::Bound(exposed_revision) = exposure { if exposed_revision != claim_revision_at(conn, memory_id, None)? { // The run saw the old proposition. Its delayed outcome belongs // to that retained revision, even if correction removed the @@ -2532,6 +2543,84 @@ mod correction_regressions { schema::initialize(c).unwrap(); apply_events(c, &[event("memory.accepted", serde_json::json!({"memory_id":"m", "scope":"project", "kind":"fact", "text":"original quokka"}), "2026-01-01T00:00:00Z")]).unwrap(); } + #[test] + fn explicit_unbound_exposure_never_credits_a_claim() { + for corrected in [false, true] { + for bindings in [ + serde_json::json!({}), + serde_json::json!({"other":"baseline:other"}), + ] { + let c = Connection::open_in_memory().unwrap(); + seed(&c); + if corrected { + apply_events( + &c, + &[event( + "memory.corrected", + serde_json::json!({"memory_id":"m","text":"new claim"}), + "2026-01-02T00:00:00Z", + )], + ) + .unwrap(); + } + let run = RunId::new(); + let mut events = vec![ + event( + "run.started", + serde_json::json!({"project_id":"p","task":"t"}), + "2026-01-03T00:00:00Z", + ), + event( + "context.injected", + serde_json::json!({"memory_ids":["m"],"memory_revisions":bindings}), + "2026-01-04T00:00:00Z", + ), + event( + "memory.cited", + serde_json::json!({"memory_id":"m","turn":1}), + "2026-01-05T00:00:00Z", + ), + event( + "run.finished", + serde_json::json!({"total_cost_usd":0}), + "2026-01-06T00:00:00Z", + ), + ]; + for e in &mut events { + e.run_id = run; + } + apply_events(&c, &events).unwrap(); + for _ in 0..2 { + let current: (i64, f64) = c + .query_row( + "SELECT use_count,usefulness_score FROM memories WHERE memory_id='m'", + [], + |r| Ok((r.get(0)?, r.get(1)?)), + ) + .unwrap(); + assert_eq!( + current, + (0, 0.0), + "explicit unbound exposure must not fall back to current claim" + ); + let citations: i64 = c + .query_row("SELECT count(*) FROM memory_citations", [], |r| r.get(0)) + .unwrap(); + assert_eq!(citations, 0); + let revision_uses: i64 = c + .query_row( + "SELECT COALESCE(SUM(use_count),0) FROM memory_revisions", + [], + |r| r.get(0), + ) + .unwrap(); + assert_eq!(revision_uses, 0); + rebuild_in_place(&c).unwrap(); + } + } + } + } + #[test] fn delayed_run_evidence_stays_on_the_retiring_claim() { let c = Connection::open_in_memory().unwrap(); @@ -2767,6 +2856,13 @@ pub(crate) fn claim_revision_at( Ok(revision.unwrap_or_else(|| format!("baseline:{memory_id}"))) } +/// Distinguish a legacy absent exposure from an explicitly unbound delivery. +enum ClaimExposure { + Absent, + Unbound, + Bound(String), +} + /// Legacy injections identify IDs only. Attribute an in-flight run to its /// earliest exposure rather than silently transferring old evidence on edit. /// For legacy unbound events, equal-time corrections are conservatively treated @@ -2775,7 +2871,7 @@ fn run_claim_revision( conn: &Connection, run_id: &str, memory_id: &str, -) -> KimetsuResult> { +) -> KimetsuResult { let exposure = conn .query_row( "SELECT ts,payload_json FROM events e WHERE run_id=?1 AND kind='context.injected' @@ -2786,17 +2882,29 @@ fn run_claim_revision( ) .optional()?; match exposure { - None => Ok(None), + None => Ok(ClaimExposure::Absent), Some((at, payload)) => { let payload: serde_json::Value = serde_json::from_str(&payload)?; - if let Some(revision) = payload - .get("memory_revisions") - .and_then(|v| v.get(memory_id)) - .and_then(|v| v.as_str()) - { - Ok(Some(revision.to_string())) + if let Some(bindings) = payload.get("memory_revisions") { + // Presence (even malformed/empty/partial) means the producer + // supplied an authoritative hydration map. Never invent a later + // claim identity for an omitted or ambiguous entry. + Ok( + match bindings + .get(memory_id) + .and_then(|v| v.as_str()) + .filter(|r| !r.is_empty()) + { + Some(revision) => ClaimExposure::Bound(revision.to_string()), + None => ClaimExposure::Unbound, + }, + ) } else { - Ok(Some(claim_revision_at(conn, memory_id, Some(&at))?)) + Ok(ClaimExposure::Bound(claim_revision_at( + conn, + memory_id, + Some(&at), + )?)) } } } From d45b7f79e675ade5ace8c56e1c681f2900bd8299 Mon Sep 17 00:00:00 2001 From: RodCor Date: Fri, 4 Sep 2026 22:00:19 -0300 Subject: [PATCH 06/34] Persist temporary applicability through ingestion and proposal review --- crates/kimetsu-brain/src/migrate.rs | 5 + crates/kimetsu-brain/src/project.rs | 72 ++++++++-- crates/kimetsu-brain/src/projector.rs | 50 ++++++- crates/kimetsu-brain/src/schema.rs | 11 ++ crates/kimetsu-brain/src/user_brain.rs | 68 ++++------ crates/kimetsu-cli/src/distiller.rs | 174 +++++++++++++++++-------- crates/kimetsu-core/src/lib.rs | 2 +- 7 files changed, 271 insertions(+), 111 deletions(-) diff --git a/crates/kimetsu-brain/src/migrate.rs b/crates/kimetsu-brain/src/migrate.rs index eea8d58..296c333 100644 --- a/crates/kimetsu-brain/src/migrate.rs +++ b/crates/kimetsu-brain/src/migrate.rs @@ -109,6 +109,11 @@ fn migrations() -> &'static [Migration] { description: "durable correction revisions and corpus freshness", up: crate::schema::migrate_v11_to_v12, }, + Migration { + version: 13, + description: "preserve proposal temporal applicability", + up: crate::schema::migrate_v12_to_v13, + }, ] } diff --git a/crates/kimetsu-brain/src/project.rs b/crates/kimetsu-brain/src/project.rs index 7c63ff6..62440f1 100644 --- a/crates/kimetsu-brain/src/project.rs +++ b/crates/kimetsu-brain/src/project.rs @@ -781,6 +781,19 @@ pub fn add_memory( scope: MemoryScope, kind: MemoryKind, text: &str, +) -> KimetsuResult { + add_memory_with_validity(start, scope, kind, text, None, None) +} + +/// Add with temporal bounds in the same durable write. Duplicate claims retain +/// their original bounds; observing them again does not renew their lifetime. +pub fn add_memory_with_validity( + start: &Path, + scope: MemoryScope, + kind: MemoryKind, + text: &str, + valid_from: Option<&str>, + valid_to: Option<&str>, ) -> KimetsuResult { // v0.4.5: redact secrets at the ingest boundary. The redaction // pipeline catches Anthropic/OpenAI/GitHub/AWS/Slack/Google @@ -824,7 +837,9 @@ pub fn add_memory( .map(|cfg| cfg.kimetsu.use_user_brain) .unwrap_or(true); if let Some(user_conn) = user_brain::open_user_brain_for_config(use_user_brain)? { - return user_brain::add_user_memory(&user_conn, kind, text, 1.0); + return user_brain::add_user_memory_with_validity( + &user_conn, kind, text, 1.0, valid_from, valid_to, + ); } // User brain disabled/unreachable → fall through to the project DB // (which DOES require a valid project — same pre-P0 behavior for @@ -837,7 +852,7 @@ pub fn add_memory( let embedder = embeddings::open_embedder_for(config.embedder.enabled); add_memory_inner( - &conn, &paths, &config, scope, kind, text, None, None, embedder, + &conn, &paths, &config, scope, kind, text, valid_from, valid_to, embedder, ) } @@ -931,6 +946,8 @@ fn add_memory_inner( "normalized_text": normalized, "confidence": DIRECT_ADD_CONFIDENCE, "initial_usefulness": initial_kind_weight, + "valid_from": valid_from, + "valid_to": valid_to, "provenance_snapshot": build_provenance(run_id, text), }), ); @@ -948,12 +965,6 @@ fn add_memory_inner( projector::apply_events(conn, &[started, accepted, finished])?; - // Flagship 1 / temporal: stamp valid_from / valid_to when requested. - // This is event-sourced (rebuild-safe) via mark_memory_temporal. - if valid_from.is_some() || valid_to.is_some() { - projector::mark_memory_temporal(conn, &memory_id, valid_from, valid_to)?; - } - // v0.4.2: post-projection embedding write. v0.4.3 wired the // default embedder behind a feature flag — see // `embeddings::open_default_embedder`. Default build: NoopEmbedder @@ -1224,6 +1235,20 @@ pub fn propose_memory( text: &str, confidence: f32, rationale: &str, +) -> KimetsuResult { + propose_memory_with_validity(start, scope, kind, text, confidence, rationale, None, None) +} + +#[allow(clippy::too_many_arguments)] +pub fn propose_memory_with_validity( + start: &Path, + scope: MemoryScope, + kind: MemoryKind, + text: &str, + confidence: f32, + rationale: &str, + valid_from: Option<&str>, + valid_to: Option<&str>, ) -> KimetsuResult { let redaction = redact::redact_secrets(text); if redaction.was_redacted() { @@ -1252,6 +1277,8 @@ pub fn propose_memory( "text": text, "rationale": rationale, "proposed_confidence": confidence.clamp(0.0, 1.0), + "valid_from": valid_from, + "valid_to": valid_to, "source_event_ids": [], }), ); @@ -1282,6 +1309,22 @@ pub fn propose_or_merge_memory( text: &str, confidence: f32, rationale: &str, +) -> KimetsuResult { + propose_or_merge_memory_with_validity( + start, scope, kind, text, confidence, rationale, None, None, + ) +} + +#[allow(clippy::too_many_arguments)] +pub fn propose_or_merge_memory_with_validity( + start: &Path, + scope: MemoryScope, + kind: MemoryKind, + text: &str, + confidence: f32, + rationale: &str, + valid_from: Option<&str>, + valid_to: Option<&str>, ) -> KimetsuResult { let redaction = redact::redact_secrets(text); if redaction.was_redacted() { @@ -1311,10 +1354,12 @@ pub fn propose_or_merge_memory( // Related claims can disagree. Never append them or inflate use counts. if confidence >= 0.7 { - let memory_id = add_memory(start, scope, kind, text)?; + let memory_id = add_memory_with_validity(start, scope, kind, text, valid_from, valid_to)?; Ok(ProposeResult::Added(memory_id)) } else { - let proposal_id = propose_memory(start, scope, kind, text, confidence, rationale)?; + let proposal_id = propose_memory_with_validity( + start, scope, kind, text, confidence, rationale, valid_from, valid_to, + )?; Ok(ProposeResult::Proposed(proposal_id)) } } @@ -1848,6 +1893,11 @@ pub fn accept_proposal( ) -> KimetsuResult { let (paths, config, conn) = load_project(start)?; let proposal = load_pending_proposal(&conn, proposal_id)?; + let (valid_from, valid_to): (Option, Option) = conn.query_row( + "SELECT valid_from, valid_to FROM memory_proposals WHERE proposal_id=?1", + [proposal_id], + |r| Ok((r.get(0)?, r.get(1)?)), + )?; let run_id = RunId::new(); let _lock = ProjectLock::acquire(&paths, "brain memory accept", Some(run_id))?; let memory_id = Ulid::new().to_string(); @@ -1874,6 +1924,8 @@ pub fn accept_proposal( "kind": proposal.kind, "text": proposal.text, "normalized_text": normalized, + "valid_from": valid_from, + "valid_to": valid_to, "confidence": resolved_confidence, "provenance_snapshot": { "source": "memory_proposal", diff --git a/crates/kimetsu-brain/src/projector.rs b/crates/kimetsu-brain/src/projector.rs index d214f81..b671839 100644 --- a/crates/kimetsu-brain/src/projector.rs +++ b/crates/kimetsu-brain/src/projector.rs @@ -807,6 +807,34 @@ fn collect_injected_memory_ids(conn: &Connection, run_id: &str) -> KimetsuResult Ok(seen.into_iter().collect()) } +/// Validate applicability before projecting any part of the claim. +fn event_validity<'a>( + conn: &Connection, + event: &'a Event, +) -> KimetsuResult<(Option<&'a str>, Option<&'a str>)> { + let endpoint = |name| -> KimetsuResult> { + match event.payload.get(name) { + None | Some(serde_json::Value::Null) => Ok(None), + Some(serde_json::Value::String(value)) => Ok(Some(value.as_str())), + _ => Err(format!("{name} must be a timestamp string or null").into()), + } + }; + let from = endpoint("valid_from")?; + let to = endpoint("valid_to")?; + let (start, end): (Option, Option) = conn.query_row( + "SELECT julianday(?1),julianday(?2)", + params![from, to], + |r| Ok((r.get(0)?, r.get(1)?)), + )?; + if (from.is_some() && start.is_none()) + || (to.is_some() && end.is_none()) + || matches!((start,end), (Some(a),Some(b)) if a >= b) + { + return Err("invalid or empty temporal validity interval".into()); + } + Ok((from, to)) +} + fn apply_memory_accepted(conn: &Connection, event: &Event) -> KimetsuResult<()> { let Some(memory_id) = event .payload @@ -815,6 +843,7 @@ fn apply_memory_accepted(conn: &Connection, event: &Event) -> KimetsuResult<()> else { return Ok(()); }; + let (valid_from, valid_to) = event_validity(conn, event)?; let scope = event .payload .get("scope") @@ -860,9 +889,9 @@ fn apply_memory_accepted(conn: &Connection, event: &Event) -> KimetsuResult<()> INSERT OR REPLACE INTO memories ( memory_id, scope, kind, text, normalized_text, confidence, source_event_id, provenance_snapshot_json, created_at, use_count, - usefulness_score + usefulness_score, valid_from, valid_to ) - VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, 0, ?10) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, 0, ?10, ?11, ?12) ", params![ memory_id, @@ -874,7 +903,9 @@ fn apply_memory_accepted(conn: &Connection, event: &Event) -> KimetsuResult<()> event.event_id.to_string(), serde_json::to_string(&provenance_snapshot)?, ts_text(event)?, - initial_usefulness + initial_usefulness, + valid_from, + valid_to ], )?; @@ -891,6 +922,10 @@ fn apply_memory_accepted(conn: &Connection, event: &Event) -> KimetsuResult<()> // Best-effort: an entity-index hiccup must not fail the write that carries // the user's actual memory. let _ = crate::graph::project_entities(conn, memory_id, text); + if let Some(proposal_id) = event.payload.get("proposal_id").and_then(|v| v.as_str()) { + conn.execute("UPDATE memory_proposals SET status='accepted', decided_at=?2, decided_by='cli' WHERE proposal_id=?1", + params![proposal_id,ts_text(event)?])?; + } Ok(()) } @@ -902,6 +937,7 @@ fn apply_memory_proposed(conn: &Connection, event: &Event) -> KimetsuResult<()> else { return Ok(()); }; + let (valid_from, valid_to) = event_validity(conn, event)?; let scope = event .payload .get("scope") @@ -937,9 +973,9 @@ fn apply_memory_proposed(conn: &Connection, event: &Event) -> KimetsuResult<()> " INSERT OR REPLACE INTO memory_proposals ( proposal_id, run_id, scope, kind, text, rationale, - proposed_confidence, source_event_ids_json, status + proposed_confidence, source_event_ids_json, status, valid_from, valid_to ) - VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, 'pending') + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, 'pending', ?9, ?10) ", params![ proposal_id, @@ -949,7 +985,9 @@ fn apply_memory_proposed(conn: &Connection, event: &Event) -> KimetsuResult<()> text, rationale, confidence, - serde_json::to_string(&source_event_ids)? + serde_json::to_string(&source_event_ids)?, + valid_from, + valid_to ], )?; Ok(()) diff --git a/crates/kimetsu-brain/src/schema.rs b/crates/kimetsu-brain/src/schema.rs index c650a4f..d60fa1a 100644 --- a/crates/kimetsu-brain/src/schema.rs +++ b/crates/kimetsu-brain/src/schema.rs @@ -1217,3 +1217,14 @@ pub fn migrate_v11_to_v12(conn: &Connection) -> KimetsuResult<()> { CREATE TRIGGER IF NOT EXISTS corpus_update AFTER UPDATE OF embedding, embedding_model, text, invalidated_at, superseded_by ON memories BEGIN UPDATE corpus_revision SET revision=revision+1 WHERE id=1; END;")?; Ok(()) } + +/// Keep proposed applicability through review and replay. +pub fn migrate_v12_to_v13(conn: &Connection) -> KimetsuResult<()> { + // Synthetic partial schemas used by migration tooling may omit proposals. + if !table_has_column(conn, "memory_proposals", "proposal_id")? { + return Ok(()); + } + add_column_if_missing(conn, "memory_proposals", "valid_from TEXT")?; + add_column_if_missing(conn, "memory_proposals", "valid_to TEXT")?; + Ok(()) +} diff --git a/crates/kimetsu-brain/src/user_brain.rs b/crates/kimetsu-brain/src/user_brain.rs index a3450fd..8baf1fd 100644 --- a/crates/kimetsu-brain/src/user_brain.rs +++ b/crates/kimetsu-brain/src/user_brain.rs @@ -38,7 +38,6 @@ use kimetsu_core::paths::{ user_brain_db_path, user_brain_enabled, user_brain_enabled_with, user_kimetsu_dir, }; use rusqlite::{Connection, OpenFlags, OptionalExtension}; -use time::OffsetDateTime; use ulid::Ulid; use crate::conflict; @@ -156,9 +155,8 @@ pub fn user_brain_path() -> Option { /// Write a GlobalUser memory to the user brain. /// -/// Differs from `project::add_memory` deliberately: we do NOT emit -/// trace events, run rows, or take the project lock — the user brain -/// has no project to attribute those to. We DO honor the same +/// Uses durable acceptance events without project run rows or a project lock. +/// The user brain has no project to attribute those to. We honor the same /// dedup-by-normalized-text rule so a user who imports the same /// reusable preference twice doesn't end up with duplicate rows. /// @@ -169,6 +167,17 @@ pub fn add_user_memory( kind: MemoryKind, text: &str, confidence: f32, +) -> KimetsuResult { + add_user_memory_with_validity(conn, kind, text, confidence, None, None) +} + +pub fn add_user_memory_with_validity( + conn: &Connection, + kind: MemoryKind, + text: &str, + confidence: f32, + valid_from: Option<&str>, + valid_to: Option<&str>, ) -> KimetsuResult { // v0.4.5: defense-in-depth redaction for external callers who // bypass `project::add_memory` and write to the user brain @@ -201,44 +210,19 @@ pub fn add_user_memory( } let memory_id = Ulid::new().to_string(); - let created_at = OffsetDateTime::now_utc() - .format(&time::format_description::well_known::Rfc3339) - .map_err(|e| format!("timestamp format: {e}"))?; - // The provenance snapshot mirrors what `add_memory` writes for a - // manual_cli source. We use a synthesized RunId because user-brain - // writes don't live inside a run. - let provenance = serde_json::json!({ - "source": "user_brain", - "run_id": RunId::new().to_string(), - "text": text, - }) - .to_string(); - conn.execute( - " - INSERT INTO memories ( - memory_id, scope, kind, text, normalized_text, - confidence, provenance_snapshot_json, created_at, - use_count, usefulness_score - ) - VALUES (?1, 'global_user', ?2, ?3, ?4, ?5, ?6, ?7, 0, 0.0) - ", - rusqlite::params![ - memory_id, - kind.to_string(), - text, - normalized, - confidence, - provenance, - created_at, - ], - )?; - conn.execute( - " - INSERT INTO memories_fts (memory_id, text, kind, scope) - VALUES (?1, ?2, ?3, 'global_user') - ", - rusqlite::params![memory_id, text, kind.to_string()], - )?; + // User memories share the durable projector, including atomic FTS and + // validity. Rebuild must not erase their original accepted claim. + let event = kimetsu_core::event::Event::new( + RunId::new(), + "memory.accepted", + serde_json::json!({ + "memory_id": memory_id, "scope": "global_user", "kind": kind.to_string(), + "text": text, "normalized_text": normalized, "confidence": confidence, + "valid_from": valid_from, "valid_to": valid_to, + "provenance_snapshot": {"source": "user_brain", "text": text}, + }), + ); + crate::projector::apply_events(conn, &[event])?; // v0.4.2: post-insert embedding update. v0.4.3 swapped the // default behind the `embeddings` feature flag — same Noop diff --git a/crates/kimetsu-cli/src/distiller.rs b/crates/kimetsu-cli/src/distiller.rs index 4e185a4..444d661 100644 --- a/crates/kimetsu-cli/src/distiller.rs +++ b/crates/kimetsu-cli/src/distiller.rs @@ -409,11 +409,8 @@ fn tail_chars(s: &str, n: usize) -> String { /// has no proposal queue, so this is add-or-dedup). Returns the count recorded. /// For `GlobalUser`, `start` is ignored (the user brain is global). /// -/// Story 1.2 / Pass B: when a lesson carries `valid_from`/`valid_to` fields -/// (model-detected temporal scope), the written memory is immediately stamped -/// via `mark_memory_temporal` (event-sourced, rebuild-safe). This is optional -/// and cheap-model-gated — without a cheap model there are no temporal tags -/// (graceful: most memories have no bound). +/// Temporal bounds travel in the accepted/proposed event itself. Proposal +/// acceptance preserves applicability and duplicate observations cannot renew it. pub fn distill_and_record( start: &Path, view: &str, @@ -438,9 +435,18 @@ pub fn distill_and_record( .as_ref() .is_none_or(|cfg| cfg.ingestion.quality_filter_enabled); let conn_opt: Option = if quality_enabled { - paths_ok - .as_ref() - .and_then(|paths| rusqlite::Connection::open(&paths.brain_db).ok()) + let user = if scope == MemoryScope::GlobalUser { + kimetsu_brain::user_brain::open_user_brain_readonly_for_config( + cfg_opt + .as_ref() + .is_none_or(|cfg| cfg.kimetsu.use_user_brain), + ) + .ok() + .flatten() + } else { + None + }; + user.or_else(|| project::load_project(start).ok().map(|(_, _, conn)| conn)) } else { None }; @@ -500,16 +506,24 @@ pub fn distill_and_record( let valid_to = lesson.valid_to.clone(); let memory_id_opt: Option = match scope { - MemoryScope::GlobalUser => { - project::add_memory(start, MemoryScope::GlobalUser, kind, text).ok() - } - _ => project::propose_or_merge_memory( + MemoryScope::GlobalUser => project::add_memory_with_validity( + start, + MemoryScope::GlobalUser, + kind, + text, + valid_from.as_deref(), + valid_to.as_deref(), + ) + .ok(), + _ => project::propose_or_merge_memory_with_validity( start, scope, kind, text, lesson.confidence.clamp(0.0, 1.0), "auto-harvested at session end", + valid_from.as_deref(), + valid_to.as_deref(), ) .ok() .and_then(|r| match r { @@ -520,46 +534,7 @@ pub fn distill_and_record( }), }; - if let Some(memory_id) = memory_id_opt { - // Story 1.2 / Pass B: stamp temporal bounds when the model emitted them. - // Only valid_from / valid_to that look like ISO-8601 dates are stamped; - // we skip the stamp when both are None (the common case) to avoid the - // round-trip cost. Best-effort: a stamp failure never blocks recording. - let has_temporal = valid_from.is_some() || valid_to.is_some(); - if has_temporal { - // Load the project connection to stamp the memory. - // For GlobalUser scope the memory lives in the user brain DB; - // use the user-brain open path. - let stamp_result = if scope == MemoryScope::GlobalUser { - kimetsu_brain::user_brain::open_user_brain() - .ok() - .flatten() - .map(|conn| { - kimetsu_brain::projector::mark_memory_temporal( - &conn, - &memory_id, - valid_from.as_deref(), - valid_to.as_deref(), - ) - }) - } else { - // Project scope: load the project DB. - kimetsu_core::paths::ProjectPaths::discover(start) - .ok() - .and_then(|paths| rusqlite::Connection::open(&paths.brain_db).ok()) - .map(|conn| { - kimetsu_brain::projector::mark_memory_temporal( - &conn, - &memory_id, - valid_from.as_deref(), - valid_to.as_deref(), - ) - }) - }; - if let Some(Err(e)) = stamp_result { - eprintln!("kimetsu-distiller: temporal stamp failed for {memory_id}: {e}"); - } - } + if memory_id_opt.is_some() { recorded += 1; } } @@ -1273,6 +1248,101 @@ mod tests { std::fs::remove_dir_all(dir).ok(); } + #[test] + fn temporary_proposal_keeps_expiry_on_acceptance_and_rebuild() { + kimetsu_brain::user_brain::with_user_brain_disabled(|| { + let root = std::env::temp_dir().join(format!("ttl-proposal-{}", ulid::Ulid::new())); + kimetsu_core::paths::git_init_boundary(&root); + project::init_project(&root, false).unwrap(); + let mut provider = MockProvider::new([text_response( + r#"[{"lesson":"Temporarily bypass the cache for integration tests","confidence":0.5,"valid_to":"2099-01-01T00:00:00Z"}]"#, + )]); + distill_and_record( + &root, + "user: cache workaround", + &mut provider, + MemoryScope::Project, + ); + let proposals = + project::list_proposals(&root, project::ProposalFilter::default()).unwrap(); + assert_eq!(proposals.len(), 1); + let id = project::accept_proposal( + &root, + &proposals[0].proposal_id, + project::AcceptOverrides::default(), + ) + .unwrap(); + let (_, _, conn) = project::load_project(&root).unwrap(); + for rebuild in [false, true] { + if rebuild { + kimetsu_brain::projector::rebuild_in_place(&conn).unwrap(); + } + let expiry: Option = conn + .query_row( + "SELECT valid_to FROM memories WHERE memory_id=?1", + [&id], + |r| r.get(0), + ) + .unwrap(); + assert_eq!(expiry.as_deref(), Some("2099-01-01T00:00:00Z")); + } + }); + } + + #[test] + fn temporary_user_brain_duplicates_do_not_renew_expiry() { + let dir = std::env::temp_dir().join(format!("ttl-user-{}", ulid::Ulid::new())); + std::fs::create_dir_all(&dir).unwrap(); + with_user_brain_dir(&dir, || { + for expiry in ["2099-01-01T00:00:00Z", "2099-02-01T00:00:00Z"] { + let json = format!( + r#"[{{"lesson":"Temporarily bypass the shared test cache","confidence":0.9,"valid_to":"{expiry}"}}]"# + ); + let mut provider = MockProvider::new([text_response(&json)]); + distill_and_record( + &dir, + "user: cache workaround", + &mut provider, + MemoryScope::GlobalUser, + ); + } + let conn = kimetsu_brain::user_brain::open_user_brain() + .unwrap() + .unwrap(); + for rebuild in [false, true] { + if rebuild { + kimetsu_brain::projector::rebuild_in_place(&conn).unwrap(); + } + let expiry: Option = conn.query_row("SELECT valid_to FROM memories WHERE text='Temporarily bypass the shared test cache'", [], |r| r.get(0)).unwrap(); + assert_eq!(expiry.as_deref(), Some("2099-01-01T00:00:00Z")); + } + }); + } + + #[test] + fn temporary_global_fallback_keeps_expiry_and_duplicates_do_not_renew_it() { + kimetsu_brain::user_brain::with_user_brain_disabled(|| { + let root = std::env::temp_dir().join(format!("ttl-global-{}", ulid::Ulid::new())); + kimetsu_core::paths::git_init_boundary(&root); + project::init_project(&root, false).unwrap(); + for expiry in ["2099-01-01T00:00:00Z", "2099-02-01T00:00:00Z"] { + let json = format!( + r#"[{{"lesson":"Temporarily bypass the shared test cache","confidence":0.9,"valid_to":"{expiry}"}}]"# + ); + let mut provider = MockProvider::new([text_response(&json)]); + distill_and_record( + &root, + "user: cache workaround", + &mut provider, + MemoryScope::GlobalUser, + ); + } + let (_, _, conn) = project::load_project(&root).unwrap(); + let expiry: Option = conn.query_row("SELECT valid_to FROM memories WHERE text='Temporarily bypass the shared test cache'", [], |r| r.get(0)).unwrap(); + assert_eq!(expiry.as_deref(), Some("2099-01-01T00:00:00Z")); + }); + } + #[test] fn distill_and_record_writes_to_a_temp_brain() { let root = std::env::temp_dir().join(format!( diff --git a/crates/kimetsu-core/src/lib.rs b/crates/kimetsu-core/src/lib.rs index febb2d1..96c296d 100644 --- a/crates/kimetsu-core/src/lib.rs +++ b/crates/kimetsu-core/src/lib.rs @@ -7,7 +7,7 @@ pub mod memory; pub mod paths; pub mod secret; -pub const KIMETSU_SCHEMA_VERSION: i64 = 12; +pub const KIMETSU_SCHEMA_VERSION: i64 = 13; /// The `project.toml` config-file format version. Deliberately decoupled /// from `KIMETSU_SCHEMA_VERSION` (the brain.db schema): the DB schema can /// advance via migrations without forcing every project.toml to be rewritten. From c688c686e170726bffc6e7e07eaa8b8438af9f83 Mon Sep 17 00:00:00 2001 From: RodCor Date: Fri, 4 Sep 2026 22:03:19 -0300 Subject: [PATCH 07/34] Expose opt-in shared ONNX inference thread control --- Cargo.lock | 1 + crates/kimetsu-brain/Cargo.toml | 4 ++- crates/kimetsu-brain/src/embeddings.rs | 50 ++++++++++++++++++++++++++ docs/local-inference.md | 11 ++++++ 4 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 docs/local-inference.md diff --git a/Cargo.lock b/Cargo.lock index d76d5a1..a8e1d2f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2016,6 +2016,7 @@ dependencies = [ "hf-hub", "ignore", "kimetsu-core", + "ort", "petgraph", "regex", "rusqlite", diff --git a/crates/kimetsu-brain/Cargo.toml b/crates/kimetsu-brain/Cargo.toml index f631e78..42d1d1f 100644 --- a/crates/kimetsu-brain/Cargo.toml +++ b/crates/kimetsu-brain/Cargo.toml @@ -20,7 +20,7 @@ categories = ["database", "development-tools"] # retrieval that v0.4.2 ships, so `cargo install kimetsu-cli` # without --features embeddings never downloads a model. default = [] -embeddings = ["dep:fastembed", "dep:usearch", "dep:hf-hub"] +embeddings = ["dep:fastembed", "dep:usearch", "dep:hf-hub", "dep:ort"] # S5.3 (remote-only): full petgraph Tier-2 backend — centrality, # shortest-path, community detection for candidate expansion. # NEVER in default. Enabled by kimetsu-remote so the local CLI never @@ -41,6 +41,8 @@ blake3.workspace = true # Optional so the lean/CLI build never links it. `graph` feature gate. petgraph = { version = "0.6", optional = true } fastembed = { version = "5", optional = true } +# Must share FastEmbed's ORT instance so its sessions use the configured pool. +ort = { version = "=2.0.0-rc.12", optional = true, default-features = false, features = ["std", "api-24"] } # v1.0.0 reranker bench: user-defined ONNX model download via HuggingFace Hub. # Mirrors fastembed's own hf-hub usage (ureq sync API, no tokio). hf-hub = { version = "0.5", optional = true, default-features = false, features = ["ureq"] } diff --git a/crates/kimetsu-brain/src/embeddings.rs b/crates/kimetsu-brain/src/embeddings.rs index 27f24da..ca31b5b 100644 --- a/crates/kimetsu-brain/src/embeddings.rs +++ b/crates/kimetsu-brain/src/embeddings.rs @@ -620,6 +620,27 @@ mod fastembed_backend { }; use std::sync::{Arc, Mutex, OnceLock}; + /// Opt-in process-wide pool configured before any local model session. + /// With no setting, leave the embedding application's ORT environment alone. + /// ORT disables per-session pools when a global pool is installed, so this + /// overrides FastEmbed's per-session available_parallelism setting as well. + fn configure_runtime_threads() -> Result<(), EmbedderError> { + static CONFIGURED: OnceLock> = OnceLock::new(); + CONFIGURED.get_or_init(|| { + let raw = std::env::var("KIMETSU_INTRA_THREADS").ok(); + let Some(threads) = super::parse_runtime_threads(raw.as_deref())? else { return Ok(()) }; + let pool = ort::environment::GlobalThreadPoolOptions::default() + .with_intra_threads(threads).map_err(|e| e.to_string())? + .with_inter_threads(1).map_err(|e| e.to_string())? + .with_spin_control(false).map_err(|e| e.to_string())?; + if !ort::init().with_global_thread_pool(pool).commit() { + return Err("KIMETSU_INTRA_THREADS cannot take effect: ONNX environment already configured; set it before the first model load".into()); + } + eprintln!("kimetsu-brain: ONNX shared intra-op threads={threads}, inter-op=1, spinning=off"); + Ok(()) + }).clone().map_err(EmbedderError::LoadFailed) + } + // ── HF Hub download helper (user-defined ONNX rerankers) ───────────────── /// Alias table: lowercased stable id → HuggingFace repo id. @@ -707,6 +728,7 @@ mod fastembed_backend { impl FastembedEmbedder { pub fn try_open(builtin_id: &str) -> Result { + configure_runtime_threads()?; let (kind, model_id, dim) = match builtin_id { "bge-m3" => (EmbeddingModel::BGEM3, "bge-m3", 1024), "jina-v2-base-code" => ( @@ -827,6 +849,7 @@ mod fastembed_backend { /// initialize a `TextRerank` engine. Unknown ids fall back to the /// jina-reranker-v1-turbo-en default. pub fn try_open(builtin_id: &str) -> Result { + configure_runtime_threads()?; let (kind, stable_id) = match builtin_id { "bge-reranker-base" => (RerankerModel::BGERerankerBase, "bge-reranker-base"), "bge-reranker-v2-m3" => (RerankerModel::BGERerankerV2M3, "bge-reranker-v2-m3"), @@ -857,6 +880,7 @@ mod fastembed_backend { /// the normalized alias (e.g. `"jina-reranker-v1-tiny-en"`) or the raw /// repo id, lower-cased, so it is stable across calls. pub fn try_open_user_defined(alias_or_repo: &str) -> Result { + configure_runtime_threads()?; use fastembed::{RerankInitOptionsUserDefined, UserDefinedRerankingModel}; let (onnx_source, tokenizer_files) = download_user_defined_reranker(alias_or_repo)?; @@ -1054,10 +1078,36 @@ pub fn decode_embedding(bytes: &[u8], expected_dim: Option) -> KimetsuRes Ok(out) } +#[cfg(any(test, feature = "embeddings"))] +fn parse_runtime_threads(raw: Option<&str>) -> Result, String> { + let Some(raw) = raw else { return Ok(None) }; + let threads = raw + .trim() + .parse::() + .map_err(|_| "KIMETSU_INTRA_THREADS must be an integer from 1 to 1024".to_string())?; + if !(1..=1024).contains(&threads) { + return Err("KIMETSU_INTRA_THREADS must be an integer from 1 to 1024".into()); + } + Ok(Some(threads)) +} + #[cfg(test)] mod tests { use super::*; + #[test] + fn runtime_threads_are_explicit_bounded_and_invalid_values_are_errors() { + assert_eq!(parse_runtime_threads(None).unwrap(), None); + assert_eq!(parse_runtime_threads(Some(" 4 ")).unwrap(), Some(4)); + assert_eq!(parse_runtime_threads(Some("1")).unwrap(), Some(1)); + for value in ["0", "-1", "abc", "1025", "999999999999999999999999"] { + assert!( + parse_runtime_threads(Some(value)).is_err(), + "invalid setting: {value}" + ); + } + } + #[test] fn map_builtin_id_maps_aliases_and_defaults_unknown() { assert_eq!(map_builtin_id("bge-small-en-v1.5"), "bge-small-en-v1.5"); diff --git a/docs/local-inference.md b/docs/local-inference.md new file mode 100644 index 0000000..a5172e9 --- /dev/null +++ b/docs/local-inference.md @@ -0,0 +1,11 @@ +# Local inference thread control + +`KIMETSU_INTRA_THREADS=4` opts the process into a shared ONNX Runtime pool with four intra-operation threads, one inter-operation thread and idle spinning disabled. Set it before the first embedding or reranking model loads. Valid values are integers from 1 to 1024. Unset preserves the existing backend default; changing the environment after first load requires a new process. This controls local embedding/reranking inference, not host model generation. + +The configured pool is shared by both models, including user-defined ONNX rerankers. Startup reports the applied setting to stderr. If another embedding application already configured ONNX Runtime, initialization reports that this requested setting cannot take effect rather than claiming success. Existing model-loader fallback behavior still applies, so inspect stderr when diagnosing a disabled semantic path. + +FastEmbed 5.13.4 sets per-session threads to available logical CPUs. The pinned ort 2.0.0-rc.12 session builder disables per-session pools when the environment provides a global pool, making this control effective without vendoring FastEmbed. The direct ort dependency intentionally matches FastEmbed's runtime instance. + +Choose thread counts empirically on the deployment machine. Compare 1, 4, 8 and the unset default with identical cached models, corpus, query order and repetitions. Record quality, p50/p95 latency, total wall time and memory. Shared CPU workloads and idle power matter alongside isolated throughput. No count is declared universally optimal. + +Sources: [ONNX Runtime thread management](https://onnxruntime.ai/docs/performance/tune-performance/threading.html), [ort environment API](https://docs.rs/ort/2.0.0-rc.12/ort/environment/struct.EnvironmentBuilder.html). The dependency's local source was also checked for automatic DisablePerSessionThreads in session builder pre_commit. From 01cc74fecb5135fbe11be0c4e52b634db42cbfb6 Mon Sep 17 00:00:00 2001 From: RodCor Date: Fri, 4 Sep 2026 22:13:37 -0300 Subject: [PATCH 08/34] fix(brain): bound final context delivery and disable Free host harvesting --- crates/kimetsu-agent/src/pipeline.rs | 1 + crates/kimetsu-brain/src/context.rs | 3 + crates/kimetsu-brain/src/delivery.rs | 151 ++++++ crates/kimetsu-chat/src/mcp_server.rs | 437 ++++++++++++------ crates/kimetsu-cli/src/commands/brain.rs | 8 +- crates/kimetsu-cli/src/commands/hooks.rs | 65 ++- crates/kimetsu-cli/src/embed_daemon/proto.rs | 18 + crates/kimetsu-cli/src/embed_daemon/server.rs | 3 + crates/kimetsu-cli/src/main.rs | 24 +- crates/kimetsu-cli/tests/cli_smoke.rs | 67 +++ crates/kimetsu-core/src/config.rs | 26 ++ 11 files changed, 660 insertions(+), 143 deletions(-) create mode 100644 crates/kimetsu-brain/src/delivery.rs diff --git a/crates/kimetsu-agent/src/pipeline.rs b/crates/kimetsu-agent/src/pipeline.rs index 3d87ceb..bd7ea06 100644 --- a/crates/kimetsu-agent/src/pipeline.rs +++ b/crates/kimetsu-agent/src/pipeline.rs @@ -1912,6 +1912,7 @@ fn emit_context_injected( "stage": stage.as_str(), "capsule_handles": capsule_handles, "memory_ids": memory_ids, + "memory_revisions": context::memory_revision_bindings(&bundle.capsules), "prior_run_ids": prior_run_ids, "file_paths": file_paths, "used_tokens": bundle.used_tokens, diff --git a/crates/kimetsu-brain/src/context.rs b/crates/kimetsu-brain/src/context.rs index f8ec55e..a15fa61 100644 --- a/crates/kimetsu-brain/src/context.rs +++ b/crates/kimetsu-brain/src/context.rs @@ -1,3 +1,6 @@ +#[path = "delivery.rs"] +pub mod delivery; + use std::cmp::Ordering; use std::collections::HashMap; diff --git a/crates/kimetsu-brain/src/delivery.rs b/crates/kimetsu-brain/src/delivery.rs new file mode 100644 index 0000000..3953b82 --- /dev/null +++ b/crates/kimetsu-brain/src/delivery.rs @@ -0,0 +1,151 @@ +//! Final serving boundary. Retrieval/reranking decides relevance; this module +//! admits only whole chosen capsules that fit the serialized delivery budget. +use super::{ContextCapsule, memory_revision_bindings}; +use serde_json::{Value, json}; + +pub struct Delivery { + pub payload: Value, + pub capsules: Vec, +} + +/// Conservative tokenizer-independent bound: one token per UTF-8 byte, including +/// the MCP content envelope and both layers of JSON escaping. This is an upper +/// bound for byte-based tokenizers, not a measured model tokenizer count. JSON-RPC +/// request IDs/framing are transport-only and are not included. +pub fn serialized_output_tokens(payload: &Value) -> u32 { + let bytes = json!({"content": [{"type": "text", "text": payload.to_string()}]}) + .to_string() + .len(); + u32::try_from(bytes).unwrap_or(u32::MAX) +} + +fn account(payload: &mut Value) -> u32 { + payload["used_tokens"] = json!(0); + loop { + let bound = serialized_output_tokens(payload); + if payload["used_tokens"].as_u64() == Some(u64::from(bound)) { + return bound; + } + payload["used_tokens"] = json!(bound); + } +} + +pub fn compact_capsules(capsules: &[ContextCapsule]) -> Vec { + capsules + .iter() + .map(|c| { + json!({ + "id": c.id, "kind": c.kind, "summary": c.summary, + "expansion_handle": c.expansion_handle, "score": c.score, + }) + }) + .collect() +} + +/// `render` must rebuild all text and counts from this slice, including duplicated +/// playbook text. Rejected candidates never enter the render callback. If even an +/// empty envelope cannot fit, return an explicit error with its true bound (which +/// can exceed the requested tiny budget); never report success or delivered IDs. +pub fn fit_json( + mut capsules: Vec, + budget: u32, + render: impl Fn(&[ContextCapsule]) -> Value, +) -> Delivery { + loop { + let mut payload = render(&capsules); + payload["budget_tokens"] = json!(budget); + payload["token_accounting"] = json!("utf8_byte_upper_bound"); + if account(&mut payload) <= budget { + return Delivery { payload, capsules }; + } + if capsules.pop().is_none() { + let mut payload = json!({"ok": false, "error": "budget_too_small", + "budget_tokens": budget, "capsules": [], "capsule_count": 0, + "token_accounting": "utf8_byte_upper_bound"}); + account(&mut payload); + return Delivery { payload, capsules }; + } + } +} + +/// Add optional framing only if the complete payload still fits. Evidence wins +/// over warm-start hints; callers must invoke this before logging the exposure. +pub fn add_optional_field(delivery: &mut Delivery, key: &str, value: Value, budget: u32) { + let mut payload = delivery.payload.clone(); + payload[key] = value; + if account(&mut payload) <= budget { + delivery.payload = payload; + } +} + +/// Exposure is built from the final delivered slice, never by re-reading current +/// claims. An empty revision map explicitly means unbound, not legacy attribution. +pub fn injected_payload(capsules: &[ContextCapsule], used_tokens: u32) -> Value { + json!({ + "memory_ids": capsules.iter().filter_map(|c| c.expansion_handle.strip_prefix("memory:")).collect::>(), + "memory_revisions": memory_revision_bindings(capsules), + "capsule_handles": capsules.iter().map(|c| c.expansion_handle.as_str()).collect::>(), + "capsule_count": capsules.len(), "used_tokens": used_tokens, + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn capsule(id: &str, text: &str) -> ContextCapsule { + let mut c = ContextCapsule::wire_minimal(text.into(), "memory".into(), 0.9); + c.id = id.into(); + c.expansion_handle = format!("memory:{id}"); + c.claim_revision = Some(format!("revision-{id}")); + c + } + fn render(c: &[ContextCapsule]) -> Value { + json!({"ok":true,"capsules":compact_capsules(c),"capsule_count":c.len()}) + } + + #[test] + fn final_serialization_bounds_unicode_identifiers_and_escaping() { + for text in [ + "字".repeat(1000), + "no_space_identifier".repeat(1000), + "\"\\\n".repeat(1000), + ] { + let delivery = fit_json( + vec![capsule("kept", "short"), capsule("dropped", &text)], + 800, + render, + ); + assert_eq!(delivery.capsules.len(), 1); + assert_eq!( + delivery.payload["used_tokens"].as_u64(), + Some(u64::from(serialized_output_tokens(&delivery.payload))) + ); + assert!(serialized_output_tokens(&delivery.payload) <= 800); + let event = injected_payload(&delivery.capsules, 0); + assert_eq!(event["memory_ids"], json!(["kept"])); + assert_eq!(event["memory_revisions"], json!({"kept":"revision-kept"})); + } + } + + #[test] + fn tiny_budget_reports_actual_error_cost_and_no_exposure() { + let delivery = fit_json(vec![capsule("secret", "secret")], 1, render); + assert_eq!(delivery.payload["error"], "budget_too_small"); + assert!(delivery.payload["used_tokens"].as_u64().unwrap() > 1); + assert!(delivery.capsules.is_empty()); + assert_eq!(injected_payload(&[], 0)["memory_revisions"], json!({})); + } + + #[test] + fn repeated_playbook_text_is_included_in_final_bound() { + let delivery = fit_json( + vec![capsule("large", &"x".repeat(500))], + 900, + |c| json!({"capsules":compact_capsules(c),"playbook_markdown":c.iter().map(|c|c.summary.as_str()).collect::>().join("\n")}), + ); + assert!(delivery.capsules.is_empty()); + assert_eq!(delivery.payload["playbook_markdown"], ""); + assert!(serialized_output_tokens(&delivery.payload) <= 900); + } +} diff --git a/crates/kimetsu-chat/src/mcp_server.rs b/crates/kimetsu-chat/src/mcp_server.rs index 8b8e9e7..7852da2 100644 --- a/crates/kimetsu-chat/src/mcp_server.rs +++ b/crates/kimetsu-chat/src/mcp_server.rs @@ -12,11 +12,11 @@ use crate::bridge::{ }; use crate::skills::{SkillConfig, SkillRegistry, skill_origin_label}; -const KIMETSU_MCP_INSTRUCTIONS: &str = "Kimetsu is a persistent brain sidecar: it accumulates generalizable knowledge across sessions and retrieves it on demand. Retrieve with kimetsu_brain_context when you start a task. A `skipped: true` reply means the brain held nothing relevant and the call cost nothing, so a call that returns empty is not a wasted one — retrieving is cheaper than rediscovering. Record with kimetsu_brain_record once you know something a later session would otherwise have to work out again: a constraint that was not obvious, an approach that turned out to be wrong, a convention this project follows. Concrete and actionable, with 2-5 domain tags. Cite with kimetsu_brain_cite when a retrieved memory changed what you did. Citations are the brain's only evidence about which memories earn their place; an uncited memory reads as unused. For Terminal-Bench tasks use kimetsu_benchmark_context instead — it prioritizes semantic_operator and anti_pattern memories over episodic summaries. kimetsu_bridge_status and kimetsu_skills_search surface portable skills."; +const KIMETSU_MCP_INSTRUCTIONS: &str = "Kimetsu is a persistent brain sidecar: it accumulates generalizable knowledge across sessions and retrieves it on demand. Retrieve with kimetsu_brain_context when you start a task. A `skipped: true` reply means the brain held nothing relevant; the reply still has a small input-token cost — retrieving is cheaper than rediscovering. Record with kimetsu_brain_record once you know something a later session would otherwise have to work out again: a constraint that was not obvious, an approach that turned out to be wrong, a convention this project follows. Concrete and actionable, with 2-5 domain tags. Cite with kimetsu_brain_cite when a retrieved memory changed what you did. Citations are the brain's only evidence about which memories earn their place; an uncited memory reads as unused. For Terminal-Bench tasks use kimetsu_benchmark_context instead — it prioritizes semantic_operator and anti_pattern memories over episodic summaries. kimetsu_bridge_status and kimetsu_skills_search surface portable skills."; const BRAIN_STATUS_DESCRIPTION: &str = "Inspect the Kimetsu brain for this workspace. Use this to see whether brain.db is initialized, how many memories/runs/proposals exist, and which memories have positive outcome usefulness. Call before relying on memory if you need to know whether the brain has signal."; -const BRAIN_CONTEXT_DESCRIPTION: &str = "Primary Kimetsu brain tool. Call early on non-trivial tasks with a concise task query to retrieve broker-ranked context capsules: accepted memories, repo snippets, manifests, and usefulness-weighted signals. Returns skipped:true (zero tokens) when no capsule is relevant above min_score threshold — safe to call on every non-trivial task without overhead concern."; +const BRAIN_CONTEXT_DESCRIPTION: &str = "Primary Kimetsu brain tool. Call early on non-trivial tasks with a concise task query to retrieve broker-ranked context capsules: accepted memories, repo snippets, manifests, and usefulness-weighted signals. Returns skipped:true when no capsule is delivered. Responses have a small framing cost; used_tokens reports a conservative bound on the complete serialized MCP content."; // TODO (v0.6+): fold benchmark-tag filtering + semantic_operator/anti_pattern // preference into kimetsu_brain_context so kimetsu_benchmark_context becomes @@ -218,7 +218,7 @@ pub fn dispatch( Ok(json!({ "content": [{ "type": "text", - "text": serde_json::to_string_pretty(&value).unwrap_or_else(|_| value.to_string()), + "text": value.to_string(), }] })) } @@ -650,7 +650,7 @@ fn parse_shared_retrieval_args( .filter(|v| !v.trim().is_empty()) .unwrap_or("localization") .to_string(), - budget_tokens: u32_arg(arguments, "budget_tokens", default_budget, 500, 30000), + budget_tokens: u32_arg(arguments, "budget_tokens", default_budget, 0, 30000), max_capsules: u32_arg(arguments, "max_capsules", default_max_capsules, 1, 20) as usize, } } @@ -680,28 +680,38 @@ fn take_session_warm_start(workspace: &Path) -> Option { } fn kimetsu_brain_context(workspace: &Path, arguments: &Value) -> Value { - let mut value = match brain_context_tool(workspace, arguments, None) { - Ok(v) => v, - Err(e) => brain_unavailable_json(workspace, &e), - }; + brain_context_tool_with_warm( + workspace, + arguments, + None, + take_session_warm_start(workspace), + ) + .unwrap_or_else(|e| { + bounded_context_error(arguments, 6000, brain_unavailable_json(workspace, &e)) + }) +} - // Hosts with a session-start hook (Claude Code) or a per-turn hook (Codex, - // Pi, OpenClaw) already receive the warm-start block out of band. Cursor - // has neither — it only speaks MCP — so the session's first context call is - // the one place the repo digest and episodic resume can reach it. Attached - // as a sibling field so the retrieval response shape stays untouched. - if let Some(block) = take_session_warm_start(workspace) { - if let Some(obj) = value.as_object_mut() { - obj.insert( - "warm_start".into(), - json!({ - "context": block, - "how_to_use": "First context call of this session: what this repo is, and where you left off last time. Read it before planning. It is not repeated on later calls." - }), - ); - } +fn bounded_context_error(arguments: &Value, default_budget: u32, error: Value) -> Value { + let budget = parse_shared_retrieval_args(arguments, default_budget, 1).budget_tokens; + kimetsu_brain::context::delivery::fit_json(Vec::new(), budget, |_| error.clone()).payload +} + +fn record_context_delivery( + workspace: &Path, + arguments: &Value, + delivery: &kimetsu_brain::context::delivery::Delivery, + surface: &str, +) { + if std::env::var("KIMETSU_BRAIN_LOG_RETRIEVAL").as_deref() == Ok("0") { + return; } - value + let mut payload = kimetsu_brain::context::delivery::injected_payload( + &delivery.capsules, + delivery.payload["used_tokens"].as_u64().unwrap_or(0) as u32, + ); + payload["surface"] = json!(surface); + payload["session_id"] = arguments.get("session_id").cloned().unwrap_or(Value::Null); + let _ = project::log_telemetry_event(workspace, "context.injected", payload); } /// Candidate pool the remote reranker judges before truncating to the caller's @@ -720,13 +730,22 @@ pub const REMOTE_RERANK_FLOOR: f32 = 0.30; /// - bumps `budget_tokens` to at least 6000 so the pool isn't token-starved /// - retrieves, then calls `rerank_capsules` before serialising /// -/// The JSON response shape is byte-compatible with the `None` path so -/// existing tests and the stdio consumer are unaffected. +/// Both paths share compact serialization and the final output budget. Rejected +/// summaries and ranking diagnostics are never serialized on this surface. pub fn brain_context_tool( workspace: &Path, arguments: &serde_json::Value, reranker: Option<&dyn kimetsu_brain::embeddings::Reranker>, ) -> Result { + brain_context_tool_with_warm(workspace, arguments, reranker, None) +} + +fn brain_context_tool_with_warm( + workspace: &Path, + arguments: &Value, + reranker: Option<&dyn kimetsu_brain::embeddings::Reranker>, + warm_start: Option, +) -> Result { use kimetsu_brain::context::ContextRequest; let query = arguments @@ -735,11 +754,15 @@ pub fn brain_context_tool( .unwrap_or("") .trim(); if query.is_empty() { - return Ok(json!({ - "ok": false, - "error": "missing `query`", - "usage": "Pass a concise task description, e.g. {\"query\":\"terminal-bench mips interpreter create frame.bmp\",\"stage\":\"implementation\"}." - })); + return Ok(bounded_context_error( + arguments, + 6000, + json!({ + "ok": false, + "error": "missing `query`", + "usage": "Pass a concise task description, e.g. {\"query\":\"terminal-bench mips interpreter create frame.bmp\",\"stage\":\"implementation\"}." + }), + )); } let shared = parse_shared_retrieval_args(arguments, 6000, 3); let stage = shared.stage.as_str(); @@ -780,7 +803,7 @@ pub fn brain_context_tool( // W3.2: load broker.ambient from the project config (best-effort; // default true keeps existing behavior when config is missing). let config_ambient = load_config_ambient(workspace); - let (effective_query, ambient_payload) = augment_with_ambient( + let (effective_query, _ambient_payload) = augment_with_ambient( workspace, query, arguments, @@ -826,20 +849,6 @@ pub fn brain_context_tool( REMOTE_RERANK_FLOOR, cap, ); - if bundle.skipped { - return Ok(json!({ - "ok": true, - "skipped": true, - "top_score": bundle.top_score, - "top_abs_evidence": bundle.top_abs_evidence, - "min_score": min_score, - "capsule_count": 0, - "capsules": [], - "usage": { - "how_to_use": "Brain has no capsules above the relevance threshold for this query. Proceed without brain context — this call cost nothing." - } - })); - } let mut bundle = bundle; // v1.5 (Story 2.1): render-time compression. Load compress_capsules @@ -857,46 +866,36 @@ pub fn brain_context_tool( } } - Ok(json!({ - "ok": true, - "skipped": false, - "top_score": bundle.top_score, - "usage": { - "how_to_use": kimetsu_brain::framing::MCP_HOW_TO_USE, - "next_steps": [ - "Use returned expansion_handle values as provenance when deciding what files or memories matter.", - "If capsule_count is 0 or repo capsules are missing, call kimetsu_brain_status and then kimetsu_brain_ingest_repo if repo_indexed_files_for_current_root is 0.", - "Continue with the host harness's normal file/shell/edit tools.", - "If a memory is stale or harmful, call kimetsu_brain_memory_invalidate with its memory id." - ] - }, - "stage": bundle.stage, - "query": query, - "augmented_query": effective_query, - "ambient": ambient_payload, - "budget_tokens": bundle.budget_tokens, - "used_tokens": bundle.used_tokens, - "capsule_count": bundle.capsules.len(), - "excluded_count": bundle.excluded.len(), - // v2.6: how much of the query these capsules collectively - // cover, and what none of them mention. A reader that knows - // memory is thin here can abstain instead of inferring. - "evidence_coverage": bundle.evidence_coverage, - "uncovered_terms": bundle.uncovered_terms, - "partial_evidence_notice": - kimetsu_brain::context::partial_evidence_notice(&bundle), - // v2.6: true when the question was about order, so the capsules - // are oldest-first and each carries the date it was recorded — - // without which a time-ordered bundle reads as a broken ranking. - "chronological": bundle.chronological, - "chronological_note": bundle - .chronological - .then_some(kimetsu_brain::ordering::CHRONOLOGICAL_NOTE), - "capsules": bundle.capsules, - "excluded": bundle.excluded, - })) + use kimetsu_brain::context::delivery::{ + add_optional_field, compact_capsules, fit_json, + }; + let count = bundle.capsules.len(); + let mut delivery = fit_json(bundle.capsules.clone(), budget_tokens, |capsules| { + json!({ + "ok": true, + "skipped": capsules.is_empty(), + "capsule_count": capsules.len(), + "excluded_count": bundle.excluded.len() + count - capsules.len(), + "capsules": compact_capsules(capsules), + "partial_evidence": bundle.evidence_coverage < 1.0 || capsules.len() < count, + }) + }); + if let Some(block) = warm_start { + add_optional_field( + &mut delivery, + "warm_start", + json!({"context":block}), + budget_tokens, + ); + } + record_context_delivery(workspace, arguments, &delivery, "brain_context"); + Ok(delivery.payload) } - Err(err) => Ok(brain_unavailable_json(workspace, &err.to_string())), + Err(err) => Ok(bounded_context_error( + arguments, + 6000, + brain_unavailable_json(workspace, &err.to_string()), + )), } } @@ -1079,11 +1078,15 @@ fn kimetsu_benchmark_context(workspace: &Path, arguments: &Value) -> Value { .unwrap_or("") .trim(); if task.is_empty() { - return json!({ - "ok": false, - "error": "missing `task`", - "usage": "Pass the Terminal-Bench instruction text, e.g. {\"task\":\"compile-compcert build task\",\"dataset\":\"terminal-bench/terminal-bench-2\"}." - }); + return bounded_context_error( + arguments, + 2500, + json!({ + "ok": false, + "error": "missing `task`", + "usage": "Pass the Terminal-Bench instruction text, e.g. {\"task\":\"compile-compcert build task\",\"dataset\":\"terminal-bench/terminal-bench-2\"}." + }), + ); } let dataset = optional_string_arg(arguments, "dataset") @@ -1133,42 +1136,58 @@ fn kimetsu_benchmark_context(workspace: &Path, arguments: &Value) -> Value { ambient_suffix.as_deref(), ) { Ok(context) => { - let ok = context.required_ok; - let error = if ok { - None - } else { - Some("required exact-slug or generalized benchmark memory was not retrieved") - }; - json!({ - "ok": ok, - "error": error, - "usage": { - "how_to_use": "Read playbook_markdown before broad exploration. The playbook prioritizes accepted semantic_operator and anti_pattern memories first, exact episodic run summaries as evidence, then repo snippets.", - "required_mode": "When require_benchmark_memory=true, ok=false means retrieval worked but no exact-slug or generalized benchmark memory was usable. Seed or record benchmark outcome memory before using strict required mode.", - "after_attempt": "Call kimetsu_benchmark_record_outcome with status, commands, pitfalls, verification, and optional generalized_memory so future benchmark runs retrieve a better playbook." - }, - "dataset": context.dataset, - "task": context.task, - "task_slug": context.task_slug, - "warm_policy": context.warm_policy.as_str(), - "query": context.query, - "ambient": ambient_ctx, - "stage": context.stage, - "budget_tokens": context.budget_tokens, - "used_tokens": context.used_tokens, - "capsule_count": context.capsule_count, - "memory_capsule_count": context.memory_capsule_count, - "benchmark_memory_count": context.benchmark_memory_count, - "generalizable_memory_count": context.generalizable_memory_count, - "episodic_memory_count": context.episodic_memory_count, - "required_ok": context.required_ok, - "playbook_markdown": context.playbook_markdown, - "capsules": context.capsules, - "excluded_count": context.excluded.len(), - "excluded": context.excluded, - }) + use kimetsu_brain::context::delivery::{compact_capsules, fit_json}; + let original_count = context.capsules.len(); + let delivery = fit_json(context.capsules.clone(), budget_tokens, |capsules| { + let memory_count = capsules.iter().filter(|c| c.kind == "memory").count(); + let benchmark_count = capsules + .iter() + .filter(|c| { + benchmark::benchmark_memory_matches(c, context.task_slug.as_deref()) + }) + .count(); + let generalizable_count = capsules + .iter() + .filter(|c| { + benchmark::benchmark_memory_role(c).is_some_and(|r| r.is_generalizable()) + }) + .count(); + let episodic_count = capsules + .iter() + .filter(|c| { + benchmark::benchmark_memory_role(c) + == Some(benchmark::BenchmarkMemoryRole::Episodic) + }) + .count(); + let required_ok = + !require_benchmark_memory || benchmark_count > 0 || generalizable_count > 0; + let mut playbook = String::from("# Kimetsu Benchmark Playbook\n"); + for capsule in capsules { + playbook.push_str(&format!( + "- {} [{}]\n", + capsule.summary, capsule.expansion_handle + )); + } + json!({ + "ok": required_ok, "required_ok": required_ok, + "dataset": context.dataset, "task_slug": context.task_slug, + "warm_policy": context.warm_policy.as_str(), + "capsule_count": capsules.len(), "memory_capsule_count": memory_count, + "benchmark_memory_count": benchmark_count, + "generalizable_memory_count": generalizable_count, + "episodic_memory_count": episodic_count, + "playbook_markdown": playbook, "capsules": compact_capsules(capsules), + "excluded_count": context.excluded.len() + original_count - capsules.len(), + }) + }); + record_context_delivery(workspace, arguments, &delivery, "benchmark_context"); + delivery.payload } - Err(err) => brain_unavailable_json(workspace, &err.to_string()), + Err(err) => bounded_context_error( + arguments, + 2500, + brain_unavailable_json(workspace, &err.to_string()), + ), } } @@ -1896,7 +1915,7 @@ fn u32_arg(arguments: &Value, name: &str, default: u32, min: u32, max: u32) -> u _ => None, }) .unwrap_or(default as u64); - (value as u32).clamp(min, max) + value.clamp(u64::from(min), u64::from(max)) as u32 } fn tool_definitions() -> Value { @@ -1917,8 +1936,8 @@ fn tool_definitions() -> Value { "type": "string", "enum": ["localization", "patch_plan", "implementation", "verification", "review"] }, - "budget_tokens": { "type": "integer", "minimum": 500, "maximum": 30000 }, - "min_score": { "type": "number", "minimum": 0.0, "maximum": 1.0, "description": "Skip threshold — if the best capsule scores below this, return empty (zero tokens injected). Default 0.15." }, + "budget_tokens": { "type": "integer", "minimum": 0, "maximum": 30000, "description": "Final serialized MCP content budget, accounted as a conservative UTF-8 byte upper bound. Impossible tiny budgets return budget_too_small with the actual response bound." }, + "min_score": { "type": "number", "minimum": 0.0, "maximum": 1.0, "description": "Skip threshold — if the best capsule scores below this, return no capsules. Response framing still consumes tokens. Default 0.15." }, "max_capsules": { "type": "integer", "minimum": 1, "maximum": 20, "description": "Hard cap on returned capsules. Default 3." }, "tags": { "type": "array", "items": { "type": "string" }, "description": "Domain-hint tags. Capsules whose text contains any of these get a 1.4× score boost." }, "prefer_roles": { "type": "array", "items": { "type": "string" }, "description": "Boost capsules whose kind matches (e.g. [\"semantic_operator\",\"anti_pattern\"] for bench use)." } @@ -1976,7 +1995,7 @@ fn tool_definitions() -> Value { "type": "string", "enum": ["localization", "patch_plan", "implementation", "verification", "review", "harbor"] }, - "budget_tokens": { "type": "integer", "minimum": 500, "maximum": 30000 }, + "budget_tokens": { "type": "integer", "minimum": 0, "maximum": 30000, "description": "Final serialized MCP content budget, accounted as a conservative UTF-8 byte upper bound. Impossible tiny budgets return budget_too_small with the actual response bound." }, "max_capsules": { "type": "integer", "minimum": 1, "maximum": 20 }, "require_benchmark_memory": { "type": "boolean", "description": "When true, ok=false unless at least one exact-slug episodic memory or generalized semantic/anti-pattern benchmark memory is in the playbook." } }, @@ -2524,6 +2543,166 @@ mod tests { }); } + #[test] + fn hardening_context_final_payload_excludes_rejected_and_is_bounded() { + kimetsu_brain::user_brain::with_user_brain_disabled(|| { + let root = temp_root("hardening-mcp-budget"); + fs::create_dir_all(&root).unwrap(); + project::init_project(&root, false).unwrap(); + for i in 0..12 { + project::add_memory( + &root, + MemoryScope::Repo, + MemoryKind::Convention, + &format!( + "ripgrep search files advice {i} {}", + "字\"\\no_space_identifier".repeat(45) + ), + ) + .unwrap(); + } + let result = brain_context_tool( + &root, + &json!({"query":"ripgrep search files", "budget_tokens":500, + "max_capsules":1,"min_score":0.0,"include_ambient":false}), + None, + ) + .unwrap(); + assert!( + result.get("excluded").is_none(), + "rejected summaries must never reach normal MCP" + ); + let wire = json!({"content":[{"type":"text","text":result.to_string()}]}).to_string(); + assert!( + wire.len() <= 500, + "full output exceeds budget: {}", + wire.len() + ); + assert_eq!(result["used_tokens"].as_u64(), Some(wire.len() as u64)); + fs::remove_dir_all(root).unwrap(); + }); + } + + #[test] + fn hardening_served_ids_and_revisions_match_final_mcp_payload() { + kimetsu_brain::user_brain::with_user_brain_disabled(|| { + let root = temp_root("hardening-mcp-delivered"); + fs::create_dir_all(&root).unwrap(); + project::init_project(&root, false).unwrap(); + project::add_memory( + &root, + MemoryScope::Repo, + MemoryKind::Convention, + "ripgrep search files before broad reads", + ) + .unwrap(); + let args = json!({"query":"ripgrep search files", "budget_tokens":1200, + "max_capsules":1,"min_score":0.0,"include_ambient":false}); + let result = brain_context_tool_with_warm( + &root, + &args, + None, + Some("huge warm start 字".repeat(1000)), + ) + .unwrap(); + assert_eq!(result["capsules"].as_array().unwrap().len(), 1); + assert!(result.get("warm_start").is_none()); + assert!(kimetsu_brain::context::delivery::serialized_output_tokens(&result) <= 1200); + let (_, _, conn) = project::load_project(&root).unwrap(); + let payload: String = conn.query_row("SELECT payload_json FROM events WHERE kind='context.injected' ORDER BY rowid DESC LIMIT 1", [], |r| r.get(0)).unwrap(); + let event: Value = serde_json::from_str(&payload).unwrap(); + let id = result["capsules"][0]["expansion_handle"] + .as_str() + .unwrap() + .strip_prefix("memory:") + .unwrap(); + assert_eq!(event["memory_ids"], json!([id])); + assert_eq!(event["memory_revisions"].as_object().unwrap().len(), 1); + assert!(event["memory_revisions"][id].as_str().is_some()); + assert_eq!(event["used_tokens"], result["used_tokens"]); + drop(conn); + fs::remove_dir_all(root).unwrap(); + }); + } + + #[test] + fn hardening_normal_output_does_not_grow_with_rejected_summary_size() { + kimetsu_brain::user_brain::with_user_brain_disabled(|| { + let root = temp_root("hardening-mcp-excluded-growth"); + fs::create_dir_all(&root).unwrap(); + project::init_project(&root, false).unwrap(); + project::add_memory( + &root, + MemoryScope::Repo, + MemoryKind::Convention, + "ripgrep search files before broad reads", + ) + .unwrap(); + let args = json!({"query":"ripgrep search files", "budget_tokens":1200, + "max_capsules":1,"min_score":0.0,"include_ambient":false}); + let before = brain_context_tool(&root, &args, None).unwrap(); + for i in 0..8 { + project::add_memory( + &root, + MemoryScope::Repo, + MemoryKind::Fact, + &format!( + "ripgrep search files rejected_pool_{i} {}", + "do_not_leak_long_identifier".repeat(250) + ), + ) + .unwrap(); + } + let after = brain_context_tool(&root, &args, None).unwrap(); + assert_eq!( + before["capsules"][0]["expansion_handle"], + after["capsules"][0]["expansion_handle"] + ); + assert_eq!( + before["capsules"][0]["summary"], + after["capsules"][0]["summary"] + ); + assert!(after["excluded_count"].as_u64().unwrap() > 0); + // Capsule instance IDs and freshness scores change on retrieval; + // permit bounded numeric metadata, never rejected summary growth. + assert!(after.to_string().len() <= before.to_string().len() + 32); + assert!(!after.to_string().contains("do_not_leak_long_identifier")); + fs::remove_dir_all(root).unwrap(); + }); + } + + #[test] + fn hardening_benchmark_budget_recomputes_required_evidence() { + kimetsu_brain::user_brain::with_user_brain_disabled(|| { + let root = temp_root("hardening-benchmark-budget"); + fs::create_dir_all(&root).unwrap(); + project::init_project(&root, false).unwrap(); + project::add_memory( + &root, + MemoryScope::Repo, + MemoryKind::Command, + &format!( + "[terminal-bench:compile-compcert] build CompCert {}", + "字\\\"".repeat(700) + ), + ) + .unwrap(); + let result = kimetsu_benchmark_context( + &root, + &json!({"task":"compile-compcert build CompCert", "budget_tokens":1200, + "require_benchmark_memory":true,"include_ambient":false}), + ); + assert!(result.get("excluded").is_none()); + assert_eq!(result["ok"], false); + assert_eq!(result["required_ok"], false); + assert_eq!(result["capsule_count"], 0); + assert_eq!(result["benchmark_memory_count"], 0); + assert!(!result["playbook_markdown"].as_str().unwrap().contains("字")); + assert!(kimetsu_brain::context::delivery::serialized_output_tokens(&result) <= 1200); + fs::remove_dir_all(root).unwrap(); + }); + } + #[test] fn brain_context_returns_memory_capsules() { let root = temp_root("kimetsu-mcp-brain"); diff --git a/crates/kimetsu-cli/src/commands/brain.rs b/crates/kimetsu-cli/src/commands/brain.rs index 9491f2f..edb4b5c 100644 --- a/crates/kimetsu-cli/src/commands/brain.rs +++ b/crates/kimetsu-cli/src/commands/brain.rs @@ -1935,7 +1935,13 @@ pub(crate) fn daemon_capsules_to_bundle( use kimetsu_brain::context::{ContextBundle, ContextCapsule}; let capsules: Vec = capsules .into_iter() - .map(|c| ContextCapsule::wire_minimal(c.summary, c.kind, c.score)) + .map(|c| { + let mut capsule = ContextCapsule::wire_minimal(c.summary, c.kind, c.score); + capsule.id = c.id; + capsule.expansion_handle = c.expansion_handle; + capsule.claim_revision = c.claim_revision; + capsule + }) .collect(); // v2.6: measure coverage here too. The in-process path does it during // finalization, which this path skips — so without this the "memory does diff --git a/crates/kimetsu-cli/src/commands/hooks.rs b/crates/kimetsu-cli/src/commands/hooks.rs index 7255366..ccc856c 100644 --- a/crates/kimetsu-cli/src/commands/hooks.rs +++ b/crates/kimetsu-cli/src/commands/hooks.rs @@ -302,6 +302,15 @@ pub(crate) fn brain_context_hook(args: ContextHookArgs) -> KimetsuResult<()> { print_user_prompt_submit_context(&additional_context)?; + let delivered: Vec<_> = capsules_to_render.iter().map(|c| (*c).clone()).collect(); + record_hook_delivery( + &workspace, + &delivered, + &additional_context, + session_id.as_deref(), + "user_prompt", + ); + // v1.5 (Story 2.3): persist newly surfaced handles so subsequent prompts // in the same session skip them. Best-effort — state write must never // break the hook's primary output. @@ -500,11 +509,7 @@ pub(crate) fn brain_stop_hook(args: StopHookArgs) -> KimetsuResult<()> { .and_then(|v| v.as_bool()) .unwrap_or(false); let paths = kimetsu_core::paths::ProjectPaths::discover(&workspace).ok(); - let auto_harvest = paths - .as_ref() - .and_then(|p| project::load_config(p).ok()) - .map(|c| c.learning.auto_harvest) - .unwrap_or(true); + let config = paths.as_ref().and_then(|p| project::load_config(p).ok()); let distiller_enabled = distiller::resolve_pipeline_distiller(&workspace).is_some(); let state_path = paths.as_ref().map(|p| { let cache_dir = kimetsu_core::paths::user_cache_dir_for(&p.repo_root); @@ -530,7 +535,9 @@ pub(crate) fn brain_stop_hook(args: StopHookArgs) -> KimetsuResult<()> { } } - if should_emit_stop_harvest_cue(auto_harvest, distiller_enabled) + if config + .as_ref() + .is_some_and(|c| should_emit_stop_harvest_cue(c, distiller_enabled)) && !stop_active && let Some(paths) = paths.as_ref() { @@ -547,6 +554,14 @@ pub(crate) fn brain_stop_hook(args: StopHookArgs) -> KimetsuResult<()> { } } + // Strict Free (including config-load failure) does not cue host learning. + if !config + .as_ref() + .is_some_and(|c| c.allows_automatic_harvest()) + { + return Ok(()); + } + emit_stop_hook_json(stop_no_lessons_json_with_savings_and_tune( session_savings.as_deref(), retune_cue.as_deref(), @@ -706,8 +721,11 @@ pub(crate) fn stop_lessons_recorded_json_with_savings_and_tune( /// The end-of-session harvest cue fires only when auto-harvest is on AND /// the credentialed distiller is not handling end-of-session itself. -pub(crate) fn should_emit_stop_harvest_cue(auto_harvest: bool, distiller_enabled: bool) -> bool { - auto_harvest && !distiller_enabled +pub(crate) fn should_emit_stop_harvest_cue( + config: &kimetsu_core::config::ProjectConfig, + distiller_enabled: bool, +) -> bool { + config.allows_automatic_harvest() && !distiller_enabled } /// Count `kimetsu_brain_record` tool-use blocks across transcript @@ -878,13 +896,13 @@ pub(crate) fn proactive_hook(event: ProactiveEvent, args: ProactiveHookArgs) -> Ok(config) => { kimetsu_brain::embeddings::apply_embedder_selection(Some(&config.embedder.model)); ( - config.learning.auto_harvest, + config.allows_automatic_harvest(), config.broker.compress_capsules, config.broker.proactive_prefetch, ) } // Fallback: safe defaults — proactive_prefetch OFF (zero behaviour change) - Err(_) => (true, true, false), + Err(_) => (false, true, false), }; let mut input = String::new(); @@ -1147,12 +1165,39 @@ pub(crate) fn proactive_hook(event: ProactiveEvent, args: ProactiveHookArgs) -> print_tool_use_context(event, &additional_context)?; + record_hook_delivery( + &workspace, + std::slice::from_ref(capsule), + &additional_context, + hook.session_id.as_deref(), + "proactive", + ); + state.mark_surfaced(&capsule.expansion_handle); state.record_injection(now); proactive_state::save(&state_path, &state); Ok(()) } +fn record_hook_delivery( + workspace: &std::path::Path, + capsules: &[kimetsu_brain::context::ContextCapsule], + text: &str, + session_id: Option<&str>, + surface: &str, +) { + if std::env::var("KIMETSU_BRAIN_LOG_RETRIEVAL").as_deref() == Ok("0") { + return; + } + let mut payload = kimetsu_brain::context::delivery::injected_payload( + capsules, + u32::try_from(text.len()).unwrap_or(u32::MAX), + ); + payload["session_id"] = serde_json::json!(session_id); + payload["surface"] = serde_json::json!(surface); + let _ = project::log_telemetry_event(workspace, "context.injected", payload); +} + pub(crate) fn proactive_header(event: ProactiveEvent, loop_mode: bool) -> &'static str { match (event, loop_mode) { (_, true) => { diff --git a/crates/kimetsu-cli/src/embed_daemon/proto.rs b/crates/kimetsu-cli/src/embed_daemon/proto.rs index 3268ba8..de0842e 100644 --- a/crates/kimetsu-cli/src/embed_daemon/proto.rs +++ b/crates/kimetsu-cli/src/embed_daemon/proto.rs @@ -62,6 +62,12 @@ pub enum Response { /// `ContextCapsule` — only what the hook needs to render the injection). #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Capsule { + #[serde(default)] + pub id: String, + #[serde(default)] + pub expansion_handle: String, + #[serde(default)] + pub claim_revision: Option, pub summary: String, pub kind: String, pub score: f32, @@ -94,6 +100,15 @@ mod tests { use super::*; use std::io::Cursor; + #[test] + fn legacy_capsule_is_explicitly_unbound() { + let c: Capsule = + serde_json::from_str(r#"{"summary":"legacy", "kind":"memory", "score":0.8}"#).unwrap(); + assert!(c.id.is_empty()); + assert!(c.expansion_handle.is_empty()); + assert!(c.claim_revision.is_none()); + } + #[test] fn request_round_trips_through_a_line() { let req = Request::Retrieve(RetrieveArgs { @@ -125,6 +140,9 @@ mod tests { fn response_round_trips() { let resp = Response::Capsules { capsules: vec![Capsule { + id: "m1".into(), + expansion_handle: "memory:m1".into(), + claim_revision: Some("rev1".into()), summary: "repo:fact - x".into(), kind: "memory".into(), score: 0.9, diff --git a/crates/kimetsu-cli/src/embed_daemon/server.rs b/crates/kimetsu-cli/src/embed_daemon/server.rs index e8ef498..266c900 100644 --- a/crates/kimetsu-cli/src/embed_daemon/server.rs +++ b/crates/kimetsu-cli/src/embed_daemon/server.rs @@ -118,6 +118,9 @@ impl DaemonState { .capsules .iter() .map(|c| proto::Capsule { + id: c.id.clone(), + expansion_handle: c.expansion_handle.clone(), + claim_revision: c.claim_revision.clone(), summary: c.summary.clone(), kind: c.kind.clone(), score: c.score, diff --git a/crates/kimetsu-cli/src/main.rs b/crates/kimetsu-cli/src/main.rs index 9d6d7e8..810945b 100644 --- a/crates/kimetsu-cli/src/main.rs +++ b/crates/kimetsu-cli/src/main.rs @@ -2613,11 +2613,23 @@ mod tests { fs::remove_dir_all(root).expect("remove temp project"); } + #[test] + fn hardening_free_never_requests_host_harvesting() { + let config = kimetsu_core::config::ProjectConfig::default_for_project("free-hooks"); + assert!(!should_emit_stop_harvest_cue(&config, false)); + } + #[test] fn stop_cue_suppressed_when_distiller_enabled() { - assert!(should_emit_stop_harvest_cue(true, false)); - assert!(!should_emit_stop_harvest_cue(true, true)); - assert!(!should_emit_stop_harvest_cue(false, false)); + let mut config = kimetsu_core::config::ProjectConfig::default_for_project("deep-hooks"); + config.cheap_model = Some(kimetsu_core::config::CheapModelSection { + enabled: true, + ..Default::default() + }); + assert!(should_emit_stop_harvest_cue(&config, false)); + assert!(!should_emit_stop_harvest_cue(&config, true)); + config.learning.auto_harvest = false; + assert!(!should_emit_stop_harvest_cue(&config, false)); } // ── Stop-hook output must be valid JSON (CC validates stdout as the @@ -4148,6 +4160,9 @@ scope = 0.1 ..Default::default() }; let wire = vec![crate::embed_daemon::proto::Capsule { + id: "m1".into(), + expansion_handle: "memory:m1".into(), + claim_revision: Some("rev1".into()), summary: "repo:fact - x".to_string(), kind: "memory".to_string(), score: 0.9, @@ -4159,6 +4174,9 @@ scope = 0.1 let bundle = daemon_capsules_to_bundle(&tmp, &request, wire, false, 0.9); assert_eq!(bundle.capsules.len(), 1); assert_eq!(bundle.capsules[0].summary, "repo:fact - x"); + assert_eq!(bundle.capsules[0].id, "m1"); + assert_eq!(bundle.capsules[0].expansion_handle, "memory:m1"); + assert_eq!(bundle.capsules[0].claim_revision.as_deref(), Some("rev1")); assert_eq!(bundle.capsules[0].kind, "memory"); assert!(!bundle.skipped); assert!((bundle.top_score - 0.9).abs() < 1e-6); diff --git a/crates/kimetsu-cli/tests/cli_smoke.rs b/crates/kimetsu-cli/tests/cli_smoke.rs index c792af5..58e001a 100644 --- a/crates/kimetsu-cli/tests/cli_smoke.rs +++ b/crates/kimetsu-cli/tests/cli_smoke.rs @@ -1241,3 +1241,70 @@ fn standing_preferences_reach_the_agent_without_being_retrieved() { let _ = fs::remove_dir_all(&root); } + +#[test] +fn hardening_free_hooks_never_cue_host_after_resolution_or_stop() { + for configured_model in [false, true] { + let (root, cache_home) = seeded_proactive_project(if configured_model { + "free_configured_hooks" + } else { + "free_default_hooks" + }); + let (paths, mut config, conn) = brain_project::load_project(&root).unwrap(); + drop(conn); + config.kimetsu.tier = Some(kimetsu_core::config::Tier::Free); + config.cheap_model = configured_model.then(|| kimetsu_core::config::CheapModelSection { + enabled: true, + ..Default::default() + }); + fs::write(&paths.project_toml, config.to_toml().unwrap()).unwrap(); + run_posttool_hook( + &root, + &cache_home, + "free-resolution", + "cargo test", + "error[E0433]: failed to resolve crate", + ); + let success = run_posttool_hook( + &root, + &cache_home, + "free-resolution", + "cargo test", + "test result: ok. 1 passed; 0 failed", + ); + assert!( + success.trim().is_empty(), + "Free resolution must not invoke host learning: {success}" + ); + let payload = serde_json::json!({"session_id":"free-stop", "transcript": vec![serde_json::json!({"role":"assistant","content":"work"}); 14]}); + let mut child = Command::new(kimetsu_bin()) + .args(["brain", "stop-hook", "--distill-on-stop", "--workspace"]) + .arg(&root) + .env("KIMETSU_TIER", "free") + .env("KIMETSU_USER_BRAIN", "0") + .env("KIMETSU_USER_BRAIN_DIR", &cache_home) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + child + .stdin + .take() + .unwrap() + .write_all(payload.to_string().as_bytes()) + .unwrap(); + let output = child.wait_with_output().unwrap(); + assert!( + output.status.success(), + "{}", + String::from_utf8_lossy(&output.stderr) + ); + assert!( + output.stdout.is_empty(), + "Free Stop must not request learning: {}", + String::from_utf8_lossy(&output.stdout) + ); + fs::remove_dir_all(root).unwrap(); + } +} diff --git a/crates/kimetsu-core/src/config.rs b/crates/kimetsu-core/src/config.rs index f7fc37b..3dc86b4 100644 --- a/crates/kimetsu-core/src/config.rs +++ b/crates/kimetsu-core/src/config.rs @@ -227,6 +227,13 @@ impl ProjectConfig { self.tier().allows_model() } + /// Automatic harvesting may ask either a configured model or the host to + /// generate lessons. Both obey the same Free/Deep policy; a missing model + /// is Free, never an implicit host-generation fallback. + pub fn allows_automatic_harvest(&self) -> bool { + self.learning.auto_harvest && self.allows_model_in_pipeline() + } + pub fn from_toml(value: &str) -> KimetsuResult { Ok(toml::from_str(value)?) } @@ -1428,6 +1435,25 @@ mod tests { } } + #[test] + fn hardening_automatic_harvest_policy_matrix() { + for tier in [None, Some(Tier::Free), Some(Tier::Deep)] { + for model in [false, true] { + for automatic in [false, true] { + let mut config = ProjectConfig::default_for_project("harvest-matrix"); + config.kimetsu.tier = tier; + config.cheap_model = model.then(enabled_cheap_model); + config.learning.auto_harvest = automatic; + assert_eq!( + config.allows_automatic_harvest(), + automatic && model && tier != Some(Tier::Free), + "tier={tier:?} model={model} automatic={automatic}" + ); + } + } + } + } + /// A brand-new project with no model configured is Free, which is what the /// "zero LLM calls in the memory pipeline" claim is measured on. #[test] From e0611db8450f4fbc86ee566c471078bb14303d27 Mon Sep 17 00:00:00 2001 From: RodCor Date: Fri, 4 Sep 2026 22:19:18 -0300 Subject: [PATCH 09/34] fix(remote): preserve compact context delivery budget --- crates/kimetsu-remote/src/rpc.rs | 6 +- crates/kimetsu-remote/tests/http_roundtrip.rs | 57 +++++++++++++++++++ 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/crates/kimetsu-remote/src/rpc.rs b/crates/kimetsu-remote/src/rpc.rs index febe68b..d29a88b 100644 --- a/crates/kimetsu-remote/src/rpc.rs +++ b/crates/kimetsu-remote/src/rpc.rs @@ -241,9 +241,9 @@ async fn dispatch_request( return match res { Ok(Ok(value)) => { // Wrap in the same `{content:[{type,text}]}` envelope that - // generic dispatch produces for tools/call results. - let text = - serde_json::to_string_pretty(&value).unwrap_or_else(|_| value.to_string()); + // generic dispatch produces for tools/call results. Compact JSON + // must match context::delivery's final budget accounting. + let text = value.to_string(); ( Outcome::Ok, jsonrpc_ok( diff --git a/crates/kimetsu-remote/tests/http_roundtrip.rs b/crates/kimetsu-remote/tests/http_roundtrip.rs index b48e0f3..317c082 100644 --- a/crates/kimetsu-remote/tests/http_roundtrip.rs +++ b/crates/kimetsu-remote/tests/http_roundtrip.rs @@ -183,3 +183,60 @@ async fn reranker_in_appstate_intercepts_brain_context() { // Confirm the Arc round-trips through Clone correctly. let _ = Arc::new(StubReranker); } + +#[tokio::test] +async fn hardening_remote_reranker_empty_reply_obeys_final_budget() { + isolate(); + let tmp = tempfile::tempdir().unwrap(); + let mut request = context("unknown deployment"); + request["params"]["arguments"]["budget_tokens"] = json!(250); + request["params"]["arguments"]["include_ambient"] = json!(false); + let response = send_with_reranker(tmp.path(), "empty-budget", request).await; + let payload = inner(&response); + assert_eq!(payload["ok"], true); + assert_eq!(payload["capsule_count"], 0); + let actual = response["result"].to_string().len() as u64; + assert_eq!( + payload["used_tokens"].as_u64(), + Some(actual), + "remote envelope differs from accounted content" + ); + assert!( + actual <= 250, + "remote content exceeded tight budget: {actual}" + ); +} + +#[tokio::test] +async fn hardening_remote_reranker_escaped_capsule_obeys_final_budget() { + isolate(); + let tmp = tempfile::tempdir().unwrap(); + let lesson = "deployment restart flushing path \"C:\\cache\\字\""; + let recorded = send(tmp.path(), "escaped-budget", record(lesson)).await; + assert_eq!(inner(&recorded)["ok"], true); + let mut request = context("deployment restart flushing"); + request["params"]["arguments"]["include_ambient"] = json!(false); + let initial = inner(&send_with_reranker(tmp.path(), "escaped-budget", request.clone()).await); + assert_eq!( + initial["capsule_count"], 1, + "fixture must actually deliver escaped evidence: {initial}" + ); + // Allow only small numeric freshness variations across retrievals. + let budget = initial["used_tokens"].as_u64().unwrap() + 16; + request["params"]["arguments"]["budget_tokens"] = json!(budget); + let response = send_with_reranker(tmp.path(), "escaped-budget", request).await; + let payload = inner(&response); + assert_eq!(payload["capsule_count"], 1); + let summary = payload["capsules"][0]["summary"].as_str().unwrap(); + assert!(summary.contains('字') && summary.contains('"') && summary.contains('\\')); + let actual = response["result"].to_string().len() as u64; + assert_eq!( + payload["used_tokens"].as_u64(), + Some(actual), + "escaping and MCP envelope must be included" + ); + assert!( + actual <= budget, + "remote content {actual} exceeded {budget}" + ); +} From da92a957286cc6a98eb493b8f575d27117daf42b Mon Sep 17 00:00:00 2001 From: RodCor Date: Fri, 4 Sep 2026 22:40:58 -0300 Subject: [PATCH 10/34] Preserve replay history and make memory rebuilds atomic under concurrent writes --- crates/kimetsu-brain/src/maintenance.rs | 39 ++------ crates/kimetsu-brain/src/project.rs | 40 ++++----- crates/kimetsu-brain/src/projector.rs | 109 +++++++++++++++++++++-- crates/kimetsu-cli/src/commands/brain.rs | 5 +- crates/kimetsu-cli/src/main.rs | 12 ++- docs/memory-maintenance.md | 9 ++ 6 files changed, 143 insertions(+), 71 deletions(-) create mode 100644 docs/memory-maintenance.md diff --git a/crates/kimetsu-brain/src/maintenance.rs b/crates/kimetsu-brain/src/maintenance.rs index 82cbcc8..b8baecc 100644 --- a/crates/kimetsu-brain/src/maintenance.rs +++ b/crates/kimetsu-brain/src/maintenance.rs @@ -212,7 +212,7 @@ pub struct CompactReport { /// /// 1. Acquires the project lock (same as `rebuild_projection`). /// 2. Optionally purges invalidated memory rows (`purge_invalidated`). -/// 3. Optionally trims old events (`trim_events_older_than`). +/// 3. Optionally trims old non-projecting telemetry (`trim_events_older_than`). /// 4. Runs `VACUUM` (outside any transaction) to rebuild the file in-place. /// 5. Checkpoints the WAL before measuring `bytes_after` so the measurement /// reflects the on-disk file, not the shadow WAL. @@ -245,37 +245,16 @@ pub fn compact_brain( 0 }; - // Step 4: trim old events (optional, gated by caller). + // Only these non-projecting telemetry kinds may be dropped. Unknown kinds + // are retained so future state/evidence events cannot silently lose history. let events_trimmed = if let Some(dur) = trim_events_older_than { - // Compute the cutoff as an RFC 3339 string (UTC) so it compares - // correctly against the TEXT `ts` column. - let cutoff_secs = dur.as_secs(); - let now_unix = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_secs()) - .unwrap_or(0); - let cutoff_unix = now_unix.saturating_sub(cutoff_secs); - // Format as a naive UTC RFC 3339 string (matches the stored format). - let cutoff_rfc3339 = { - let secs = cutoff_unix as i64; - // Use the `time` crate (already a dependency of projector.rs). - use time::OffsetDateTime; - use time::format_description::well_known::Rfc3339; - OffsetDateTime::from_unix_timestamp(secs) - .map_err(|e| format!("compact_brain: invalid cutoff timestamp: {e}"))? - .format(&Rfc3339) - .map_err(|e| format!("compact_brain: failed to format cutoff: {e}"))? - }; - let count: i64 = conn.query_row( - "SELECT COUNT(*) FROM events WHERE ts < ?1", - rusqlite::params![cutoff_rfc3339], - |r| r.get(0), - )?; + let age_seconds = dur.as_secs().min(i64::MAX as u64) as i64; conn.execute( - "DELETE FROM events WHERE ts < ?1", - rusqlite::params![cutoff_rfc3339], - )?; - count as u64 + "DELETE FROM events + WHERE kind IN ('context.served','retrieval.stats','digest_served','resume_served') + AND julianday(ts) < julianday('now') - CAST(?1 AS REAL) / 86400.0", + rusqlite::params![age_seconds], + )? as u64 } else { 0 }; diff --git a/crates/kimetsu-brain/src/project.rs b/crates/kimetsu-brain/src/project.rs index 62440f1..3a26d9a 100644 --- a/crates/kimetsu-brain/src/project.rs +++ b/crates/kimetsu-brain/src/project.rs @@ -5935,13 +5935,7 @@ max_total_cost_usd = 250.0 }); } - /// Q8-3: event trim removes old events but materialized memories survive. - /// - /// Uses trim_events_older_than = Duration::ZERO so ALL events are - /// classified as "old" relative to `now`. After trim: - /// - events_trimmed > 0 - /// - list_memories still returns the seeded memory (projection survives) - /// - memories are NOT deleted by event trimming + /// Compaction removes expendable telemetry while retaining claim history. #[test] fn compact_brain_event_trim_keeps_materialized_memories() { with_user_brain_disabled(|| { @@ -5956,10 +5950,7 @@ max_total_cost_usd = 250.0 ) .expect("add memory"); - // Trim with a 1-second Duration — but we add a 2-second sleep - // alternative: use Duration::from_secs(0) which means cutoff = - // now, so events older than "right now" are ALL deleted. - // Using 0 ensures even events written 1ms ago are trimmed. + seed_old_compaction_telemetry(&root); let trim_dur = std::time::Duration::from_secs(0); // Small sleep to ensure events are definitively in the past @@ -5990,17 +5981,22 @@ max_total_cost_usd = 250.0 }); } - /// Q8-4: rebuild_projection after event trim does not error. - /// - /// Even with a partially trimmed event log, rebuild_in_place can complete — - /// it replays whatever events remain without panicking or returning an error. + fn seed_old_compaction_telemetry(root: &std::path::Path) { + let (_, _, conn) = load_project(root).unwrap(); + let mut telemetry = Event::new(RunId::new(), "context.served", serde_json::json!({})); + telemetry.ts = time::OffsetDateTime::from_unix_timestamp(946684800).unwrap(); + crate::projector::apply_events(&conn, &[telemetry]).unwrap(); + conn.execute("UPDATE events SET ts='2000-01-01T00:00:00Z'", []).unwrap(); + } + + /// A successful rebuild must preserve the memory, not merely avoid errors. #[test] fn compact_brain_event_trim_then_rebuild_is_consistent() { with_user_brain_disabled(|| { let root = test_root(); init_project(&root, false).expect("init"); - add_memory( + let mid = add_memory( &root, MemoryScope::Project, MemoryKind::Fact, @@ -6008,20 +6004,16 @@ max_total_cost_usd = 250.0 ) .expect("add memory"); - // Trim all events (cutoff = now). - std::thread::sleep(std::time::Duration::from_millis(100)); + seed_old_compaction_telemetry(&root); let report = compact_brain(&root, Some(std::time::Duration::from_secs(0)), false) .expect("compact_brain"); assert!(report.events_trimmed > 0, "events must have been trimmed"); - // rebuild_projection must not error — it replays whatever events remain. let replayed = rebuild_projection(&root, false).expect("rebuild_projection after event trim"); - // The events are gone so the replay count should be 0 (empty log). - assert_eq!( - replayed, 0, - "replayed should be 0 after all events are trimmed" - ); + assert!(replayed > 0, "durable claim history must survive trim"); + assert!(list_memories(&root).unwrap().iter().any(|m| m.memory_id == mid), + "compaction followed by rebuild erased the memory"); }); } diff --git a/crates/kimetsu-brain/src/projector.rs b/crates/kimetsu-brain/src/projector.rs index b671839..6d62ee3 100644 --- a/crates/kimetsu-brain/src/projector.rs +++ b/crates/kimetsu-brain/src/projector.rs @@ -83,8 +83,14 @@ fn upcast_event(event: &Event) -> Cow<'_, Event> { } pub fn rebuild(conn: &Connection, events: &[Event]) -> KimetsuResult<()> { - reset_projection(conn)?; - apply_events(conn, events) + with_write_txn(conn, |c| { + // Trace import supplements the durable log. It must not replace claims + // written directly to that log, nor leave an empty projection on error. + for event in events { + apply_event(c, event)?; + } + replay_locked(c).map(|_| ()) + }) } /// Rebuild the projection from the durable events table (in place). Reads @@ -92,14 +98,33 @@ pub fn rebuild(conn: &Connection, events: &[Event]) -> KimetsuResult<()> { /// re-inserting events (so no duplication). Returns the number of events /// replayed. pub fn rebuild_in_place(conn: &Connection) -> KimetsuResult { - let events = read_events_ordered(conn)?; + let mut count = 0; with_write_txn(conn, |c| { - reset_projection(c)?; - for event in &events { - project_event(c, event)?; - } + count = replay_locked(c)?; Ok(()) })?; + Ok(count) +} + +/// Caller holds the SQLite writer lock across snapshot, reset and replay. +fn replay_locked(conn: &Connection) -> KimetsuResult { + let events = read_events_ordered(conn)?; + let existing = { + let mut stmt = conn.prepare("SELECT memory_id FROM memories")?; + stmt.query_map([], |r| r.get::<_, String>(0))? + .collect::, _>>()? + }; + reset_projection(conn)?; + for event in &events { + project_event(conn, event)?; + } + let mut stmt = conn.prepare("SELECT memory_id FROM memories")?; + let restored = stmt.query_map([], |r| r.get::<_, String>(0))? + .collect::, _>>()?; + let missing = existing.difference(&restored).count(); + if missing > 0 { + return Err(format!("rebuild refused: {missing} existing memories absent from replay; transaction rolled back. Back up the brain and recover missing events before rebuilding; legacy unlogged rows require migration.").into()); + } Ok(events.len()) } @@ -1424,6 +1449,76 @@ mod tests { conn } + #[test] + fn rebuild_import_failure_preserves_existing_projection() { + let conn = make_conn(); + let accepted = Event::new(RunId::new(), "memory.accepted", json!({ + "memory_id":"kept", "text":"keep my evidence", "scope":"project", "kind":"fact" + })); + apply_events(&conn, &[accepted]).unwrap(); + let malformed = Event::new(RunId::new(), "memory.accepted", json!({ + "memory_id":"broken", "text":"bad validity", "scope":"project", "kind":"fact", "valid_from":"nonsense" + })); + assert!(super::rebuild(&conn, &[malformed]).is_err()); + let text: String = conn.query_row("SELECT text FROM memories WHERE memory_id='kept'", [], |r|r.get(0)).unwrap(); + assert_eq!(text, "keep my evidence"); + } + + #[test] + fn rebuild_import_keeps_durable_events_missing_from_trace() { + let conn = make_conn(); + let accepted = Event::new(RunId::new(), "memory.accepted", json!({ + "memory_id":"kept", "text":"durable but not in trace", "scope":"project", "kind":"fact" + })); + apply_events(&conn, &[accepted]).unwrap(); + super::rebuild(&conn, &[]).unwrap(); + assert_eq!(conn.query_row("SELECT count(*) FROM memories WHERE memory_id='kept'", [], |r|r.get::<_,i64>(0)).unwrap(), 1); + } + + #[test] + fn rebuild_refuses_to_erase_unlogged_legacy_user_memory() { + let conn = make_conn(); + conn.execute("INSERT INTO memories(memory_id,scope,kind,text,normalized_text,confidence,provenance_snapshot_json,created_at) VALUES ('legacy','global_user','fact','original','original',0.7,'{\"source\":\"user_brain\"}','2020-01-01T00:00:00Z')", []).unwrap(); + let error = rebuild_in_place(&conn).unwrap_err(); + assert!(error.to_string().contains("absent from replay")); + assert_eq!(conn.query_row("SELECT text FROM memories WHERE memory_id='legacy'", [], |r|r.get::<_,String>(0)).unwrap(), "original"); + assert!(super::rebuild(&conn, &[]).is_err()); + } + + #[test] + fn rebuild_reads_events_after_acquiring_writer_lock() { + use std::sync::atomic::{AtomicBool, Ordering}; + static WAITING: AtomicBool = AtomicBool::new(false); + fn busy(_: i32) -> bool { + WAITING.store(true, Ordering::SeqCst); + std::thread::sleep(std::time::Duration::from_millis(1)); + true + } + WAITING.store(false, Ordering::SeqCst); + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("rebuild.db"); + let writer = Connection::open(&path).unwrap(); + schema::initialize(&writer).unwrap(); + writer.execute_batch("PRAGMA journal_mode=WAL").unwrap(); + let rebuilding = Connection::open(&path).unwrap(); + rebuilding.busy_handler(Some(busy)).unwrap(); + writer.execute_batch("BEGIN IMMEDIATE").unwrap(); + let event = Event::new(RunId::new(), "memory.accepted", json!({ + "memory_id":"concurrent", "text":"committed while rebuild waits", "scope":"project", "kind":"fact" + })); + super::apply_event(&writer, &event).unwrap(); + let worker = std::thread::spawn(move || rebuild_in_place(&rebuilding).map_err(|e|e.to_string())); + let deadline = std::time::Instant::now() + std::time::Duration::from_secs(5); + while !WAITING.load(Ordering::SeqCst) && std::time::Instant::now() < deadline { + std::thread::sleep(std::time::Duration::from_millis(1)); + } + let was_waiting = WAITING.load(Ordering::SeqCst); + writer.execute_batch("COMMIT").unwrap(); + assert!(was_waiting, "rebuild did not reach the contested write lock"); + assert_eq!(worker.join().unwrap().unwrap(), 1); + assert_eq!(writer.query_row("SELECT count(*) FROM memories WHERE memory_id='concurrent'", [], |r|r.get::<_,i64>(0)).unwrap(), 1); + } + fn make_event(run_id: RunId, kind: &str, payload: serde_json::Value) -> Event { Event::new(run_id, kind, payload) } diff --git a/crates/kimetsu-cli/src/commands/brain.rs b/crates/kimetsu-cli/src/commands/brain.rs index edb4b5c..d28f530 100644 --- a/crates/kimetsu-cli/src/commands/brain.rs +++ b/crates/kimetsu-cli/src/commands/brain.rs @@ -1186,9 +1186,8 @@ pub(crate) fn brain_compact(args: CompactArgs) -> KimetsuResult<()> { // Print warnings before performing any destructive operations. if let Some(ref dur_str) = args.trim_events_older_than { eprintln!( - "WARNING: --trim-events-older-than {dur_str} will delete events older than \ - {dur_str} from the durable event log. Materialized memories are unaffected, \ - but the rebuild history window will be reduced." + "Trimming expendable telemetry older than {dur_str}; retaining durable \ + claim, exposure and outcome history for rebuilds." ); } if args.purge_invalidated { diff --git a/crates/kimetsu-cli/src/main.rs b/crates/kimetsu-cli/src/main.rs index 810945b..f02d153 100644 --- a/crates/kimetsu-cli/src/main.rs +++ b/crates/kimetsu-cli/src/main.rs @@ -701,10 +701,9 @@ enum BrainCommand { /// makes VACUUM actually shrink the file. Note: they will no longer appear /// in audit/blame output. /// - /// --trim-events-older-than : deletes events older than the given - /// duration (e.g. 30d, 7d, 24h). WARNING: this shrinks the rebuild - /// history window. Materialized memories (projection rows) are NOT - /// affected — only the raw event log is trimmed. + /// --trim-events-older-than : deletes expendable telemetry older + /// than the given duration (e.g. 30d, 7d, 24h). Durable claim, exposure, + /// outcome and correction history is retained for safe rebuilds. /// /// Examples: /// kimetsu brain compact @@ -1623,9 +1622,8 @@ struct CompactArgs { /// audit/blame output after this operation. #[arg(long)] purge_invalidated: bool, - /// Trim events older than this duration before VACUUM (e.g. 30d, 7d, 24h). - /// WARNING: reduces the rebuild history window. Materialized memories - /// (projection rows) are NOT affected — only the raw event log is trimmed. + /// Trim expendable telemetry older than this duration (e.g. 30d, 7d, 24h). + /// Retains claim, exposure and outcome history required by rebuilds. #[arg(long, value_name = "DUR")] trim_events_older_than: Option, /// Emit machine-readable JSON instead of the human summary. diff --git a/docs/memory-maintenance.md b/docs/memory-maintenance.md new file mode 100644 index 0000000..192bd9f --- /dev/null +++ b/docs/memory-maintenance.md @@ -0,0 +1,9 @@ +# Memory maintenance and replay + +`brain compact --trim-events-older-than ` now removes only expendable telemetry (`context.served`, `retrieval.stats`, `digest_served`, `resume_served`). Claim, correction, exposure, citation, outcome and unknown event kinds are retained. This deliberately reclaims less history than earlier versions: deleting accepted events left memories visible until a rebuild erased them. + +Projection rebuild takes the SQLite writer lock before reading the event log and holds it through reset and replay. A concurrent accepted write is either included in that snapshot or runs after the rebuild. Trace import supplements the durable log, and failed imports roll back both the log and projection. + +If replay cannot reconstruct an existing memory ID, rebuild rolls back and reports the missing-history condition. Older user-brain versions wrote some rows without accepted events; the guard preserves those rows but does not fabricate historical provenance. Back up such a brain and recover its missing events or migrate its legacy rows before rebuilding. This release prevents erasure; it does not provide a complete legacy-history migration. + +Compaction is not archival. Automatic forgetting uses the separate reversible archive/restore lifecycle. Explicit invalidated-row purging still removes materialized audit rows; their durable events remain and replay can reconstruct them. From 1eb5981835b57dc4abdc3f65d3270e1c50ed0d99 Mon Sep 17 00:00:00 2001 From: RodCor Date: Fri, 4 Sep 2026 22:48:53 -0300 Subject: [PATCH 11/34] Stage historical trace events before binding revisions in causal replay --- crates/kimetsu-brain/src/projector.rs | 50 +++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/crates/kimetsu-brain/src/projector.rs b/crates/kimetsu-brain/src/projector.rs index 6d62ee3..2567221 100644 --- a/crates/kimetsu-brain/src/projector.rs +++ b/crates/kimetsu-brain/src/projector.rs @@ -87,7 +87,9 @@ pub fn rebuild(conn: &Connection, events: &[Event]) -> KimetsuResult<()> { // Trace import supplements the durable log. It must not replace claims // written directly to that log, nor leave an empty projection on error. for event in events { - apply_event(c, event)?; + // Historical events cannot be applied to today's final state: + // that could bind an old exposure to a newly corrected claim. + insert_event(c, redact_memory_event(event).as_ref())?; } replay_locked(c).map(|_| ()) }) @@ -116,7 +118,14 @@ fn replay_locked(conn: &Connection) -> KimetsuResult { }; reset_projection(conn)?; for event in &events { - project_event(conn, event)?; + // Upgrade legacy missing bindings at their causal replay position, + // preserving every explicitly supplied map (including empty maps). + let bound = bind_injected_revisions(conn, event)?; + if matches!(&bound, Cow::Owned(_)) { + conn.execute("UPDATE events SET payload_json=?2 WHERE event_id=?1", + params![event.event_id.to_string(), serde_json::to_string(&bound.payload)?])?; + } + project_event(conn, bound.as_ref())?; } let mut stmt = conn.prepare("SELECT memory_id FROM memories")?; let restored = stmt.query_map([], |r| r.get::<_, String>(0))? @@ -1475,6 +1484,43 @@ mod tests { assert_eq!(conn.query_row("SELECT count(*) FROM memories WHERE memory_id='kept'", [], |r|r.get::<_,i64>(0)).unwrap(), 1); } + #[test] + fn trace_import_binds_historical_exposure_before_later_correction() { + let conn = make_conn(); + let accepted = Event::new(RunId::new(), "memory.accepted", json!({ + "memory_id":"m", "text":"old claim", "scope":"project", "kind":"fact" + })); + apply_events(&conn, std::slice::from_ref(&accepted)).unwrap(); + let original_revision = super::claim_revision_at(&conn, "m", None).unwrap(); + let exposure = Event::new(RunId::new(), "context.injected", json!({"memory_ids":["m"]})); + let corrected = Event::new(RunId::new(), "memory.corrected", json!({"memory_id":"m", "text":"new claim"})); + apply_events(&conn, &[corrected]).unwrap(); + super::rebuild(&conn, std::slice::from_ref(&exposure)).unwrap(); + for _ in 0..2 { + let revision: String = conn.query_row("SELECT json_extract(payload_json,'$.memory_revisions.m') FROM events WHERE event_id=?1", [exposure.event_id.to_string()], |r|r.get(0)).unwrap(); + assert_eq!(revision, original_revision); + rebuild_in_place(&conn).unwrap(); + } + } + + #[test] + fn trace_import_replays_missing_correction_before_later_invalidation() { + let conn = make_conn(); + let accepted = Event::new(RunId::new(), "memory.accepted", json!({ + "memory_id":"m", "text":"old claim", "scope":"project", "kind":"fact" + })); + apply_events(&conn, &[accepted]).unwrap(); + let correction = Event::new(RunId::new(), "memory.corrected", json!({"memory_id":"m", "text":"historically corrected"})); + let invalidated = Event::new(RunId::new(), "memory.invalidated", json!({"memory_id":"m", "reason":"retired"})); + apply_events(&conn, &[invalidated]).unwrap(); + super::rebuild(&conn, &[correction]).unwrap(); + for _ in 0..2 { + let row: (String, bool) = conn.query_row("SELECT text,invalidated_at IS NOT NULL FROM memories WHERE memory_id='m'", [], |r|Ok((r.get(0)?,r.get(1)?))).unwrap(); + assert_eq!(row, ("historically corrected".into(), true)); + rebuild_in_place(&conn).unwrap(); + } + } + #[test] fn rebuild_refuses_to_erase_unlogged_legacy_user_memory() { let conn = make_conn(); From a01d9ad678d2d54a072a199c0b5ec71617f3a543 Mon Sep 17 00:00:00 2001 From: RodCor Date: Fri, 4 Sep 2026 22:54:11 -0300 Subject: [PATCH 12/34] Bind memory feedback to delivered claims and preserve recoverable work state --- crates/kimetsu-agent/src/pipeline.rs | 66 +-- crates/kimetsu-brain/src/conflict.rs | 117 ++--- crates/kimetsu-brain/src/conflicts.rs | 6 +- crates/kimetsu-brain/src/context.rs | 9 +- crates/kimetsu-brain/src/digest.rs | 5 +- crates/kimetsu-brain/src/episode.rs | 63 ++- crates/kimetsu-brain/src/feedback.rs | 143 ++++-- .../src/hardening_evidence_tests.rs | 409 ++++++++++++++++++ crates/kimetsu-brain/src/lib.rs | 3 + crates/kimetsu-brain/src/lifecycle.rs | 130 +++++- crates/kimetsu-brain/src/migrate.rs | 5 + crates/kimetsu-brain/src/project.rs | 12 +- crates/kimetsu-brain/src/projector.rs | 318 ++++++++++---- crates/kimetsu-brain/src/reinforce.rs | 42 +- crates/kimetsu-brain/src/roi.rs | 65 +-- crates/kimetsu-brain/src/schema.rs | 8 + crates/kimetsu-brain/src/trust.rs | 76 +--- crates/kimetsu-chat/src/ask.rs | 70 ++- crates/kimetsu-chat/src/mcp_server.rs | 126 +++++- crates/kimetsu-chat/src/repl.rs | 48 +- crates/kimetsu-cli/src/commands/brain.rs | 117 +++-- crates/kimetsu-cli/src/commands/hooks.rs | 19 +- crates/kimetsu-cli/src/commands/lifecycle.rs | 12 +- crates/kimetsu-cli/src/distiller.rs | 32 +- crates/kimetsu-cli/src/main.rs | 22 +- crates/kimetsu-cli/tests/cli_smoke.rs | 59 +++ crates/kimetsu-core/src/lib.rs | 2 +- ...026-09-04-evidence-continuity-hardening.md | 15 + 28 files changed, 1559 insertions(+), 440 deletions(-) create mode 100644 crates/kimetsu-brain/src/hardening_evidence_tests.rs create mode 100644 docs/audits/2026-09-04-evidence-continuity-hardening.md diff --git a/crates/kimetsu-agent/src/pipeline.rs b/crates/kimetsu-agent/src/pipeline.rs index bd7ea06..c8de2bf 100644 --- a/crates/kimetsu-agent/src/pipeline.rs +++ b/crates/kimetsu-agent/src/pipeline.rs @@ -332,17 +332,7 @@ pub fn run_coding(options: CodingRunOptions) -> KimetsuResult { "Context capsules retrieved.", ) }; - // MP-4a: emit a `context.injected` event per stage so the projector can - // correlate accepted memories with terminal outcomes. The projector reads - // every context.injected for a run when applying run.finished/failed and - // updates memories.usefulness_score / use_count accordingly. - emit_context_injected( - &mut writer, - &mut events, - run_id, - CodingStage::Localization, - &localization_context, - )?; + // Retrieval telemetry is not exposure. Emit injections at model delivery. emit_context_served( &mut writer, &mut events, @@ -350,13 +340,6 @@ pub fn run_coding(options: CodingRunOptions) -> KimetsuResult { CodingStage::Localization, &localization_context, )?; - emit_context_injected( - &mut writer, - &mut events, - run_id, - CodingStage::PatchPlan, - &patch_context, - )?; emit_context_served( &mut writer, &mut events, @@ -713,6 +696,34 @@ pub fn run_coding(options: CodingRunOptions) -> KimetsuResult { } }; + let mut delivered = patch_context.clone(); + delivered + .capsules + .retain(|c| !recall_ledger.is_injected(&c.id)); + if let Some(pitfalls) = proactive_pitfall_bundle.as_ref() { + delivered.capsules.extend( + pitfalls + .capsules + .iter() + .filter(|c| !recall_ledger.is_surfaced(&c.id)) + .cloned(), + ); + } + let initial_messages = build_implementation_messages( + &options.task, + &patch_plan, + &patch_context, + proactive_pitfall_bundle.as_ref(), + last_failure_context.as_deref(), + &mut recall_ledger, + )?; + emit_context_injected( + &mut writer, + &mut events, + run_id, + CodingStage::Implementation, + &delivered, + )?; let mut runtime = ToolRuntime::new(&paths.repo_root, run_id)? .with_stage(CodingStage::Implementation.as_str()) .with_config(tool_runtime_config(&config)) @@ -728,14 +739,7 @@ pub fn run_coding(options: CodingRunOptions) -> KimetsuResult { temperature: config.model.temperature, }; let mut loop_runner = AgentLoop::new(provider, runtime, loop_config); - let loop_result = loop_runner.run(build_implementation_messages( - &options.task, - &patch_plan, - &patch_context, - proactive_pitfall_bundle.as_ref(), - last_failure_context.as_deref(), - &mut recall_ledger, - )?); + let loop_result = loop_runner.run(initial_messages); let runtime = loop_runner.into_runtime(); let Some((restored_writer, _)) = runtime.into_trace() else { return Err("implementation runtime lost trace writer".into()); @@ -1201,6 +1205,8 @@ fn try_model_patch_plan( return Ok(None); }; + let mut delivered = patch_context.clone(); + delivered.capsules.retain(|c| !ledger.is_injected(&c.id)); let request = build_patch_plan_request(config, task, files_to_read, patch_context, ledger); record_model_requested( writer, @@ -1211,6 +1217,7 @@ fn try_model_patch_plan( &provider.model_name, &request, )?; + emit_context_injected(writer, events, run_id, CodingStage::PatchPlan, &delivered)?; let response = provider.complete(request)?; record_model_responded( writer, @@ -1915,7 +1922,8 @@ fn emit_context_injected( "memory_revisions": context::memory_revision_bindings(&bundle.capsules), "prior_run_ids": prior_run_ids, "file_paths": file_paths, - "used_tokens": bundle.used_tokens, + "used_tokens": bundle.capsules.iter().map(|c|c.token_estimate).sum::(), + "cost_unit": "legacy_token_estimate", "capsule_count": bundle.capsules.len(), }), ), @@ -2782,6 +2790,10 @@ mod tests { .any(|event| event.kind == "patch.plan.created") ); assert!(events.iter().any(|event| event.kind == "run.finished")); + assert!( + !events.iter().any(|event| event.kind == "context.injected"), + "retrieval without model delivery cannot earn exposure credit" + ); // Dry-run skips Verification. assert!(!events.iter().any(|event| event.kind == "stage.entered" && event.payload.get("stage").and_then(|s| s.as_str()) == Some("verification"))); diff --git a/crates/kimetsu-brain/src/conflict.rs b/crates/kimetsu-brain/src/conflict.rs index cc45f40..4edab6d 100644 --- a/crates/kimetsu-brain/src/conflict.rs +++ b/crates/kimetsu-brain/src/conflict.rs @@ -669,65 +669,80 @@ pub fn resolve_conflict( ) .into()); } - // Pull the pair so we know which (if any) memory to invalidate. + let mut changed = false; + crate::projector::with_write_txn(conn, |conn| { + let metadata: Option<(String,String,String,String,f64,String)> = conn.query_row( + "SELECT new_memory_id,existing_memory_id,scope,kind,similarity,detected_at FROM memory_conflicts WHERE conflict_id=?1 AND resolved_at IS NULL", + [conflict_id], |r|Ok((r.get(0)?,r.get(1)?,r.get(2)?,r.get(3)?,r.get(4)?,r.get(5)?))).optional()?; + let Some((new_id, existing_id, scope, kind, similarity, detected_at)) = metadata else { + return Ok(()); + }; + let event = kimetsu_core::event::Event::new( + kimetsu_core::ids::RunId::new(), + "conflict.resolved", + serde_json::json!({ + "conflict_id":conflict_id,"new_memory_id":new_id,"existing_memory_id":existing_id, + "scope":scope,"kind":kind,"similarity":similarity,"detected_at":detected_at,"resolution":resolution + }), + ); + crate::projector::apply_event(conn, &event)?; + changed = true; + Ok(()) + })?; + Ok(changed) +} + +/// Self-contained pair metadata makes explicit decisions replayable even when +/// the original similarity detection was a derived-only row. +pub(crate) fn project_resolution( + conn: &Connection, + event: &kimetsu_core::event::Event, +) -> KimetsuResult<()> { + let field = |key| { + event + .payload + .get(key) + .and_then(|v| v.as_str()) + .ok_or_else(|| format!("conflict.resolved missing {key}")) + }; + let id = field("conflict_id")?; + let new_id = field("new_memory_id")?; + let existing_id = field("existing_memory_id")?; + let resolution = field("resolution")?; + if new_id == existing_id || !matches!(resolution, "kept_new" | "kept_existing" | "kept_both") { + return Err("invalid conflict pair or resolution".into()); + } let pair: Option<(String, String)> = conn .query_row( - " - SELECT new_memory_id, existing_memory_id - FROM memory_conflicts - WHERE conflict_id = ?1 AND resolved_at IS NULL - ", - params![conflict_id], - |row| Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?)), + "SELECT new_memory_id,existing_memory_id FROM memory_conflicts WHERE conflict_id=?1", + [id], + |r| Ok((r.get(0)?, r.get(1)?)), ) .optional()?; - let Some((new_memory_id, existing_memory_id)) = pair else { - return Ok(false); + if pair.is_some_and(|(a, b)| a != new_id || b != existing_id) { + return Err("conflict pair mismatch".into()); + } + let ts = event + .ts + .format(&time::format_description::well_known::Rfc3339)?; + conn.execute("INSERT OR IGNORE INTO memory_conflicts(conflict_id,new_memory_id,existing_memory_id,scope,kind,similarity,detected_at) VALUES(?1,?2,?3,?4,?5,?6,?7)", + params![id,new_id,existing_id,field("scope")?,field("kind")?,event.payload["similarity"].as_f64().unwrap_or(0.0),field("detected_at")?])?; + let loser = match resolution { + "kept_new" => Some(existing_id), + "kept_existing" => Some(new_id), + _ => None, }; - - let now = OffsetDateTime::now_utc() - .format(&time::format_description::well_known::Rfc3339) - .map_err(|e| format!("timestamp format: {e}"))?; - - // Invalidate the losing side, if any. We do this BEFORE marking - // the conflict resolved so a crash mid-resolve leaves the row - // still actionable for the operator. - let invalidation_reason = format!("v0.5.2 conflict {conflict_id} resolved as {resolution}"); - if resolution == "kept_new" { - conn.execute( - " - UPDATE memories - SET invalidated_at = COALESCE(invalidated_at, ?2), - invalidated_reason = COALESCE(invalidated_reason, ?3) - WHERE memory_id = ?1 - ", - params![existing_memory_id, now, invalidation_reason], - )?; + if let Some(loser) = loser { + conn.execute("UPDATE memories SET invalidated_at=COALESCE(invalidated_at,?2),invalidated_reason=COALESCE(invalidated_reason,?3) WHERE memory_id=?1",params![loser,ts,format!("conflict {id} resolved as {resolution}")])?; + conn.execute("DELETE FROM memories_fts WHERE memory_id=?1", [loser])?; #[cfg(feature = "embeddings")] - crate::ann::on_invalidate(conn, &existing_memory_id); - } else if resolution == "kept_existing" { - conn.execute( - " - UPDATE memories - SET invalidated_at = COALESCE(invalidated_at, ?2), - invalidated_reason = COALESCE(invalidated_reason, ?3) - WHERE memory_id = ?1 - ", - params![new_memory_id, now, invalidation_reason], - )?; - #[cfg(feature = "embeddings")] - crate::ann::on_invalidate(conn, &new_memory_id); + crate::ann::on_invalidate(conn, loser); } - - let updated = conn.execute( - " - UPDATE memory_conflicts - SET resolved_at = ?2, resolution = ?3 - WHERE conflict_id = ?1 AND resolved_at IS NULL - ", - params![conflict_id, now, resolution], + conn.execute( + "UPDATE memory_conflicts SET resolved_at=?2,resolution=?3 WHERE conflict_id=?1", + params![id, ts, resolution], )?; - Ok(updated > 0) + Ok(()) } #[cfg(test)] diff --git a/crates/kimetsu-brain/src/conflicts.rs b/crates/kimetsu-brain/src/conflicts.rs index c4c162a..988a07b 100644 --- a/crates/kimetsu-brain/src/conflicts.rs +++ b/crates/kimetsu-brain/src/conflicts.rs @@ -57,11 +57,7 @@ pub fn list_conflicts(start: &Path, limit: u32) -> KimetsuResult KimetsuResult { let (paths, config, project_conn) = load_project(start)?; let _lock = ProjectLock::acquire(&paths, "brain memory conflict resolve", None)?; diff --git a/crates/kimetsu-brain/src/context.rs b/crates/kimetsu-brain/src/context.rs index a15fa61..17320e0 100644 --- a/crates/kimetsu-brain/src/context.rs +++ b/crates/kimetsu-brain/src/context.rs @@ -1718,14 +1718,7 @@ pub(crate) fn memory_row_to_candidate( } else { 0 }; - // v2.6: discount by origin, unless the memory has proven itself here. - // - // `last_useful_at` is set only on a citation in a *successful* run, so its - // presence is exactly "this has been tested on this machine" — at which - // point where it was written stops being the most informative thing about - // it, whatever that was. Applied after the usefulness boost so it is the - // last word: a memory of unknown origin cannot boost its way past the - // discount, but a corroborated one carries none. + // Reliance and outcome association do not verify a memory or erase origin. let provenance = crate::trust::Provenance::from_snapshot(provenance_snapshot.as_deref().unwrap_or("{}")); let trusted_relevance = diff --git a/crates/kimetsu-brain/src/digest.rs b/crates/kimetsu-brain/src/digest.rs index aeb0fca..a302df3 100644 --- a/crates/kimetsu-brain/src/digest.rs +++ b/crates/kimetsu-brain/src/digest.rs @@ -185,6 +185,9 @@ fn is_stale_inner(workspace: &Path) -> KimetsuResult { /// Records ROI attribution as a side effect, so call it only when the block is /// actually going to be emitted. pub fn warm_start_block(workspace: &Path) -> Option { + warm_start_block_scoped(workspace, "") +} +pub fn warm_start_block_scoped(workspace: &Path, identity: &str) -> Option { // Gate: load warm_start from config (best-effort; default ON). let warm_start_enabled = kimetsu_core::paths::ProjectPaths::discover(workspace) .ok() @@ -204,7 +207,7 @@ pub fn warm_start_block(workspace: &Path) -> Option { } None => build_or_load_digest(workspace, false), }; - let resume = crate::episode::render_resume_context(workspace); + let resume = crate::episode::render_resume_context_scoped(workspace, identity); // v2.6: the user's standing preferences, delivered rather than retrieved. // diff --git a/crates/kimetsu-brain/src/episode.rs b/crates/kimetsu-brain/src/episode.rs index 65968e8..023e39e 100644 --- a/crates/kimetsu-brain/src/episode.rs +++ b/crates/kimetsu-brain/src/episode.rs @@ -4,8 +4,8 @@ //! # Design //! //! Episodes are event-sourced via `work.episode` events → the `work_episodes` -//! projection table. One live (non-superseded) episode per repo at a time; -//! each new capture supersedes the prior. +//! projection table. One live episode per repo and explicit identity lane; +//! each new capture supersedes the prior in the same lane only. //! //! ## Story coverage //! * **1.3** — episode event + table; auto-capture at SessionEnd; optional @@ -33,11 +33,15 @@ use crate::projector; // --------------------------------------------------------------------------- /// A serialized `work.episode` event payload (stored as JSON in the events -/// table). All fields are optional strings so a partial capture never fails. +/// table). An empty identity preserves the legacy unscoped lane. #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)] +#[serde(default)] pub struct EpisodePayload { /// Human-readable task description / goal. pub task: String, + /// Stable caller-selected task/session/worktree lane; empty is the legacy lane. + #[serde(default)] + pub identity: String, /// Narrative summary of what was done. pub summary: String, /// Things that remain to be done. @@ -60,6 +64,7 @@ pub struct EpisodePayload { #[derive(Debug, Clone)] pub struct EpisodeRow { pub episode_id: String, + pub identity: String, pub repo_root: String, pub task: String, pub summary: String, @@ -135,12 +140,12 @@ pub(crate) fn project_work_episode( let dead_ends_json = serde_json::to_string(&payload.dead_ends)?; // 1. Insert the new episode (OR IGNORE for replay-safety). - conn.execute( + let inserted = conn.execute( " INSERT OR IGNORE INTO work_episodes ( episode_id, repo_root, task, summary, open_threads, dead_ends, - hypothesis, note, created_at, superseded_by - ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, NULL) + hypothesis, note, created_at, superseded_by, identity + ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, NULL, ?10) ", params![ episode_id, @@ -152,9 +157,14 @@ pub(crate) fn project_work_episode( payload.hypothesis, payload.note, ts, + payload.identity, ], )?; + if inserted == 0 { + return Ok(()); + } + // 2. Supersede the prior live episode for this repo_root (if any). // We find the most-recent non-superseded episode that is NOT this one, // and stamp superseded_by = episode_id. @@ -165,10 +175,11 @@ pub(crate) fn project_work_episode( WHERE repo_root = ?1 AND superseded_by IS NULL AND episode_id != ?2 + AND identity = ?3 ORDER BY created_at DESC LIMIT 1 ", - params![payload.repo_root, episode_id], + params![payload.repo_root, episode_id, payload.identity], |r| r.get(0), ) .optional()?; @@ -217,6 +228,15 @@ type EpisodeDbRow = ( /// Load the live (non-superseded) episode for `repo_root`, or `None` when /// none exists. pub fn load_live_episode(conn: &Connection, repo_root: &str) -> KimetsuResult> { + load_live_episode_scoped(conn, repo_root, "") +} + +/// Exact identity selection: never falls back to another task or legacy lane. +pub fn load_live_episode_scoped( + conn: &Connection, + repo_root: &str, + identity: &str, +) -> KimetsuResult> { let row: Option = conn .query_row( " @@ -224,11 +244,12 @@ pub fn load_live_episode(conn: &Connection, repo_root: &str) -> KimetsuResult KimetsuResult = serde_json::from_str(&dead_ends_json).unwrap_or_default(); Ok(Some(EpisodeRow { + identity: identity.to_string(), episode_id, repo_root: repo_root_val, task, @@ -301,9 +323,15 @@ pub fn load_live_episode(conn: &Connection, repo_root: &str) -> KimetsuResult. /// ``` pub fn render_resume_context(workspace: &Path) -> Option { + render_resume_context_scoped(workspace, "") +} + +pub fn render_resume_context_scoped(workspace: &Path, identity: &str) -> Option { let (paths, _config, conn) = load_project_readonly(workspace).ok()?; let repo_root = paths.repo_root.to_string_lossy().to_string(); - let episode = load_live_episode(&conn, &repo_root).ok().flatten()?; + let episode = load_live_episode_scoped(&conn, &repo_root, identity) + .ok() + .flatten()?; Some(format_episode_for_context(&episode)) } @@ -387,8 +415,7 @@ pub fn capture_episode(workspace: &Path, payload: EpisodePayload) -> KimetsuResu let event_id = event.event_id.to_string(); // Write into events table and project. - projector::insert_event(&conn, &event)?; - project_work_episode(&conn, &event)?; + projector::apply_events(&conn, &[event])?; Ok(event_id) } @@ -456,6 +483,7 @@ pub fn rule_based_episode(transcript_view: &str, repo_root: &str, note: &str) -> .collect(); EpisodePayload { + identity: String::new(), task: task.chars().take(200).collect(), summary: summary.chars().take(300).collect(), open_threads, @@ -474,9 +502,15 @@ pub fn rule_based_episode(transcript_view: &str, repo_root: &str, note: &str) -> /// Convenience wrapper: load the live episode for `workspace` (resolves the /// repo_root from `ProjectPaths`). Used by `kimetsu resume`. pub fn load_live_episode_for_workspace(workspace: &Path) -> KimetsuResult> { + load_live_episode_for_workspace_scoped(workspace, "") +} +pub fn load_live_episode_for_workspace_scoped( + workspace: &Path, + identity: &str, +) -> KimetsuResult> { let (paths, _config, conn) = load_project_readonly(workspace)?; let repo_root = paths.repo_root.to_string_lossy().to_string(); - load_live_episode(&conn, &repo_root) + load_live_episode_scoped(&conn, &repo_root, identity) } // --------------------------------------------------------------------------- @@ -514,6 +548,7 @@ mod tests { let conn = make_in_memory_conn(); let run_id = kimetsu_core::ids::RunId::new(); let payload = EpisodePayload { + identity: String::new(), task: "fix the build".to_string(), summary: "added missing feature flag".to_string(), open_threads: vec!["still need tests".to_string()], @@ -614,6 +649,7 @@ mod tests { let conn = make_in_memory_conn(); let run_id = kimetsu_core::ids::RunId::new(); let payload = EpisodePayload { + identity: String::new(), task: "some task".to_string(), repo_root: "/repo/reset".to_string(), ..Default::default() @@ -653,6 +689,7 @@ mod tests { let conn = make_in_memory_conn(); let run_id = kimetsu_core::ids::RunId::new(); let payload = EpisodePayload { + identity: String::new(), task: "rebuild test".to_string(), repo_root: "/repo/rebuild".to_string(), ..Default::default() @@ -687,6 +724,7 @@ mod tests { #[test] fn render_resume_context_formats_episode() { let ep = EpisodeRow { + identity: String::new(), episode_id: "ep1".to_string(), repo_root: "/r".to_string(), task: "implement feature X".to_string(), @@ -760,6 +798,7 @@ mod tests { let conn = make_in_memory_conn(); let run_id = kimetsu_core::ids::RunId::new(); let payload = EpisodePayload { + identity: String::new(), task: "edge test".to_string(), repo_root: "/repo/edges".to_string(), memory_ids: vec!["mem-abc".to_string(), "mem-xyz".to_string()], diff --git a/crates/kimetsu-brain/src/feedback.rs b/crates/kimetsu-brain/src/feedback.rs index 7f28be8..aa9a98e 100644 --- a/crates/kimetsu-brain/src/feedback.rs +++ b/crates/kimetsu-brain/src/feedback.rs @@ -82,18 +82,9 @@ pub fn abort_run(start: &Path, run_id_str: &str) -> KimetsuResult<()> { Ok(()) } -/// C7: best-effort telemetry write from a hook context (no active run). -/// -/// Appends a single event (e.g. `context.served`) directly to the project -/// brain's `events` table with a sentinel run_id (`"hook"` encoded as a -/// ULID-zero string). Swallows all errors — telemetry must never break -/// a hook. Opens the DB read-write so the hook can record misses without -/// holding a write lock (the DB is opened and closed immediately). -/// -/// The sentinel run_id is a valid ULID-shaped string (`00000000000000000000000000` -/// padded to 26 chars). Crucially there is **no** corresponding row in the -/// `runs` table; analytics windows over `context.served` filter by `ts`, not -/// `run_id`, so this is correct. +/// Best-effort telemetry from hooks/MCP without an active pipeline run. +/// Each event receives a fresh run identity, never a shared nil-run bucket. +/// Producers include session/task identity in their payload when available. pub fn log_telemetry_event( start: &Path, kind: &str, @@ -106,13 +97,116 @@ pub fn log_telemetry_event( let conn = Connection::open(&paths.brain_db)?; schema::initialize(&conn)?; - // Sentinel run_id: all-zero ULID (26 '0' chars), never in `runs`. - let sentinel_run_id = RunId(ulid::Ulid::nil()); - let event = Event::new(sentinel_run_id, kind, payload); + // Standalone telemetry cannot alias unrelated sessions. + let event = Event::new(RunId::new(), kind, payload); projector::insert_event(&conn, &event)?; Ok(()) } +/// Persist a caller-created final delivery event. Event ID is the exposure handle. +pub fn record_context_exposure(start: &Path, event: &Event) -> KimetsuResult<()> { + if event.kind != "context.injected" + || event.payload.get("memory_revisions").is_none() + || event.run_id.0 == ulid::Ulid::nil() + { + return Err("exposure requires final delivered revision map".into()); + } + let (paths, _, conn) = load_project(start)?; + let _lock = ProjectLock::acquire(&paths, "context exposure", Some(event.run_id))?; + projector::apply_events(&conn, std::slice::from_ref(event)) +} + +fn load_exposure( + conn: &Connection, + exposure_id: &str, +) -> KimetsuResult<(RunId, serde_json::Value)> { + let row: Option<(String, String)> = conn + .query_row( + "SELECT run_id,payload_json FROM events WHERE event_id=?1 AND kind='context.injected'", + [exposure_id], + |r| Ok((r.get(0)?, r.get(1)?)), + ) + .optional()?; + let (run, payload) = row.ok_or("unknown context exposure")?; + Ok(( + RunId(run.parse::()?), + serde_json::from_str(&payload)?, + )) +} + +/// Record reliance on exactly one delivered claim. This does not verify truth +/// or credit success. Repeated citations for an exposure/memory are idempotent. +pub fn record_exposure_citation( + start: &Path, + exposure_id: &str, + memory_id: &str, + note: Option<&str>, +) -> KimetsuResult<()> { + let (paths, _, conn) = load_project(start)?; + let _lock = ProjectLock::acquire(&paths, "exposure citation", None)?; + projector::with_write_txn(&conn, |conn| { + let (run, payload) = load_exposure(conn, exposure_id)?; + if !payload["memory_ids"] + .as_array() + .is_some_and(|ids| ids.iter().any(|id| id.as_str() == Some(memory_id))) + { + return Err("memory was not delivered in this exposure".into()); + } + let revision = payload["memory_revisions"][memory_id] + .as_str() + .filter(|s| !s.is_empty()) + .ok_or("delivered claim is unbound")?; + let exists:bool=conn.query_row("SELECT EXISTS(SELECT 1 FROM events WHERE kind='memory.cited' AND json_extract(payload_json,'$.exposure_id')=?1 AND json_extract(payload_json,'$.memory_id')=?2)",rusqlite::params![exposure_id,memory_id],|r|r.get(0))?; + if exists { + return Ok(()); + } + let event = Event::new( + run, + "memory.cited", + serde_json::json!({"memory_id":memory_id,"revision_event_id":revision,"exposure_id":exposure_id,"rationale":note,"evidence_kind":"reliance"}), + ); + projector::apply_event(conn, &event) + }) +} + +/// Observed run outcome for an actual exposure. Unknown outcomes do nothing. +/// No retrieval or invented citations occur here. One outcome per exposure run. +pub fn record_exposure_outcome( + start: &Path, + exposure_id: &str, + passed: Option, +) -> KimetsuResult { + let Some(passed) = passed else { return Ok(0) }; + let (paths, _, conn) = load_project(start)?; + let _lock = ProjectLock::acquire(&paths, "exposure outcome", None)?; + let mut count = 0; + projector::with_write_txn(&conn, |conn| { + let (run, payload) = load_exposure(conn, exposure_id)?; + let exists:bool=conn.query_row("SELECT EXISTS(SELECT 1 FROM events WHERE run_id=?1 AND kind IN ('run.finished','run.failed','run.aborted'))",[run.to_string()],|r|r.get(0))?; + if exists { + return Ok(()); + } + count = payload["memory_ids"] + .as_array() + .map(|ids| { + ids.iter() + .filter(|id| { + id.as_str() + .is_some_and(|id| payload["memory_revisions"][id].as_str().is_some()) + }) + .count() + }) + .unwrap_or(0); + let event = Event::new( + run, + if passed { "run.finished" } else { "run.failed" }, + serde_json::json!({"exposure_id":exposure_id,"evidence_kind":"outcome_association"}), + ); + projector::apply_event(conn, &event) + })?; + Ok(count) +} + /// v1.5: scan `events` for `memory.cited` entries and, for each cited /// memory id, check the dropped-capsule sidecar. When a cited memory /// was in the recent-dropped window (it was excluded by the relevance @@ -168,7 +262,7 @@ pub fn emit_regret_for_cited_memories(start: &Path, events: &[kimetsu_core::even /// v1.5: write a `memory.cited` event from the MCP `kimetsu_brain_cite` tool. /// -/// Uses the same sentinel run_id as [`log_telemetry_event`] (all-zero ULID) +/// Legacy explicit manual reliance without a delivered exposure. /// so no corresponding `runs` row is required. The event is inserted then /// projected (populating `memory_citations`) in one connection, and the /// regret sidecar is checked best-effort. @@ -176,14 +270,10 @@ pub fn record_mcp_citation(start: &Path, memory_id: &str, note: Option<&str>) -> record_citations(start, &[memory_id.to_string()], note, None) } -/// v2.5.2 consolidation v1: record one or more standalone citations as a -/// GROUP. All memories share a fresh run_id, which is what makes them -/// co-cited (`brain reinforce --staple` staples pairs that answer together -/// repeatedly). `query` links the citations to the question they answered, -/// feeding the `query_routes` derived index. The `standalone: true` payload -/// flag tells the projector to apply the cited-outcome delta immediately -/// (there is no terminal run event coming), replacing the old nil-run gate -/// so grouped citations still bump usefulness. +/// Record explicit manual reliance as a group for co-citation analysis. These +/// legacy unscoped citations do not update outcome statistics or verify claims. +/// Query text is retained only when learning.store_queries is enabled. Callers +/// with delivered context should use record_exposure_citation instead. pub fn record_citations( start: &Path, memory_ids: &[String], @@ -197,6 +287,9 @@ pub fn record_citations( let conn = Connection::open(&paths.brain_db)?; schema::initialize(&conn)?; + let store_queries = crate::project::load_config(&paths) + .map(|cfg| cfg.learning.store_queries) + .unwrap_or(false); let group_run_id = RunId::new(); let mut events = Vec::with_capacity(memory_ids.len()); for (turn, memory_id) in memory_ids.iter().enumerate() { @@ -208,7 +301,7 @@ pub fn record_citations( if let Some(n) = note { payload["rationale"] = serde_json::json!(n); } - if let Some(q) = query { + if let Some(q) = query.filter(|_| store_queries) { payload["query"] = serde_json::json!(q); } events.push(kimetsu_core::event::Event::new( diff --git a/crates/kimetsu-brain/src/hardening_evidence_tests.rs b/crates/kimetsu-brain/src/hardening_evidence_tests.rs new file mode 100644 index 0000000..8655c63 --- /dev/null +++ b/crates/kimetsu-brain/src/hardening_evidence_tests.rs @@ -0,0 +1,409 @@ +use crate::{episode, projector, schema}; +use kimetsu_core::{event::Event, ids::RunId}; +use rusqlite::Connection; +use serde_json::json; +fn event(kind: &str, payload: serde_json::Value) -> Event { + Event::new(RunId::new(), kind, payload) +} +fn conn() -> Connection { + let c = Connection::open_in_memory().unwrap(); + schema::initialize(&c).unwrap(); + c +} +fn accepted(id: &str) -> Event { + event( + "memory.accepted", + json!({"memory_id":id,"scope":"project","kind":"fact","text":format!("quokka {id}")}), + ) +} +#[test] +fn hardening_concurrent_episode_lanes_replay() { + let c = conn(); + for (lane, note) in [ + ("a", "first"), + ("b", "other"), + ("a", "latest"), + ("", "legacy"), + ] { + projector::apply_events( + &c, + &[event( + "work.episode", + serde_json::to_value(episode::EpisodePayload { + repo_root: "repo".into(), + identity: lane.into(), + note: note.into(), + ..Default::default() + }) + .unwrap(), + )], + ) + .unwrap(); + } + for replay in [false, true] { + if replay { + projector::rebuild_in_place(&c).unwrap(); + } + assert_eq!( + episode::load_live_episode_scoped(&c, "repo", "a") + .unwrap() + .unwrap() + .note, + "latest" + ); + assert_eq!( + episode::load_live_episode_scoped(&c, "repo", "b") + .unwrap() + .unwrap() + .note, + "other" + ); + assert!( + episode::load_live_episode_scoped(&c, "repo", "missing") + .unwrap() + .is_none() + ); + assert_eq!( + episode::load_live_episode(&c, "repo") + .unwrap() + .unwrap() + .note, + "legacy" + ); + } +} +#[test] +fn hardening_archive_restore_replay_preserves_expiry_and_invalidity() { + let c = conn(); + projector::apply_events( + &c, + &[ + accepted("archive"), + accepted("invalid"), + accepted("superseded"), + event( + "memory.temporal", + json!({"memory_id":"archive","valid_to":"2025-01-01T00:00:00Z"}), + ), + event( + "memory.invalidated", + json!({"memory_id":"archive","reason":"forgotten"}), + ), + event( + "memory.invalidated", + json!({"memory_id":"invalid","reason":"incorrect"}), + ), + event( + "memory.invalidated", + json!({"memory_id":"superseded","reason":"forgotten"}), + ), + event( + "memory.superseded", + json!({"memory_id":"superseded","survivor_id":"archive"}), + ), + event("memory.restored", json!({"memory_id":"archive"})), + event( + "memory.invalidated", + json!({"memory_id":"invalid","reason":"forgotten"}), + ), + event("memory.restored", json!({"memory_id":"invalid"})), + event("memory.restored", json!({"memory_id":"superseded"})), + ], + ) + .unwrap(); + for replay in [false, true] { + if replay { + projector::rebuild_in_place(&c).unwrap(); + } + assert!(c.query_row("SELECT invalidated_at IS NULL AND valid_to='2025-01-01T00:00:00Z' FROM memories WHERE memory_id='archive'",[],|r|r.get::<_,bool>(0)).unwrap()); + assert_eq!(c.query_row("SELECT COUNT(*) FROM memories WHERE memory_id IN ('invalid','superseded') AND invalidated_at IS NOT NULL",[],|r|r.get::<_,i64>(0)).unwrap(),2); + assert_eq!( + c.query_row( + "SELECT COUNT(*) FROM memories_fts WHERE memory_id='archive'", + [], + |r| r.get::<_, i64>(0) + ) + .unwrap(), + 1 + ); + } +} +#[test] +fn hardening_manual_conflict_replay_and_atomic_validation() { + let c = conn(); + projector::apply_events(&c, &[accepted("a"), accepted("b"), accepted("c")]).unwrap(); + for (id, a, b) in [("ab", "a", "b"), ("ac", "a", "c")] { + c.execute("INSERT INTO memory_conflicts(conflict_id,new_memory_id,existing_memory_id,scope,kind,similarity,detected_at) VALUES(?1,?2,?3,'project','fact',0.95,'2026-01-01T00:00:00Z')",[id,a,b]).unwrap(); + } + assert!(crate::conflict::resolve_conflict(&c, "ab", "kept_new").unwrap()); + assert!(crate::conflict::resolve_conflict(&c, "ac", "kept_both").unwrap()); + assert!(!crate::conflict::resolve_conflict(&c, "ab", "kept_existing").unwrap()); + projector::rebuild_in_place(&c).unwrap(); + assert_eq!( + c.query_row( + "SELECT COUNT(*) FROM memory_conflicts WHERE resolved_at IS NOT NULL", + [], + |r| r.get::<_, i64>(0) + ) + .unwrap(), + 2 + ); + assert!( + c.query_row( + "SELECT invalidated_at IS NOT NULL FROM memories WHERE memory_id='b'", + [], + |r| r.get::<_, bool>(0) + ) + .unwrap() + ); + assert!( + c.query_row( + "SELECT invalidated_at IS NULL FROM memories WHERE memory_id='c'", + [], + |r| r.get::<_, bool>(0) + ) + .unwrap() + ); + let before: i64 = c + .query_row("SELECT COUNT(*) FROM events", [], |r| r.get(0)) + .unwrap(); + assert!(projector::apply_events(&c,&[event("conflict.resolved",json!({"conflict_id":"ab","new_memory_id":"b","existing_memory_id":"a","resolution":"kept_both"}))]).is_err()); + assert_eq!( + c.query_row("SELECT COUNT(*) FROM events", [], |r| r.get::<_, i64>(0)) + .unwrap(), + before + ); +} +#[test] +fn hardening_exposure_no_invention_stale_feedback_and_unknown_outcome() { + crate::user_brain::with_user_brain_disabled(|| { + use crate::project; + use kimetsu_core::memory::{MemoryKind, MemoryScope}; + let root = std::env::temp_dir().join(format!("kimetsu-exposure-{}", ulid::Ulid::new())); + kimetsu_core::paths::git_init_boundary(&root); + project::init_project(&root, false).unwrap(); + let id = project::add_memory(&root, MemoryScope::Project, MemoryKind::Fact, "quokka fact") + .unwrap(); + assert_eq!( + crate::reinforce::credit_benchmark_outcome(&root, "quokka", true, 3).unwrap(), + 0 + ); + let exposure = event( + "context.injected", + json!({"memory_ids":[id],"memory_revisions":{id.clone():format!("baseline:{id}")}}), + ); + project::record_context_exposure(&root, &exposure).unwrap(); + assert_eq!( + project::record_exposure_outcome(&root, &exposure.event_id.to_string(), None).unwrap(), + 0 + ); + assert!( + project::record_exposure_citation( + &root, + &exposure.event_id.to_string(), + "unknown", + None + ) + .is_err() + ); + project::record_exposure_citation(&root, &exposure.event_id.to_string(), &id, None) + .unwrap(); + let (_, _, c) = project::load_project(&root).unwrap(); + assert_eq!( + c.query_row( + "SELECT use_count FROM memories WHERE memory_id=?1", + [&id], + |r| r.get::<_, i64>(0) + ) + .unwrap(), + 0 + ); + projector::apply_events( + &c, + &[event( + "memory.corrected", + json!({"memory_id":id,"text":"replacement quokka"}), + )], + ) + .unwrap(); + project::record_exposure_outcome(&root, &exposure.event_id.to_string(), Some(true)) + .unwrap(); + assert_eq!( + c.query_row( + "SELECT use_count FROM memories WHERE memory_id=?1", + [&id], + |r| r.get::<_, i64>(0) + ) + .unwrap(), + 0 + ); + assert!( + c.query_row( + "SELECT last_useful_at IS NULL FROM memories WHERE memory_id=?1", + [&id], + |r| r.get::<_, bool>(0) + ) + .unwrap() + ); + let empty = event( + "context.injected", + json!({"memory_ids":[],"memory_revisions":{}}), + ); + project::record_context_exposure(&root, &empty).unwrap(); + assert_eq!( + project::record_exposure_outcome(&root, &empty.event_id.to_string(), Some(true)) + .unwrap(), + 0 + ); + let revision = projector::claim_revision_at(&c, &id, None).unwrap(); + let current = event( + "context.injected", + json!({"memory_ids":[id],"memory_revisions":{id.clone():revision}}), + ); + project::record_context_exposure(&root, ¤t).unwrap(); + project::record_exposure_citation(&root, ¤t.event_id.to_string(), &id, None).unwrap(); + assert_eq!( + project::record_exposure_outcome(&root, ¤t.event_id.to_string(), Some(true)) + .unwrap(), + 1 + ); + assert_eq!( + project::record_exposure_outcome(&root, ¤t.event_id.to_string(), Some(true)) + .unwrap(), + 0 + ); + projector::rebuild_in_place(&c).unwrap(); + assert_eq!( + c.query_row( + "SELECT use_count FROM memories WHERE memory_id=?1", + [&id], + |r| r.get::<_, i64>(0) + ) + .unwrap(), + 1 + ); + drop(c); + std::fs::remove_dir_all(root).unwrap(); + }); +} + +#[test] +fn hardening_roi_labels_assumptions_and_keeps_delivery_units_separate() { + let c = conn(); + projector::apply_events(&c,&[event("context.injected",json!({"memory_ids":[],"memory_revisions":{},"used_tokens":100,"cost_unit":"serialized_utf8_byte_bound"})),event("context.injected",json!({"memory_ids":[],"memory_revisions":{},"used_tokens":20}))]).unwrap(); + let report = + crate::roi::roi_report(&c, crate::roi::RoiWindow::All, "test-model", None).unwrap(); + assert!(report.estimate_label.contains("not measured")); + assert_eq!(report.model, "test-model"); + assert_eq!( + report.delivered_cost_by_unit["serialized_utf8_byte_bound"], + 100 + ); + assert_eq!(report.delivered_cost_by_unit["legacy_token_estimate"], 20); + assert_eq!(report.assumptions["tokens_per_citation"]["fact"], 500); +} + +#[test] +fn hardening_mixed_revision_run_is_not_current_claim_evidence() { + let c = conn(); + let run = RunId::new(); + projector::apply_events(&c, &[accepted("m")]).unwrap(); + let old = Event::new( + run, + "context.injected", + json!({"memory_ids":["m"],"memory_revisions":{"m":"baseline:m"}}), + ); + projector::apply_events( + &c, + &[ + old, + event( + "memory.corrected", + json!({"memory_id":"m","text":"replacement"}), + ), + ], + ) + .unwrap(); + let revision = projector::claim_revision_at(&c, "m", None).unwrap(); + projector::apply_events( + &c, + &[ + Event::new( + run, + "context.injected", + json!({"memory_ids":["m"],"memory_revisions":{"m":revision}}), + ), + Event::new(run, "run.finished", json!({})), + ], + ) + .unwrap(); + assert_eq!( + c.query_row( + "SELECT use_count FROM memories WHERE memory_id='m'", + [], + |r| r.get::<_, i64>(0) + ) + .unwrap(), + 0 + ); +} + +#[test] +fn hardening_explicit_revision_cannot_override_actual_delivery() { + let c = conn(); + let run = RunId::new(); + projector::apply_events(&c, &[accepted("m")]).unwrap(); + let exposure = Event::new( + run, + "context.injected", + json!({"memory_ids":["m"],"memory_revisions":{"m":"baseline:m"}}), + ); + let exposure_id = exposure.event_id.to_string(); + projector::apply_events( + &c, + &[ + exposure, + event( + "memory.corrected", + json!({"memory_id":"m","text":"replacement"}), + ), + ], + ) + .unwrap(); + let revision = projector::claim_revision_at(&c, "m", None).unwrap(); + projector::apply_events( + &c, + &[Event::new( + run, + "memory.cited", + json!({"memory_id":"m","exposure_id":exposure_id,"revision_event_id":revision}), + )], + ) + .unwrap(); + assert_eq!( + c.query_row("SELECT count(*) FROM memory_citations", [], |r| r + .get::<_, i64>(0)) + .unwrap(), + 0 + ); +} +#[test] +fn hardening_partial_episode_preserves_explicit_lane() { + let c = conn(); + projector::apply_events( + &c, + &[event( + "work.episode", + json!({"repo_root":"repo","identity":"task-a","note":"checkpoint"}), + )], + ) + .unwrap(); + assert_eq!( + episode::load_live_episode_scoped(&c, "repo", "task-a") + .unwrap() + .unwrap() + .note, + "checkpoint" + ); + assert!(episode::load_live_episode(&c, "repo").unwrap().is_none()); +} diff --git a/crates/kimetsu-brain/src/lib.rs b/crates/kimetsu-brain/src/lib.rs index 755d5d3..50e6587 100644 --- a/crates/kimetsu-brain/src/lib.rs +++ b/crates/kimetsu-brain/src/lib.rs @@ -54,3 +54,6 @@ pub mod tune; pub mod tuneset; pub mod user_brain; pub mod user_profile; + +#[cfg(test)] +mod hardening_evidence_tests; diff --git a/crates/kimetsu-brain/src/lifecycle.rs b/crates/kimetsu-brain/src/lifecycle.rs index 2156cb5..5c50b98 100644 --- a/crates/kimetsu-brain/src/lifecycle.rs +++ b/crates/kimetsu-brain/src/lifecycle.rs @@ -48,7 +48,7 @@ use serde::{Deserialize, Serialize}; use time::OffsetDateTime; use time::format_description::well_known::Rfc3339; -use crate::project::{AcceptOverrides, invalidate_memory, reject_proposal}; +use crate::project::{AcceptOverrides, reject_proposal}; // --------------------------------------------------------------------------- // Story 3.4 — Structured invalidation taxonomy @@ -156,6 +156,7 @@ impl Default for ForgetOptions { /// One candidate identified by the forgetting pass. #[derive(Debug, Clone, Serialize)] pub struct ForgetCandidate { + pub claim_revision: String, pub memory_id: String, pub scope: String, pub kind: String, @@ -217,16 +218,77 @@ pub fn forget_brain(start: &Path, opts: ForgetOptions) -> KimetsuResult summary.archived += 1, - Err(_) => summary.failed += 1, + // Re-read eligibility while holding both the project and SQLite writer locks. + // The candidate scan is advisory; corrections/useful feedback may have landed. + let (paths, _, conn) = crate::project::load_project(start)?; + let _lock = crate::lock::ProjectLock::acquire(&paths, "archive", None)?; + crate::projector::with_write_txn(&conn, |conn| { + summary.archived = archive_candidates_locked(conn, &opts, &cutoff_iso, &candidates)?; + Ok(()) + })?; + + Ok(summary) +} + +/// Called only inside the SQLite write transaction; selection must be revalidated. +fn archive_candidates_locked( + conn: &Connection, + opts: &ForgetOptions, + cutoff_iso: &str, + candidates: &[ForgetCandidate], +) -> KimetsuResult { + let eligible = query_forget_candidates( + conn, + opts.usefulness_floor, + cutoff_iso, + opts.protect_use_count, + )?; + let mut archived = 0; + for candidate in candidates { + if !eligible.iter().any(|c| { + c.memory_id == candidate.memory_id && c.claim_revision == candidate.claim_revision + }) { + continue; } + let event = kimetsu_core::event::Event::new( + kimetsu_core::ids::RunId::new(), + "memory.invalidated", + serde_json::json!({"memory_id":candidate.memory_id,"reason":"forgotten"}), + ); + crate::projector::apply_event(conn, &event)?; + archived += 1; } + Ok(archived) +} - Ok(summary) +/// Explicit archival status; invalidated/corrected/superseded claims are excluded. +pub fn list_archived(start: &Path) -> KimetsuResult> { + let (_, _, conn) = crate::project::load_project_readonly(start)?; + let mut stmt=conn.prepare("SELECT memory_id,text,invalidated_at,valid_to FROM memories WHERE invalidated_at IS NOT NULL AND superseded_by IS NULL AND invalidated_reason IN ('forgotten','forgotten/archived','forgotten_archived') ORDER BY invalidated_at DESC")?; + let rows=stmt.query_map([],|r|Ok(serde_json::json!({"memory_id":r.get::<_,String>(0)?,"text":r.get::<_,String>(1)?,"archived_at":r.get::<_,String>(2)?,"valid_to":r.get::<_,Option>(3)?,"status":"archived"})))?.collect::,_>>()?; + Ok(rows) +} + +/// Restore archival state only; never reopen expiry or resurrect superseded claims. +pub fn restore_memory(start: &Path, memory_id: &str) -> KimetsuResult { + let (paths, _, conn) = crate::project::load_project(start)?; + let _lock = crate::lock::ProjectLock::acquire(&paths, "restore", None)?; + let mut restored = false; + crate::projector::with_write_txn(&conn, |conn| { + let eligible:bool=conn.query_row("SELECT EXISTS(SELECT 1 FROM memories WHERE memory_id=?1 AND invalidated_at IS NOT NULL AND superseded_by IS NULL AND invalidated_reason IN ('forgotten','forgotten/archived','forgotten_archived'))",[memory_id],|r|r.get(0))?; + if !eligible { + return Ok(()); + } + let event = kimetsu_core::event::Event::new( + kimetsu_core::ids::RunId::new(), + "memory.restored", + serde_json::json!({"memory_id":memory_id}), + ); + crate::projector::apply_event(conn, &event)?; + restored = true; + Ok(()) + })?; + Ok(restored) } /// Query candidates that meet the forget criteria. @@ -294,6 +356,7 @@ fn query_forget_candidates( }; let text_preview: String = text.chars().take(80).collect(); candidates.push(ForgetCandidate { + claim_revision: crate::projector::claim_revision_at(conn, &memory_id, None)?, memory_id, scope, kind, @@ -579,6 +642,7 @@ pub fn invalidations_by_reason(conn: &Connection) -> KimetsuResult(0) + ) + .unwrap(), + "old" + ); + } + // ------------------------------------------------------------------------- // Story 3.4: InvalidationReason round-trips // ------------------------------------------------------------------------- diff --git a/crates/kimetsu-brain/src/migrate.rs b/crates/kimetsu-brain/src/migrate.rs index 296c333..68ce5e4 100644 --- a/crates/kimetsu-brain/src/migrate.rs +++ b/crates/kimetsu-brain/src/migrate.rs @@ -114,6 +114,11 @@ fn migrations() -> &'static [Migration] { description: "preserve proposal temporal applicability", up: crate::schema::migrate_v12_to_v13, }, + Migration { + version: 14, + description: "scope work episodes by explicit identity", + up: crate::schema::migrate_v13_to_v14, + }, ] } diff --git a/crates/kimetsu-brain/src/project.rs b/crates/kimetsu-brain/src/project.rs index 3a26d9a..87c5b74 100644 --- a/crates/kimetsu-brain/src/project.rs +++ b/crates/kimetsu-brain/src/project.rs @@ -5986,7 +5986,8 @@ max_total_cost_usd = 250.0 let mut telemetry = Event::new(RunId::new(), "context.served", serde_json::json!({})); telemetry.ts = time::OffsetDateTime::from_unix_timestamp(946684800).unwrap(); crate::projector::apply_events(&conn, &[telemetry]).unwrap(); - conn.execute("UPDATE events SET ts='2000-01-01T00:00:00Z'", []).unwrap(); + conn.execute("UPDATE events SET ts='2000-01-01T00:00:00Z'", []) + .unwrap(); } /// A successful rebuild must preserve the memory, not merely avoid errors. @@ -6012,8 +6013,13 @@ max_total_cost_usd = 250.0 let replayed = rebuild_projection(&root, false).expect("rebuild_projection after event trim"); assert!(replayed > 0, "durable claim history must survive trim"); - assert!(list_memories(&root).unwrap().iter().any(|m| m.memory_id == mid), - "compaction followed by rebuild erased the memory"); + assert!( + list_memories(&root) + .unwrap() + .iter() + .any(|m| m.memory_id == mid), + "compaction followed by rebuild erased the memory" + ); }); } diff --git a/crates/kimetsu-brain/src/projector.rs b/crates/kimetsu-brain/src/projector.rs index 2567221..5f7d4a1 100644 --- a/crates/kimetsu-brain/src/projector.rs +++ b/crates/kimetsu-brain/src/projector.rs @@ -37,7 +37,7 @@ fn is_sqlite_busy(err: &(dyn std::error::Error + 'static)) -> bool { /// confidence) never interleave across writers. Retries the whole transaction on /// `SQLITE_BUSY`/`LOCKED` (which can only occur at `BEGIN`). `&Connection` can't /// use `transaction_with_behavior`, so the transaction is driven manually. -fn with_write_txn(conn: &Connection, mut body: F) -> KimetsuResult<()> +pub(crate) fn with_write_txn(conn: &Connection, mut body: F) -> KimetsuResult<()> where F: FnMut(&Connection) -> KimetsuResult<()>, { @@ -122,13 +122,19 @@ fn replay_locked(conn: &Connection) -> KimetsuResult { // preserving every explicitly supplied map (including empty maps). let bound = bind_injected_revisions(conn, event)?; if matches!(&bound, Cow::Owned(_)) { - conn.execute("UPDATE events SET payload_json=?2 WHERE event_id=?1", - params![event.event_id.to_string(), serde_json::to_string(&bound.payload)?])?; + conn.execute( + "UPDATE events SET payload_json=?2 WHERE event_id=?1", + params![ + event.event_id.to_string(), + serde_json::to_string(&bound.payload)? + ], + )?; } project_event(conn, bound.as_ref())?; } let mut stmt = conn.prepare("SELECT memory_id FROM memories")?; - let restored = stmt.query_map([], |r| r.get::<_, String>(0))? + let restored = stmt + .query_map([], |r| r.get::<_, String>(0))? .collect::, _>>()?; let missing = existing.difference(&restored).count(); if missing > 0 { @@ -253,7 +259,7 @@ fn reset_projection(conn: &Connection) -> KimetsuResult<()> { Ok(()) } -fn apply_event(conn: &Connection, event: &Event) -> KimetsuResult<()> { +pub(crate) fn apply_event(conn: &Connection, event: &Event) -> KimetsuResult<()> { let event = redact_memory_event(event); let event = bind_injected_revisions(conn, event.as_ref())?; let event = event.as_ref(); @@ -281,6 +287,8 @@ fn project_event(conn: &Connection, event: &Event) -> KimetsuResult<()> { "memory.proposed" => apply_memory_proposed(conn, event), "memory.rejected" => apply_memory_rejected(conn, event), "memory.invalidated" => apply_memory_invalidated(conn, event), + "memory.restored" => apply_memory_restored(conn, event), + "conflict.resolved" => crate::conflict::project_resolution(conn, event), // v0.5.1: per-turn memory citation. The model emits this // via the `cite_memory` tool when it consciously leveraged // a retrieved capsule. Best-effort — a missing or @@ -379,7 +387,12 @@ fn apply_memory_cited(conn: &Connection, event: &Event) -> KimetsuResult<()> { .payload .get("revision_event_id") .and_then(|v| v.as_str()); - let exposed = run_claim_revision(conn, &event.run_id.to_string(), memory_id)?; + let exposed = + if let Some(exposure_id) = event.payload.get("exposure_id").and_then(|v| v.as_str()) { + exact_claim_exposure(conn, exposure_id, &event.run_id.to_string(), memory_id)? + } else { + run_claim_revision(conn, &event.run_id.to_string(), memory_id)? + }; // A citation without a revision or exposure is ambiguous after a text // correction. Keep its durable event, but do not credit the new claim. let exposed = match exposed { @@ -387,6 +400,12 @@ fn apply_memory_cited(conn: &Connection, event: &Event) -> KimetsuResult<()> { ClaimExposure::Absent => None, ClaimExposure::Bound(revision) => Some(revision), }; + if explicit + .zip(exposed.as_deref()) + .is_some_and(|(explicit, delivered)| explicit != delivered) + { + return Ok(()); + } let evidence_revision = explicit.map(str::to_owned).or(exposed); if evidence_revision .as_ref() @@ -432,21 +451,7 @@ fn apply_memory_cited(conn: &Connection, event: &Event) -> KimetsuResult<()> { )?; } - // Flagship 2 / Story 2.4: a STANDALONE citation (the `record_citations` / - // `brain cite` path, marked `standalone: true` — or the legacy nil - // sentinel run_id) is an explicit outcome signal with no run finalization - // behind it, so apply the cited-memory delta here. Citations tied to a - // REAL run keep metadata-only here and are bumped by - // `apply_memory_usefulness_for_run` on the terminal run event — the gate - // avoids double-counting. - let standalone = event - .payload - .get("standalone") - .and_then(|v| v.as_bool()) - .unwrap_or(false); - if standalone || event.run_id.0 == ulid::Ulid::nil() { - apply_cited_outcome(conn, memory_id, 1.0, 1.0, &cited_at, true)?; - } + // A citation records reliance only. Outcomes are credited separately. Ok(()) } @@ -1072,6 +1077,15 @@ fn apply_memory_invalidated(conn: &Connection, event: &Event) -> KimetsuResult<( .get("reason") .and_then(|value| value.as_str()) .map(|s| s.to_string()); + if reason + .as_deref() + .is_some_and(|r| matches!(r, "forgotten" | "forgotten/archived" | "forgotten_archived")) + { + let active:bool=conn.query_row("SELECT EXISTS(SELECT 1 FROM memories WHERE memory_id=?1 AND invalidated_at IS NULL AND superseded_by IS NULL)",[memory_id],|r|r.get(0))?; + if !active { + return Ok(()); + } + } conn.execute( " UPDATE memories @@ -1081,6 +1095,7 @@ fn apply_memory_invalidated(conn: &Connection, event: &Event) -> KimetsuResult<( ", params![memory_id, ts_text(event)?, reason], )?; + conn.execute("DELETE FROM memories_fts WHERE memory_id=?1", [memory_id])?; #[cfg(feature = "embeddings")] crate::ann::on_invalidate(conn, memory_id); // v2.6: drop the entity rows too, so the graph stops routing traffic @@ -1089,6 +1104,24 @@ fn apply_memory_invalidated(conn: &Connection, event: &Event) -> KimetsuResult<( Ok(()) } +/// Restore only archived claims. Temporal expiry and supersession are preserved. +fn apply_memory_restored(conn: &Connection, event: &Event) -> KimetsuResult<()> { + let Some(id) = event.payload.get("memory_id").and_then(|v| v.as_str()) else { + return Ok(()); + }; + let changed = conn.execute( + "UPDATE memories SET invalidated_at=NULL, invalidated_reason=NULL + WHERE memory_id=?1 AND superseded_by IS NULL AND invalidated_at IS NOT NULL + AND invalidated_reason IN ('forgotten','forgotten/archived','forgotten_archived')", + [id], + )?; + if changed > 0 { + conn.execute("DELETE FROM memories_fts WHERE memory_id=?1", [id])?; + conn.execute("INSERT INTO memories_fts(memory_id,text,kind,scope) SELECT memory_id,text,kind,scope FROM memories WHERE memory_id=?1", [id])?; + } + Ok(()) +} + /// Story 3.1: project a `memory.superseded` event. /// /// Payload fields: @@ -1461,39 +1494,77 @@ mod tests { #[test] fn rebuild_import_failure_preserves_existing_projection() { let conn = make_conn(); - let accepted = Event::new(RunId::new(), "memory.accepted", json!({ - "memory_id":"kept", "text":"keep my evidence", "scope":"project", "kind":"fact" - })); + let accepted = Event::new( + RunId::new(), + "memory.accepted", + json!({ + "memory_id":"kept", "text":"keep my evidence", "scope":"project", "kind":"fact" + }), + ); apply_events(&conn, &[accepted]).unwrap(); - let malformed = Event::new(RunId::new(), "memory.accepted", json!({ - "memory_id":"broken", "text":"bad validity", "scope":"project", "kind":"fact", "valid_from":"nonsense" - })); + let malformed = Event::new( + RunId::new(), + "memory.accepted", + json!({ + "memory_id":"broken", "text":"bad validity", "scope":"project", "kind":"fact", "valid_from":"nonsense" + }), + ); assert!(super::rebuild(&conn, &[malformed]).is_err()); - let text: String = conn.query_row("SELECT text FROM memories WHERE memory_id='kept'", [], |r|r.get(0)).unwrap(); + let text: String = conn + .query_row( + "SELECT text FROM memories WHERE memory_id='kept'", + [], + |r| r.get(0), + ) + .unwrap(); assert_eq!(text, "keep my evidence"); } #[test] fn rebuild_import_keeps_durable_events_missing_from_trace() { let conn = make_conn(); - let accepted = Event::new(RunId::new(), "memory.accepted", json!({ - "memory_id":"kept", "text":"durable but not in trace", "scope":"project", "kind":"fact" - })); + let accepted = Event::new( + RunId::new(), + "memory.accepted", + json!({ + "memory_id":"kept", "text":"durable but not in trace", "scope":"project", "kind":"fact" + }), + ); apply_events(&conn, &[accepted]).unwrap(); super::rebuild(&conn, &[]).unwrap(); - assert_eq!(conn.query_row("SELECT count(*) FROM memories WHERE memory_id='kept'", [], |r|r.get::<_,i64>(0)).unwrap(), 1); + assert_eq!( + conn.query_row( + "SELECT count(*) FROM memories WHERE memory_id='kept'", + [], + |r| r.get::<_, i64>(0) + ) + .unwrap(), + 1 + ); } #[test] fn trace_import_binds_historical_exposure_before_later_correction() { let conn = make_conn(); - let accepted = Event::new(RunId::new(), "memory.accepted", json!({ - "memory_id":"m", "text":"old claim", "scope":"project", "kind":"fact" - })); + let accepted = Event::new( + RunId::new(), + "memory.accepted", + json!({ + "memory_id":"m", "text":"old claim", "scope":"project", "kind":"fact" + }), + ); apply_events(&conn, std::slice::from_ref(&accepted)).unwrap(); let original_revision = super::claim_revision_at(&conn, "m", None).unwrap(); - let exposure = Event::new(RunId::new(), "context.injected", json!({"memory_ids":["m"]})); - let corrected = Event::new(RunId::new(), "memory.corrected", json!({"memory_id":"m", "text":"new claim"})); + let exposure = Event::new( + RunId::new(), + "context.injected", + json!({"memory_ids":["m"]}), + ); + let corrected = Event::new( + RunId::new(), + "memory.corrected", + json!({"memory_id":"m", "text":"new claim"}), + ); apply_events(&conn, &[corrected]).unwrap(); super::rebuild(&conn, std::slice::from_ref(&exposure)).unwrap(); for _ in 0..2 { @@ -1506,16 +1577,34 @@ mod tests { #[test] fn trace_import_replays_missing_correction_before_later_invalidation() { let conn = make_conn(); - let accepted = Event::new(RunId::new(), "memory.accepted", json!({ - "memory_id":"m", "text":"old claim", "scope":"project", "kind":"fact" - })); + let accepted = Event::new( + RunId::new(), + "memory.accepted", + json!({ + "memory_id":"m", "text":"old claim", "scope":"project", "kind":"fact" + }), + ); apply_events(&conn, &[accepted]).unwrap(); - let correction = Event::new(RunId::new(), "memory.corrected", json!({"memory_id":"m", "text":"historically corrected"})); - let invalidated = Event::new(RunId::new(), "memory.invalidated", json!({"memory_id":"m", "reason":"retired"})); + let correction = Event::new( + RunId::new(), + "memory.corrected", + json!({"memory_id":"m", "text":"historically corrected"}), + ); + let invalidated = Event::new( + RunId::new(), + "memory.invalidated", + json!({"memory_id":"m", "reason":"retired"}), + ); apply_events(&conn, &[invalidated]).unwrap(); super::rebuild(&conn, &[correction]).unwrap(); for _ in 0..2 { - let row: (String, bool) = conn.query_row("SELECT text,invalidated_at IS NOT NULL FROM memories WHERE memory_id='m'", [], |r|Ok((r.get(0)?,r.get(1)?))).unwrap(); + let row: (String, bool) = conn + .query_row( + "SELECT text,invalidated_at IS NOT NULL FROM memories WHERE memory_id='m'", + [], + |r| Ok((r.get(0)?, r.get(1)?)), + ) + .unwrap(); assert_eq!(row, ("historically corrected".into(), true)); rebuild_in_place(&conn).unwrap(); } @@ -1527,7 +1616,15 @@ mod tests { conn.execute("INSERT INTO memories(memory_id,scope,kind,text,normalized_text,confidence,provenance_snapshot_json,created_at) VALUES ('legacy','global_user','fact','original','original',0.7,'{\"source\":\"user_brain\"}','2020-01-01T00:00:00Z')", []).unwrap(); let error = rebuild_in_place(&conn).unwrap_err(); assert!(error.to_string().contains("absent from replay")); - assert_eq!(conn.query_row("SELECT text FROM memories WHERE memory_id='legacy'", [], |r|r.get::<_,String>(0)).unwrap(), "original"); + assert_eq!( + conn.query_row( + "SELECT text FROM memories WHERE memory_id='legacy'", + [], + |r| r.get::<_, String>(0) + ) + .unwrap(), + "original" + ); assert!(super::rebuild(&conn, &[]).is_err()); } @@ -1549,28 +1646,44 @@ mod tests { let rebuilding = Connection::open(&path).unwrap(); rebuilding.busy_handler(Some(busy)).unwrap(); writer.execute_batch("BEGIN IMMEDIATE").unwrap(); - let event = Event::new(RunId::new(), "memory.accepted", json!({ - "memory_id":"concurrent", "text":"committed while rebuild waits", "scope":"project", "kind":"fact" - })); + let event = Event::new( + RunId::new(), + "memory.accepted", + json!({ + "memory_id":"concurrent", "text":"committed while rebuild waits", "scope":"project", "kind":"fact" + }), + ); super::apply_event(&writer, &event).unwrap(); - let worker = std::thread::spawn(move || rebuild_in_place(&rebuilding).map_err(|e|e.to_string())); + let worker = + std::thread::spawn(move || rebuild_in_place(&rebuilding).map_err(|e| e.to_string())); let deadline = std::time::Instant::now() + std::time::Duration::from_secs(5); while !WAITING.load(Ordering::SeqCst) && std::time::Instant::now() < deadline { std::thread::sleep(std::time::Duration::from_millis(1)); } let was_waiting = WAITING.load(Ordering::SeqCst); writer.execute_batch("COMMIT").unwrap(); - assert!(was_waiting, "rebuild did not reach the contested write lock"); + assert!( + was_waiting, + "rebuild did not reach the contested write lock" + ); assert_eq!(worker.join().unwrap().unwrap(), 1); - assert_eq!(writer.query_row("SELECT count(*) FROM memories WHERE memory_id='concurrent'", [], |r|r.get::<_,i64>(0)).unwrap(), 1); + assert_eq!( + writer + .query_row( + "SELECT count(*) FROM memories WHERE memory_id='concurrent'", + [], + |r| r.get::<_, i64>(0) + ) + .unwrap(), + 1 + ); } fn make_event(run_id: RunId, kind: &str, payload: serde_json::Value) -> Event { Event::new(run_id, kind, payload) } - /// The nil-ULID sentinel run id: a STANDALONE `memory.cited` (this run id) - /// applies a real outcome delta (+use_count) via `apply_cited_outcome`. + /// Legacy nil run identity used by manual regression fixtures. fn sentinel_run() -> RunId { RunId(ulid::Ulid::nil()) } @@ -1582,7 +1695,7 @@ mod tests { // write path under real contention. // ------------------------------------------------------------------ #[test] - fn concurrent_cites_lose_no_updates() { + fn concurrent_manual_regrets_lose_no_updates() { use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::{Arc, Barrier}; @@ -1627,12 +1740,12 @@ mod tests { for _ in 0..CITES_PER_THREAD { let cited = Event::new( sentinel_run(), - "memory.cited", - json!({ "memory_id": mem_id, "turn": 0 }), + "retrieval.regret", + json!({ "memory_id": mem_id, "source": "manual" }), ); // Must not error under contention (busy-retry + IMMEDIATE). apply_events(&conn, std::slice::from_ref(&cited)) - .expect("concurrent cite must succeed"); + .expect("concurrent explicit regret must succeed"); } })); } @@ -1645,7 +1758,7 @@ mod tests { let conn = Connection::open(&db_path).expect("open verify"); schema::initialize(&conn).expect("init verify"); - // No lost increments: every concurrent cite landed. + // No lost increments: every concurrent explicit regret landed. let use_count: i64 = conn .query_row( "SELECT use_count FROM memories WHERE memory_id = ?1", @@ -3042,51 +3155,64 @@ enum ClaimExposure { Bound(String), } -/// Legacy injections identify IDs only. Attribute an in-flight run to its -/// earliest exposure rather than silently transferring old evidence on edit. -/// For legacy unbound events, equal-time corrections are conservatively treated -/// as later than the exposure; new bound events have exact revision identity. +fn exact_claim_exposure( + conn: &Connection, + exposure_id: &str, + run_id: &str, + memory_id: &str, +) -> KimetsuResult { + let payload:Option=conn.query_row("SELECT payload_json FROM events e WHERE event_id=?1 AND run_id=?2 AND kind='context.injected' AND EXISTS(SELECT 1 FROM json_each(e.payload_json,'$.memory_ids') WHERE value=?3)",params![exposure_id,run_id,memory_id],|r|r.get(0)).optional()?; + let Some(payload) = payload else { + return Ok(ClaimExposure::Unbound); + }; + let payload: serde_json::Value = serde_json::from_str(&payload)?; + Ok( + match payload["memory_revisions"][memory_id] + .as_str() + .filter(|r| !r.is_empty()) + { + Some(r) => ClaimExposure::Bound(r.to_string()), + None => ClaimExposure::Unbound, + }, + ) +} + +/// A run can deliver several revisions. Ambiguous mixed-claim runs never +/// transfer outcome credit onto whichever claim happens to be current. fn run_claim_revision( conn: &Connection, run_id: &str, memory_id: &str, ) -> KimetsuResult { - let exposure = conn - .query_row( - "SELECT ts,payload_json FROM events e WHERE run_id=?1 AND kind='context.injected' - AND EXISTS(SELECT 1 FROM json_each(e.payload_json,'$.memory_ids') WHERE value=?2) - ORDER BY julianday(ts),rowid LIMIT 1", - params![run_id, memory_id], - |r| Ok((r.get::<_, String>(0)?, r.get::<_, String>(1)?)), - ) - .optional()?; - match exposure { - None => Ok(ClaimExposure::Absent), - Some((at, payload)) => { - let payload: serde_json::Value = serde_json::from_str(&payload)?; - if let Some(bindings) = payload.get("memory_revisions") { - // Presence (even malformed/empty/partial) means the producer - // supplied an authoritative hydration map. Never invent a later - // claim identity for an omitted or ambiguous entry. - Ok( - match bindings - .get(memory_id) - .and_then(|v| v.as_str()) - .filter(|r| !r.is_empty()) - { - Some(revision) => ClaimExposure::Bound(revision.to_string()), - None => ClaimExposure::Unbound, - }, - ) - } else { - Ok(ClaimExposure::Bound(claim_revision_at( - conn, - memory_id, - Some(&at), - )?)) - } - } + let mut stmt=conn.prepare("SELECT ts,payload_json FROM events e WHERE run_id=?1 AND kind='context.injected' AND EXISTS(SELECT 1 FROM json_each(e.payload_json,'$.memory_ids') WHERE value=?2) ORDER BY julianday(ts),rowid")?; + let rows = stmt + .query_map(params![run_id, memory_id], |r| { + Ok((r.get::<_, String>(0)?, r.get::<_, String>(1)?)) + })? + .collect::, _>>()?; + let mut bound: Option = None; + for (at, payload) in rows { + let payload: serde_json::Value = serde_json::from_str(&payload)?; + let revision = if let Some(map) = payload.get("memory_revisions") { + let Some(r) = map + .get(memory_id) + .and_then(|v| v.as_str()) + .filter(|r| !r.is_empty()) + else { + return Ok(ClaimExposure::Unbound); + }; + r.to_string() + } else { + claim_revision_at(conn, memory_id, Some(&at))? + }; + if bound.as_ref().is_some_and(|r| r != &revision) { + return Ok(ClaimExposure::Unbound); + }; + bound = Some(revision); } + Ok(bound + .map(ClaimExposure::Bound) + .unwrap_or(ClaimExposure::Absent)) } /// Bind exposures while their event is first persisted, not when a delayed diff --git a/crates/kimetsu-brain/src/reinforce.rs b/crates/kimetsu-brain/src/reinforce.rs index d075172..97e7f3d 100644 --- a/crates/kimetsu-brain/src/reinforce.rs +++ b/crates/kimetsu-brain/src/reinforce.rs @@ -57,43 +57,15 @@ pub struct ReinforceSummary { pub routes_embedded: usize, } -/// Close the benchmark learning loop for one graded task (v2.5.2): when a -/// task PASSES, the memories most relevant to it get a grouped, query-linked -/// citation — the exact signal consolidation consumes (usefulness +1.0 each, -/// query-routes from task -> those memories, and staples from their -/// co-citation). Driven host-side by the benchmark harness after grading, so -/// the learning signal never depends on the in-container agent calling any -/// tool. Failures produce no citation (the retrieved memories are not -/// necessarily to blame). Returns how many memories were credited. -/// -/// Why this exists: the MCP `kimetsu_brain_cite` tool routes through the -/// SINGLETON `record_mcp_citation` path (one id, no query, fresh run) which -/// feeds none of the three consumers. This routes through the grouped -/// `record_citations` path, which feeds all three. +/// Legacy helper has no exposure identity and therefore cannot credit memories. +/// Use `feedback::record_exposure_outcome` with a delivered exposure ID. pub fn credit_benchmark_outcome( - start: &Path, - task: &str, - passed: bool, - top_k: usize, + _start: &Path, + _task: &str, + _passed: bool, + _top_k: usize, ) -> KimetsuResult { - if !passed { - return Ok(0); - } - // Retrieve the memories most relevant to this task, then credit the top - // few as "in context when the task was solved". search_memories ranks by - // BM25 over the task text across project + user brains. - let hits = crate::project::search_memories(start, task, top_k.max(1) as u32, 0, None, None)?; - let ids: Vec = hits.into_iter().take(top_k).map(|h| h.memory_id).collect(); - if ids.is_empty() { - return Ok(0); - } - crate::project::record_citations( - start, - &ids, - Some("benchmark: in context when task passed"), - Some(task), - )?; - Ok(ids.len()) + Ok(0) } /// Run the offline consolidation pass: staple qualifying co-citations and/or diff --git a/crates/kimetsu-brain/src/roi.rs b/crates/kimetsu-brain/src/roi.rs index 27bc015..ae375be 100644 --- a/crates/kimetsu-brain/src/roi.rs +++ b/crates/kimetsu-brain/src/roi.rs @@ -4,14 +4,11 @@ //! by surfacing relevant knowledge before a coding session, so the model //! didn't have to (re-)discover it through expensive exploration. //! -//! # Design philosophy: deliberate under-claiming +//! # Assumption-based estimates //! -//! Every constant in [`SAVED_TOKENS_PER_CITATION`] is a *conservative* -//! lower-bound estimate of the avoided exploration cost for that memory -//! kind. We never inflate the numbers: the goal is that a user who sees -//! a "net positive" result can trust it. The methodology document at -//! explains the calibration approach and the -//! Terminal-Bench sanity anchor. +//! Savings constants are nominal assumptions, not measured counterfactuals, +//! calibrated guarantees, or lower bounds. Delivered cost observations retain +//! their producer units separately; byte bounds are not model token counts. use kimetsu_core::{KimetsuResult, memory::MemoryKind}; use rusqlite::{OptionalExtension, params}; @@ -21,10 +18,10 @@ use serde::Serialize; // S2.4(b): Output-token accounting // --------------------------------------------------------------------------- -/// Conservative ratio of output tokens to input tokens for a typical coding -/// assistant response. Calibration: real Claude Code sessions show ~30–40 % -/// of the context going to output. We use 0.25 as a deliberate under-claim -/// to match the project's "never inflate" policy. +/// Assumed ratio of output tokens to input tokens for a typical coding +/// assistant response. This ratio has not been calibrated against sessions. +/// We use 0.25 as a nominal assumption +/// and expose it in the public report. /// /// **Audited limitation**: this is a ratio-based *estimate* because Claude Code /// does not expose per-session output token counts to the Stop hook. The @@ -48,35 +45,35 @@ pub fn estimate_output_tokens(input_tokens: u64) -> u64 { /// Conservative token savings per `digest_served` event. /// -/// Calibration: a digest saves the model from re-reading the CLAUDE.md + +/// Assumption: a digest saves the model from re-reading the CLAUDE.md + /// searching for the top conventions at session start. Estimated equivalent: /// ~2 search calls × 600 tokens/call = ~1 200 tokens. We claim 800 as a -/// conservative lower bound. +/// nominal assumption. pub const SAVED_TOKENS_PER_DIGEST_SERVED: u64 = 800; /// Conservative token savings per `resume_served` event. /// -/// Calibration: an episodic resume avoids the model asking "what were you +/// Assumption: an episodic resume avoids the model asking "what were you /// working on?" + 1–2 file reads to reconstruct context. Estimated /// equivalent: ~2 tool calls × 400 tokens/call = ~800 tokens. We claim 500. pub const SAVED_TOKENS_PER_RESUME_SERVED: u64 = 500; /// Conservative token savings per `skill.served` event (future-proof). /// -/// Calibration: a synthesized skill file avoids the model re-deriving the +/// Assumption: a synthesized skill file avoids the model re-deriving the /// composite procedure from individual memories. We claim 300 as a -/// conservative lower bound. +/// nominal assumption. pub const SAVED_TOKENS_PER_SKILL_SERVED: u64 = 300; // --------------------------------------------------------------------------- -// Per-kind calibrated constants +// Per-kind nominal assumptions // --------------------------------------------------------------------------- -/// Conservative lower-bound estimate of tokens saved per citation, by memory +/// Assumed estimate of tokens saved per citation, by memory /// kind. These are deliberate *under*-estimates of the exploration cost the /// model would have incurred without the brain context. /// -/// Calibration methodology (see for details): +/// Assumed methodology (see for details): /// - `failure_pattern`: avoids the "try → fail → diagnose → fix" loop. /// Typical loop: ~3 tool calls × ~500 tokens/call = ~1 500 tokens. /// - `command`: avoids a web/docs lookup or `--help` trial. ~1–2 tool @@ -160,7 +157,7 @@ pub fn resolve_price_per_mtok(model: &str, price_override: Option) -> Optio /// memory kind was cited in the window. The function is intentionally pure /// (no I/O) so it can be unit-tested without a DB. /// -/// The result is a conservative lower-bound: if a kind has no entry in +/// The result is a assumption-based estimate: if a kind has no entry in /// [`SAVED_TOKENS_PER_CITATION`] it contributes 0 (fail-safe). pub fn estimate_savings(citations: &[(MemoryKind, u32)]) -> u64 { citations @@ -196,6 +193,11 @@ pub struct RoiUsd { /// Full ROI report for a time window. #[derive(Debug, Clone, Serialize)] pub struct RoiReport { + pub estimate_label: &'static str, + pub model: String, + pub assumptions: serde_json::Value, + /// Observed producer costs grouped by units. Not summed as measured tokens. + pub delivered_cost_by_unit: std::collections::BTreeMap, /// Window length in days, or `None` for "all time". pub window_days: Option, /// Total tokens injected by the brain (sum of `used_tokens` from @@ -218,7 +220,7 @@ pub struct RoiReport { /// Total citation count (rows in `memory_citations` for runs in the /// window). pub citations: u64, - /// Estimated tokens saved (conservative lower-bound). + /// Estimated tokens saved (assumption-based estimate). pub estimated_saved_tokens: u64, /// `estimated_saved_tokens − injected_tokens`. Can be negative. pub net_tokens: i64, @@ -447,6 +449,7 @@ pub fn roi_report( + resume_served_events * SAVED_TOKENS_PER_RESUME_SERVED; // --- injected_tokens (sum of used_tokens across context.injected events) --- + let mut delivered_cost_by_unit = std::collections::BTreeMap::::new(); let injected_tokens: u64 = { let payloads: Vec = match &window_since { Some(ts) => { @@ -467,6 +470,14 @@ pub fn roi_report( for p in &payloads { let v: serde_json::Value = serde_json::from_str(p)?; if let Some(t) = v.get("used_tokens").and_then(|x| x.as_u64()) { + *delivered_cost_by_unit + .entry( + v.get("cost_unit") + .and_then(|x| x.as_str()) + .unwrap_or("legacy_token_estimate") + .to_string(), + ) + .or_default() += t; sum += t; } } @@ -562,6 +573,10 @@ pub fn roi_report( }); Ok(RoiReport { + estimate_label: "Assumption-based estimate; savings are not measured or guaranteed", + model: model_name.to_string(), + assumptions: serde_json::json!({"tokens_per_citation":SAVED_TOKENS_PER_CITATION.iter().map(|(kind,n)|(kind.to_string(),*n)).collect::>(),"digest":SAVED_TOKENS_PER_DIGEST_SERVED,"resume":SAVED_TOKENS_PER_RESUME_SERVED,"output_input_ratio":OUTPUT_TOKEN_INPUT_RATIO,"price_per_mtok":price,"overhead":"legacy estimate combines producer costs; see delivered_cost_by_unit for observed units"}), + delivered_cost_by_unit, window_days: window.days(), injected_tokens, estimated_output_tokens, @@ -675,21 +690,21 @@ pub struct SessionRoi { impl SessionRoi { /// Build a one-line savings sentence for the Stop hook `systemMessage`. /// Returns a human-readable string like: - /// "[Kimetsu] Brain saved ~1 200 tokens (~$0.004) this session." + /// "[Kimetsu] Estimated savings (nominal assumptions): ~1 200 tokens (~$0.004) this session." pub fn savings_sentence(&self) -> String { match &self.usd { Some(u) if u.net >= 0.0 => format!( - "[Kimetsu] Brain saved ~{} tokens (~${:.4}) this session.", + "[Kimetsu] Estimated savings (nominal assumptions): ~{} tokens (~${:.4}) this session.", format_tokens(self.estimated_saved_tokens), u.saved, ), Some(u) => format!( - "[Kimetsu] Brain used ~{} tokens (net −${:.4}) this session.", + "[Kimetsu] Estimated overhead (nominal assumptions): ~{} tokens (net −${:.4}) this session.", format_tokens(self.injected_tokens), u.spent - u.saved, ), None => format!( - "[Kimetsu] Brain saved ~{} tokens this session.", + "[Kimetsu] Estimated savings (nominal assumptions): ~{} tokens this session.", format_tokens(self.estimated_saved_tokens), ), } diff --git a/crates/kimetsu-brain/src/schema.rs b/crates/kimetsu-brain/src/schema.rs index d60fa1a..aee6449 100644 --- a/crates/kimetsu-brain/src/schema.rs +++ b/crates/kimetsu-brain/src/schema.rs @@ -1228,3 +1228,11 @@ pub fn migrate_v12_to_v13(conn: &Connection) -> KimetsuResult<()> { add_column_if_missing(conn, "memory_proposals", "valid_to TEXT")?; Ok(()) } + +/// Optional stable task identity. Empty string retains the original legacy lane. +pub(crate) fn migrate_v13_to_v14(conn: &Connection) -> KimetsuResult<()> { + crate::episode::create_work_episodes_table(conn)?; + add_column_if_missing(conn, "work_episodes", "identity TEXT NOT NULL DEFAULT ''")?; + conn.execute_batch("CREATE INDEX IF NOT EXISTS idx_episodes_identity ON work_episodes(repo_root, identity, superseded_by)")?; + Ok(()) +} diff --git a/crates/kimetsu-brain/src/trust.rs b/crates/kimetsu-brain/src/trust.rs index ee38575..e0fa2b3 100644 --- a/crates/kimetsu-brain/src/trust.rs +++ b/crates/kimetsu-brain/src/trust.rs @@ -22,7 +22,7 @@ //! //! It scores *origin*, and nothing else. A [`Provenance`] read off the memory's //! stored snapshot maps to a [`trust_multiplier`] the broker folds into the -//! composite score, so a corroborated local lesson outranks an anonymous +//! composite score, so a local lesson outranks an anonymous //! imported one at equal relevance. //! //! Two deliberate limits: @@ -30,11 +30,8 @@ //! * **It never blocks retrieval.** Trust is a weight, not a gate. A hard gate //! on provenance would make a bad pack import silently delete a user's //! working knowledge, which is a worse failure than the one it prevents. -//! * **Corroboration outranks origin.** A memory that has been cited in a -//! successful local run has been *tested here*, whatever its origin, and -//! carries no penalty at all from then on. Otherwise an imported pack — the -//! whole point of which is to share knowledge — would stay second-class -//! forever. +//! * **Reliance is not verification.** Citations and successful-run association +//! never remove an origin penalty. No explicit verification channel exists. //! //! ## Not done here //! @@ -116,24 +113,9 @@ impl Provenance { /// Multiplier applied to a candidate's composite score. /// -/// `corroborated` means the memory has been cited in a *successful* run on this -/// machine — precisely what `memories.last_useful_at` records, and the reason -/// the signal is a boolean rather than a count: `last_useful_at` is already -/// selected by every candidate query, so reading it costs nothing, whereas -/// counting citations would put a per-row aggregate on the hot path. -/// -/// A corroborated memory carries no origin penalty at all, whatever its -/// provenance. It has been tested here; where it was written stops being the -/// most informative thing about it. Without that, an imported pack — the entire -/// point of which is to share knowledge — would stay second-class forever. -/// -/// Bounded in `(0, 1]` — trust can only ever hold a memory back, never promote -/// one above its relevance. Promotion is what `usefulness_score` is for, and -/// two mechanisms that both boost would make the composite unreadable. -pub fn trust_multiplier(provenance: Provenance, corroborated: bool) -> f32 { - if corroborated { - return 1.0; - } +/// The second argument is retained for API compatibility and represents observed +/// successful-run association. It does not verify the proposition. +pub fn trust_multiplier(provenance: Provenance, _associated: bool) -> f32 { match provenance { Provenance::Local | Provenance::Derived => 1.0, Provenance::Distilled => 0.95, @@ -149,9 +131,9 @@ pub fn trust_multiplier(provenance: Provenance, corroborated: bool) -> f32 { pub struct ProvenanceGroup { pub provenance: String, pub total: usize, - /// Cited in a successful run here at least once. - pub corroborated: usize, - /// Never corroborated *and* from an external origin: the population a + /// Observed successful-run association; not verification. + pub associated: usize, + /// From an external origin without explicit verification: the population a /// poisoned memory would be hiding in. pub unvetted: usize, } @@ -225,11 +207,11 @@ pub fn audit(conn: &rusqlite::Connection) -> kimetsu_core::KimetsuResult= Provenance::Derived { 0 // local and derived memories have no external origin to vet } else { - total - corroborated + total }, }) .collect(); @@ -296,25 +278,14 @@ mod tests { } } - /// A shared pack is the whole point of packs. A memory that has proven - /// itself locally must stop being treated as an outsider. #[test] - fn corroboration_erases_the_origin_penalty() { - let cold = trust_multiplier(Provenance::Pack, false); - let proven = trust_multiplier(Provenance::Pack, true); - assert!(cold < 1.0, "an uncorroborated pack memory is discounted"); - assert!( - (proven - 1.0).abs() < 1e-6, - "once cited in a successful run here, origin stops mattering: {proven}" - ); - for provenance in [ - Provenance::Pack, - Provenance::Remote, - Provenance::Distilled, - Provenance::Derived, - Provenance::Local, - ] { - assert_eq!(trust_multiplier(provenance, true), 1.0, "{provenance:?}"); + fn hardening_citation_retains_origin_penalty() { + for provenance in [Provenance::Pack, Provenance::Remote, Provenance::Distilled] { + assert_eq!( + trust_multiplier(provenance, true), + trust_multiplier(provenance, false) + ); + assert!(trust_multiplier(provenance, true) < 1.0); } } @@ -363,8 +334,7 @@ mod tests { .expect("insert"); } - /// The population a poisoned memory hides in: external origin, never - /// corroborated. Local memories are not "unvetted" — there is no external + /// External memories remain unvetted even after observed association. Local memories are not "unvetted" — there is no external /// origin to vet. #[test] fn audit_counts_the_unvetted_external_population() { @@ -399,12 +369,8 @@ mod tests { }; assert_eq!(group("local").unvetted, 0, "nothing external to vet"); assert_eq!(group("pack").total, 2); - assert_eq!(group("pack").corroborated, 1); - assert_eq!( - group("pack").unvetted, - 1, - "the corroborated one has been tested here" - ); + assert_eq!(group("pack").associated, 1); + assert_eq!(group("pack").unvetted, 2, "association is not verification"); assert_eq!(group("distilled").unvetted, 1); } diff --git a/crates/kimetsu-chat/src/ask.rs b/crates/kimetsu-chat/src/ask.rs index 25899f3..5a4c6b2 100644 --- a/crates/kimetsu-chat/src/ask.rs +++ b/crates/kimetsu-chat/src/ask.rs @@ -35,6 +35,8 @@ use kimetsu_core::paths::{ProjectPaths, user_brain_enabled, user_kimetsu_dir}; /// Stable JSON-serialisable output from the composer (3.1 + 3.2). #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct AskAnswer { + #[serde(default)] + pub exposure_id: Option, /// The composed (or verbatim) answer text. pub answer: String, /// Memory / file citation ids (`memory:` or `file:`). @@ -127,6 +129,7 @@ pub fn compose_answer(workspace: &Path, question: &str) -> AskAnswer { Ok(b) => b, Err(err) => { return AskAnswer { + exposure_id: None, answer: format!( "Brain unavailable for this workspace: {err}. \ Is the project initialized (`kimetsu init`)?" @@ -142,6 +145,7 @@ pub fn compose_answer(workspace: &Path, question: &str) -> AskAnswer { // ── Grounded-only refusal ───────────────────────────────────────────────── if bundle.skipped || bundle.capsules.is_empty() { return AskAnswer { + exposure_id: None, answer: "Nothing in project memory answers that.".to_string(), citations: Vec::new(), grounded: false, @@ -161,10 +165,18 @@ pub fn compose_answer(workspace: &Path, question: &str) -> AskAnswer { .collect(); // ── DP-B: resolve cheap model (local preferred) ─────────────────────────── - match resolve_ask_provider(workspace) { + let mut exposure_id = None; + let mut answer = match resolve_ask_provider(workspace) { Some((mut provider, model_id)) => { - match call_composer(&capsules, question, provider.as_mut()) { + match call_composer( + workspace, + &capsules, + question, + provider.as_mut(), + &mut exposure_id, + ) { Some(answer) => AskAnswer { + exposure_id: None, answer, citations, grounded: true, @@ -175,7 +187,32 @@ pub fn compose_answer(workspace: &Path, question: &str) -> AskAnswer { } } None => verbatim_answer(&capsules, &citations), - } + }; + answer.exposure_id = exposure_id + .or_else(|| record_ask_exposure(workspace, &capsules, answer.answer.len(), "ask_verbatim")); + answer +} + +fn record_ask_exposure( + workspace: &Path, + capsules: &[ContextCapsule], + rendered_bytes: usize, + surface: &str, +) -> Option { + let mut payload = kimetsu_brain::context::delivery::injected_payload( + capsules, + rendered_bytes.min(u32::MAX as usize) as u32, + ); + payload["surface"] = serde_json::json!(surface); + payload["cost_unit"] = serde_json::json!("rendered_utf8_bytes"); + let exposure = kimetsu_core::event::Event::new( + kimetsu_core::ids::RunId::new(), + "context.injected", + payload, + ); + project::record_context_exposure(workspace, &exposure) + .ok() + .map(|_| exposure.event_id.to_string()) } /// Record a citation for each memory handle in `citation_handles`, wiring @@ -190,6 +227,24 @@ pub fn record_helpful_mark(workspace: &Path, citation_handles: &[String]) { } } +/// Bind an explicit helpful mark to the claims in the original answer. +pub fn record_helpful_mark_scoped( + workspace: &Path, + exposure_id: &str, + citation_handles: &[String], +) { + for handle in citation_handles { + if let Some(id) = handle.strip_prefix("memory:") { + let _ = project::record_exposure_citation( + workspace, + exposure_id, + id, + Some("marked helpful via ask"), + ); + } + } +} + // ── Internal helpers ────────────────────────────────────────────────────────── /// Build a verbatim answer from capsule texts when no model is available. @@ -211,6 +266,7 @@ fn verbatim_answer(capsules: &[ContextCapsule], citations: &[String]) -> AskAnsw ) }; AskAnswer { + exposure_id: None, answer, citations: citations.to_vec(), grounded: true, @@ -221,9 +277,11 @@ fn verbatim_answer(capsules: &[ContextCapsule], citations: &[String]) -> AskAnsw /// Call the composer model and return the answer text, or `None` on failure. fn call_composer( + workspace: &Path, capsules: &[ContextCapsule], question: &str, provider: &mut dyn ModelProvider, + exposure_id: &mut Option, ) -> Option { let context_block = capsules .iter() @@ -268,6 +326,12 @@ fn call_composer( metadata: serde_json::Value::Null, }; + *exposure_id = record_ask_exposure( + workspace, + capsules, + user_msg.len() + system.len(), + "ask_composer", + ); let response = provider.complete(request).ok()?; let text = response.text?.trim().to_string(); if text.is_empty() { None } else { Some(text) } diff --git a/crates/kimetsu-chat/src/mcp_server.rs b/crates/kimetsu-chat/src/mcp_server.rs index 7852da2..af7136c 100644 --- a/crates/kimetsu-chat/src/mcp_server.rs +++ b/crates/kimetsu-chat/src/mcp_server.rs @@ -62,7 +62,7 @@ const BRIDGE_SYNC_DESCRIPTION: &str = "Bulk-import all discovered non-Kimetsu sk const PLUGIN_INSTALL_DESCRIPTION: &str = "Install Kimetsu MCP/plugin wiring for a target harness in this workspace. For codex, writes .codex/config.toml, .codex/hooks.json, the kimetsu-bridge skill, and the kimetsu-memory-harvester custom agent; for claude-code, writes .mcp.json, command docs, and .claude/settings.json hooks. Set mode=optional to recommend brain-first usage, or mode=required to tell the host harness that non-trivial work must load Kimetsu brain context. Installed guidance tells benchmark agents to prefer kimetsu_benchmark_context and record outcomes through kimetsu_benchmark_record_outcome. Set scope=workspace (default) to install into this workspace, or scope=global to install into the user's home (~/.claude, ~/.claude.json, ~/.codex) for all sessions. Existing user hooks are preserved (merged, not replaced)."; -const BRAIN_CITE_DESCRIPTION: &str = "Call when a retrieved Kimetsu memory materially helped you solve the current task. This records a ground-truth citation that powers Kimetsu's self-tuning: the brain learns which memories actually earn their keep. ROI: each citation trains the retrieval objective so future queries surface that memory sooner. Pass memory_id (from the capsule's provenance or kimetsu_brain_memory_list) and an optional note describing how it helped."; +const BRAIN_CITE_DESCRIPTION: &str = "Record reliance on a memory actually delivered in context. Pass exposure_id from the context response and memory_id from its capsule handle. Reliance does not verify truth or prove success; origin penalties remain."; #[derive(Debug, Clone)] pub struct McpServeConfig { @@ -667,12 +667,18 @@ static WARM_START_SERVED: std::sync::atomic::AtomicBool = std::sync::atomic::Ato /// /// The latch is only set once a block actually exists, so a call made against a /// cold brain does not burn the session's one chance at a warm start. -fn take_session_warm_start(workspace: &Path) -> Option { +fn take_session_warm_start(workspace: &Path, arguments: &Value) -> Option { use std::sync::atomic::Ordering; if WARM_START_SERVED.load(Ordering::SeqCst) { return None; } - let block = kimetsu_brain::digest::warm_start_block(workspace)?; + let identity = arguments + .get("task_id") + .or_else(|| arguments.get("session_id")) + .or_else(|| arguments.get("worktree_id")) + .and_then(Value::as_str) + .unwrap_or(""); + let block = kimetsu_brain::digest::warm_start_block_scoped(workspace, identity)?; if WARM_START_SERVED.swap(true, Ordering::SeqCst) { return None; // lost the race — another call is already emitting it } @@ -684,7 +690,7 @@ fn kimetsu_brain_context(workspace: &Path, arguments: &Value) -> Value { workspace, arguments, None, - take_session_warm_start(workspace), + take_session_warm_start(workspace, arguments), ) .unwrap_or_else(|e| { bounded_context_error(arguments, 6000, brain_unavailable_json(workspace, &e)) @@ -701,6 +707,7 @@ fn record_context_delivery( arguments: &Value, delivery: &kimetsu_brain::context::delivery::Delivery, surface: &str, + mut exposure: kimetsu_core::event::Event, ) { if std::env::var("KIMETSU_BRAIN_LOG_RETRIEVAL").as_deref() == Ok("0") { return; @@ -711,7 +718,20 @@ fn record_context_delivery( ); payload["surface"] = json!(surface); payload["session_id"] = arguments.get("session_id").cloned().unwrap_or(Value::Null); - let _ = project::log_telemetry_event(workspace, "context.injected", payload); + if kimetsu_core::paths::ProjectPaths::discover(workspace) + .ok() + .and_then(|p| project::load_config(&p).ok()) + .is_some_and(|cfg| cfg.learning.store_queries) + { + if let Some(query) = arguments.get("query").or_else(|| arguments.get("task")) { + payload["query"] = query.clone(); + } + } + payload["task_id"] = arguments.get("task_id").cloned().unwrap_or(Value::Null); + payload["exposure_id"] = json!(exposure.event_id.to_string()); + payload["cost_unit"] = json!("serialized_utf8_byte_bound"); + exposure.payload = payload; + let _ = project::record_context_exposure(workspace, &exposure); } /// Candidate pool the remote reranker judges before truncating to the caller's @@ -869,11 +889,17 @@ fn brain_context_tool_with_warm( use kimetsu_brain::context::delivery::{ add_optional_field, compact_capsules, fit_json, }; + let exposure = kimetsu_core::event::Event::new( + kimetsu_core::ids::RunId::new(), + "context.injected", + json!({}), + ); let count = bundle.capsules.len(); let mut delivery = fit_json(bundle.capsules.clone(), budget_tokens, |capsules| { json!({ "ok": true, "skipped": capsules.is_empty(), + "exposure_id": exposure.event_id.to_string(), "capsule_count": capsules.len(), "excluded_count": bundle.excluded.len() + count - capsules.len(), "capsules": compact_capsules(capsules), @@ -888,7 +914,7 @@ fn brain_context_tool_with_warm( budget_tokens, ); } - record_context_delivery(workspace, arguments, &delivery, "brain_context"); + record_context_delivery(workspace, arguments, &delivery, "brain_context", exposure); Ok(delivery.payload) } Err(err) => Ok(bounded_context_error( @@ -1005,7 +1031,7 @@ fn kimetsu_brain_record(workspace: &Path, arguments: &Value) -> Value { } } -/// Record a ground-truth citation for a memory that materially helped. +/// Record reliance on a delivered memory claim. /// Writes a `memory.cited` event with the all-zero sentinel run_id so /// it links to no active run — this is the MCP path (primary Claude Code usage) /// where there is no agent run in progress. @@ -1019,12 +1045,17 @@ fn kimetsu_brain_cite(workspace: &Path, arguments: &Value) -> Result Ok(json!({ "ok": true, "memory_id": memory_id, "recorded": "memory.cited", - "usage": "Citation recorded. This closes the ground-truth loop and trains Kimetsu's self-tuning objective." + "usage": "Reliance recorded for the delivered claim; this is not verification." })), Err(err) => Ok(json!({ "ok": false, "error": err.to_string() })), } @@ -1137,6 +1168,11 @@ fn kimetsu_benchmark_context(workspace: &Path, arguments: &Value) -> Value { ) { Ok(context) => { use kimetsu_brain::context::delivery::{compact_capsules, fit_json}; + let exposure = kimetsu_core::event::Event::new( + kimetsu_core::ids::RunId::new(), + "context.injected", + json!({}), + ); let original_count = context.capsules.len(); let delivery = fit_json(context.capsules.clone(), budget_tokens, |capsules| { let memory_count = capsules.iter().filter(|c| c.kind == "memory").count(); @@ -1170,6 +1206,7 @@ fn kimetsu_benchmark_context(workspace: &Path, arguments: &Value) -> Value { } json!({ "ok": required_ok, "required_ok": required_ok, + "exposure_id": exposure.event_id.to_string(), "dataset": context.dataset, "task_slug": context.task_slug, "warm_policy": context.warm_policy.as_str(), "capsule_count": capsules.len(), "memory_capsule_count": memory_count, @@ -1180,7 +1217,13 @@ fn kimetsu_benchmark_context(workspace: &Path, arguments: &Value) -> Value { "excluded_count": context.excluded.len() + original_count - capsules.len(), }) }); - record_context_delivery(workspace, arguments, &delivery, "benchmark_context"); + record_context_delivery( + workspace, + arguments, + &delivery, + "benchmark_context", + exposure, + ); delivery.payload } Err(err) => bounded_context_error( @@ -1221,10 +1264,16 @@ fn kimetsu_benchmark_record_outcome(workspace: &Path, arguments: &Value) -> Resu duration_seconds: optional_f32_arg(arguments, "duration_seconds"), generalization, }; + let credited = match arguments.get("exposure_id").and_then(Value::as_str) { + Some(id) => project::record_exposure_outcome(workspace, id, outcome.passed) + .map_err(|e| e.to_string())?, + None => 0, + }; let recorded = project::record_benchmark_outcome(workspace, outcome) .map_err(|err| format!("kimetsu benchmark record outcome: {err}"))?; Ok(json!({ "ok": true, + "associated_delivered_memories": credited, "memory_id": recorded.memory_id, "task_slug": recorded.task_slug, "kind": recorded.kind.to_string(), @@ -1675,7 +1724,7 @@ fn kimetsu_brain_prune(workspace: &Path, arguments: &Value) -> Result Result { let question = match arguments.get("question").and_then(Value::as_str) { Some(q) if !q.trim().is_empty() => q.trim(), @@ -1692,23 +1741,38 @@ fn kimetsu_brain_answer(workspace: &Path, arguments: &Value) -> Result Value { "inputSchema": { "type": "object", "properties": { + "exposure_id": {"type":"string","description":"Exact exposure_id returned by context delivery."}, "memory_id": { "type": "string", "description": "The memory_id of the retrieved memory that helped (from capsule provenance or kimetsu_brain_memory_list)." }, "note": { "type": "string", "description": "Optional short description of how the memory helped." } }, - "required": ["memory_id"] + "required": ["memory_id", "exposure_id"] } }, { @@ -2008,6 +2073,7 @@ fn tool_definitions() -> Value { "inputSchema": { "type": "object", "properties": { + "exposure_id": {"type":"string","description":"Optional exact delivered exposure. Omission gives no automatic memory credit."}, "task": { "type": "string" }, "dataset": { "type": "string", "default": "terminal-bench/terminal-bench-2" }, "task_slug": { "type": "string" }, @@ -2125,7 +2191,7 @@ fn tool_definitions() -> Value { "memory_id": { "type": "string" }, "reason": { "type": "string" } }, - "required": ["memory_id"] + "required": ["memory_id", "exposure_id"] } }, { @@ -2321,8 +2387,10 @@ fn tool_definitions() -> Value { "inputSchema": { "type": "object", "properties": { + "exposure_id": {"type":"string","description":"Original answer exposure required for mark_helpful."}, + "memory_ids": {"type":"array","items":{"type":"string"},"description":"Original delivered memories relied upon."}, "question": { "type": "string", "description": "The question to answer from project memory (e.g. 'how do I run the tests?' or 'what does the broker do?')." }, - "mark_helpful": { "type": "boolean", "description": "When true, record a citation for every memory in the returned answer, closing the self-tuning ground-truth loop. Default false." } + "mark_helpful": { "type": "boolean", "description": "When true, record reliance on memory_ids from the original exposure_id; no new retrieval. Default false." } }, "required": ["question"] } @@ -2638,6 +2706,12 @@ mod tests { "ripgrep search files before broad reads", ) .unwrap(); + // Keep query terms discriminative when adding matching rejected rows; + // otherwise corpus-wide IDF abstention changes the retrieval question. + let (_, _, fixture_conn) = project::load_project(&root).unwrap(); + let ballast=(0..32).map(|i|kimetsu_core::event::Event::new(kimetsu_core::ids::RunId::new(),"memory.accepted",json!({"memory_id":format!("ballast-{i}"),"scope":"repo","kind":"fact","text":format!("unrelated ballast bananas {i}")}))).collect::>(); + kimetsu_brain::projector::apply_events(&fixture_conn, &ballast).unwrap(); + drop(fixture_conn); let args = json!({"query":"ripgrep search files", "budget_tokens":1200, "max_capsules":1,"min_score":0.0,"include_ambient":false}); let before = brain_context_tool(&root, &args, None).unwrap(); @@ -3243,8 +3317,8 @@ mod tests { cite["description"] .as_str() .unwrap_or("") - .contains("self-tuning"), - "description must mention self-tuning" + .contains("Reliance"), + "description must distinguish reliance from verification" ); } @@ -3273,9 +3347,15 @@ mod tests { unsafe { std::env::set_var("KIMETSU_MCP_ENABLE_WRITE_TOOLS", "1"); } + let context=brain_context_tool(&root,&json!({"query":"cite MCP test memory","include_ambient":false,"min_score":0.0,"budget_tokens":2500}),None).unwrap(); + assert!( + context["capsules"] + .as_array() + .is_some_and(|c| !c.is_empty()) + ); let result = call_tool( "kimetsu_brain_cite", - json!({ "memory_id": memory_id, "note": "it helped" }), + json!({ "memory_id": memory_id, "note": "it helped", "exposure_id":context["exposure_id"] }), &root, &SkillConfig::default(), ) diff --git a/crates/kimetsu-chat/src/repl.rs b/crates/kimetsu-chat/src/repl.rs index de26546..8f048a0 100644 --- a/crates/kimetsu-chat/src/repl.rs +++ b/crates/kimetsu-chat/src/repl.rs @@ -1082,9 +1082,6 @@ fn run_repl_inner( reasoning_effort.as_str() ); } - let brain_context = - build_chat_brain_context(brain_session.as_ref(), &transcript, &loaded_skills, &task); - if route == ChatRoute::TextOnly { if let Err(err) = hooks.run(HookEvent::PreTurn, &workspace, &session, Some(&user_line)) { @@ -1148,6 +1145,15 @@ fn run_repl_inner( continue; } + let mut delivered_capsules = Vec::new(); + let brain_context = build_chat_brain_context_with_delivery( + brain_session.as_ref(), + &transcript, + &loaded_skills, + &task, + &mut delivered_capsules, + ); + let mut turn_checkpoint = if route == ChatRoute::WorkspaceAgent { match create_checkpoint(&workspace, &transcript, "", "turn") { Ok(checkpoint) => Some(checkpoint), @@ -1194,6 +1200,24 @@ fn run_repl_inner( }; ui.write_thinking(&mut writer)?; writer.flush()?; + if !delivered_capsules.is_empty() { + let mut payload = kimetsu_brain::context::delivery::injected_payload( + &delivered_capsules, + brain_context + .as_ref() + .map(|c| c.len().min(u32::MAX as usize) as u32) + .unwrap_or(0), + ); + payload["session_id"] = serde_json::json!(session.id); + payload["surface"] = serde_json::json!("chat_repl"); + payload["cost_unit"] = serde_json::json!("rendered_utf8_bytes"); + let exposure = kimetsu_core::event::Event::new( + kimetsu_core::ids::RunId::new(), + "context.injected", + payload, + ); + let _ = brain_project::record_context_exposure(&workspace, &exposure); + } let result = run_model_agent( &task, &mut runtime, @@ -5542,11 +5566,28 @@ fn run_text_only_chat_with_system( /// The agent loop renders this whole string as a "Prior knowledge" /// block before the new user message. The model gets memory-pool /// retrieval AND conversational continuity through a single channel. +#[cfg(test)] fn build_chat_brain_context( brain_session: Option<&brain_project::BrainSession>, transcript: &[TurnRecord], loaded_skills: &[LoadedSkill], task: &str, +) -> Option { + build_chat_brain_context_with_delivery( + brain_session, + transcript, + loaded_skills, + task, + &mut Vec::new(), + ) +} + +fn build_chat_brain_context_with_delivery( + brain_session: Option<&brain_project::BrainSession>, + transcript: &[TurnRecord], + loaded_skills: &[LoadedSkill], + task: &str, + delivered: &mut Vec, ) -> Option { const TURN_CAP_BYTES: usize = 1200; const TRANSCRIPT_TAIL: usize = 10; @@ -5571,6 +5612,7 @@ fn build_chat_brain_context( bundle.used_tokens, bundle.budget_tokens, )); + delivered.extend(bundle.capsules.iter().cloned()); for (i, c) in bundle.capsules.iter().enumerate() { out.push_str(&format!( " [{}] {} (score {:.2}, scope_weight {:.2})\n {}\n", diff --git a/crates/kimetsu-cli/src/commands/brain.rs b/crates/kimetsu-cli/src/commands/brain.rs index d28f530..218f901 100644 --- a/crates/kimetsu-cli/src/commands/brain.rs +++ b/crates/kimetsu-cli/src/commands/brain.rs @@ -244,6 +244,24 @@ pub(crate) fn brain(command: BrainCommand) -> KimetsuResult<()> { BrainCommand::Reflect(args) => brain_reflect(args), BrainCommand::Triage(args) => brain_triage(args), BrainCommand::Forget(args) => brain_forget(args), + BrainCommand::Archives => { + println!( + "{}", + serde_json::to_string_pretty(&kimetsu_brain::lifecycle::list_archived( + &env::current_dir()? + )?)? + ); + Ok(()) + } + BrainCommand::Restore { memory_id } => { + let restored = + kimetsu_brain::lifecycle::restore_memory(&env::current_dir()?, &memory_id)?; + println!( + "{}", + serde_json::json!({"memory_id":memory_id,"restored":restored}) + ); + Ok(()) + } BrainCommand::Cite(args) => brain_cite(args), BrainCommand::Reinforce(args) => brain_reinforce(args), BrainCommand::BenchmarkCredit(args) => brain_benchmark_credit(args), @@ -542,7 +560,19 @@ pub(crate) fn brain_session_start_hook(workspace: &Path) -> KimetsuResult<()> { // below: a brain with nothing to say still needs its upkeep. spawn_maintenance_if_due(workspace); - let Some(additional_context) = warm_start_context(workspace) else { + let mut input = String::new(); + use std::io::Read; + let _ = std::io::stdin().read_to_string(&mut input); + let payload: serde_json::Value = serde_json::from_str(input.trim()).unwrap_or_default(); + let identity = payload + .get("task_id") + .or_else(|| payload.get("session_id")) + .or_else(|| payload.get("worktree_id")) + .and_then(|v| v.as_str()) + .unwrap_or(""); + let Some(additional_context) = + kimetsu_brain::digest::warm_start_block_scoped(workspace, identity) + else { return Ok(()); }; @@ -558,15 +588,6 @@ pub(crate) fn brain_session_start_hook(workspace: &Path) -> KimetsuResult<()> { Ok(()) } -/// Assemble the warm-start block: repo digest + episodic resume. -/// -/// Thin wrapper over [`kimetsu_brain::digest::warm_start_block`], which the -/// MCP server shares so Cursor — no hooks, no session-start surface — gets the -/// same block on its first `kimetsu_brain_context` call. -pub(crate) fn warm_start_context(workspace: &Path) -> Option { - kimetsu_brain::digest::warm_start_block(workspace) -} - /// Normalize a user-supplied time into RFC 3339. /// /// Accepts a bare `YYYY-MM-DD` because that is how people actually name a day, @@ -697,12 +718,12 @@ pub(crate) fn brain_audit(args: AuditArgs) -> KimetsuResult<()> { println!(); println!( "{:<12} {:>8} {:>14} {:>9}", - "origin", "total", "corroborated", "unvetted" + "origin", "total", "associated", "unvetted" ); for group in &report.groups { println!( "{:<12} {:>8} {:>14} {:>9}", - group.provenance, group.total, group.corroborated, group.unvetted + group.provenance, group.total, group.associated, group.unvetted ); } @@ -2336,7 +2357,12 @@ pub(crate) fn brain_roi(args: RoiArgs) -> KimetsuResult<()> { let entries = per_memory_roi(&conn, window, limit)?; if args.json { - println!("{}", serde_json::to_string_pretty(&entries)?); + println!( + "{}", + serde_json::to_string_pretty( + &serde_json::json!({"estimate_label":report.estimate_label,"model":report.model,"assumptions":report.assumptions,"memories":entries}) + )? + ); return Ok(()); } @@ -2344,13 +2370,13 @@ pub(crate) fn brain_roi(args: RoiArgs) -> KimetsuResult<()> { Some(d) => format!("last {d} days"), None => "all time".to_string(), }; - println!("── ROI Top Memories ({window_label}, top {limit}) ─────"); + println!("── Estimated ROI Top Memories ({window_label}, top {limit}) ─────"); if entries.is_empty() { println!(" No citations recorded yet."); } else { for (i, e) in entries.iter().enumerate() { println!( - " #{:>2} [{:>15}] cites={:>3} saved={:>6} tok {}", + " #{:>2} [{:>15}] cites={:>3} estimated_saved={:>6} tok {}", i + 1, e.kind, e.citation_count, @@ -2378,7 +2404,14 @@ pub(crate) fn brain_roi(args: RoiArgs) -> KimetsuResult<()> { Some(d) => format!("last {d} days"), None => "all time".to_string(), }; - println!("── ROI Ledger ({window_label}) ────────────────────────"); + println!("── Estimated ROI Ledger ({window_label}) ────────────────────────"); + println!(" {}", report.estimate_label); + println!(" model: {}", report.model); + println!(" assumptions: {}", report.assumptions); + println!( + " delivered cost by unit: {:?}", + report.delivered_cost_by_unit + ); println!(" served events: {}", report.served_events); // S2.4(c): show warm-start events. if report.digest_served_events > 0 || report.resume_served_events > 0 { @@ -2391,7 +2424,7 @@ pub(crate) fn brain_roi(args: RoiArgs) -> KimetsuResult<()> { } println!(" citations: {}", report.citations); println!( - " injected tokens: {}", + " overhead estimate: {}", format_token_count(report.injected_tokens) ); // S2.4(b): output token estimate. @@ -2404,7 +2437,10 @@ pub(crate) fn brain_roi(args: RoiArgs) -> KimetsuResult<()> { format_token_count(report.estimated_saved_tokens) ); let net_sign = if report.net_tokens >= 0 { "+" } else { "" }; - println!(" net tokens: {net_sign}{}", report.net_tokens); + println!( + " estimated net tokens: {net_sign}{}", + report.net_tokens + ); if let Some(ref usd) = report.usd { println!( @@ -2435,12 +2471,12 @@ pub(crate) fn brain_roi(args: RoiArgs) -> KimetsuResult<()> { } else if report.net_tokens >= 0 { match &report.usd { Some(u) if u.net >= 0.0 => println!( - " Net positive: kimetsu saved you ~{} tokens (~${:.4}) this window.", + " Model estimate: potential savings ~{} tokens (~${:.4}) this window.", format_token_count(report.estimated_saved_tokens), u.net, ), _ => println!( - " Net positive: kimetsu saved you ~{} tokens this window.", + " Model estimate: potential savings ~{} tokens this window.", format_token_count(report.estimated_saved_tokens), ), } @@ -2448,7 +2484,7 @@ pub(crate) fn brain_roi(args: RoiArgs) -> KimetsuResult<()> { // Honest negative. match &report.usd { Some(u) => println!( - " Net negative: brain overhead exceeded savings by ~{} tokens (~${:.4}) this window.", + " Model estimate: overhead exceeds assumed savings by ~{} tokens (~${:.4}) this window.", format_token_count( report .injected_tokens @@ -2457,7 +2493,7 @@ pub(crate) fn brain_roi(args: RoiArgs) -> KimetsuResult<()> { (u.spent - u.saved).abs(), ), None => println!( - " Net negative: brain overhead exceeded savings by ~{} tokens this window.", + " Model estimate: overhead exceeds assumed savings by ~{} tokens this window.", format_token_count( report .injected_tokens @@ -3625,22 +3661,28 @@ pub(crate) fn brain_benchmark_credit(args: BenchmarkCreditArgs) -> KimetsuResult let workspace = args .workspace .unwrap_or_else(|| env::current_dir().unwrap_or_default()); - let credited = kimetsu_brain::reinforce::credit_benchmark_outcome( - &workspace, - &args.task, - args.passed, - args.top_k, - )?; + let credited = if let Some(exposure) = args.exposure_id.as_deref() { + project::record_exposure_outcome( + &workspace, + exposure, + if args.passed { + Some(true) + } else if args.failed { + Some(false) + } else { + None + }, + )? + } else { + kimetsu_brain::reinforce::credit_benchmark_outcome( + &workspace, + &args.task, + args.passed, + args.top_k, + )? + }; println!( - "benchmark-credit: {} memor{} cited for task \"{}\" ({})", - credited, - if credited == 1 { "y" } else { "ies" }, - args.task, - if args.passed { - "passed" - } else { - "not passed — no citation" - } + "benchmark-credit: {credited} delivered memories associated with explicit outcome; no citations or verification inferred" ); Ok(()) } @@ -4093,6 +4135,7 @@ pub(crate) fn brain_ask(args: AskArgs) -> KimetsuResult<()> { serde_json::to_string_pretty(&serde_json::json!({ "ok": true, "question": question, + "exposure_id": result.exposure_id, "answer": result.answer, "citations": result.citations, "grounded": result.grounded, diff --git a/crates/kimetsu-cli/src/commands/hooks.rs b/crates/kimetsu-cli/src/commands/hooks.rs index ccc856c..93ec9a8 100644 --- a/crates/kimetsu-cli/src/commands/hooks.rs +++ b/crates/kimetsu-cli/src/commands/hooks.rs @@ -75,7 +75,15 @@ pub(crate) fn brain_context_hook(args: ContextHookArgs) -> KimetsuResult<()> { // instead. Claude Code does not pass `--warm-on-first-prompt`: it already // gets the identical block from `brain session-start-hook`. let warm_start_block = if args.warm_on_first_prompt && state.warm_started_unix == 0 { - warm_start_context(&workspace) + kimetsu_brain::digest::warm_start_block_scoped( + &workspace, + hook_payload + .as_ref() + .and_then(|p| p.get("task_id")) + .and_then(|v| v.as_str()) + .or(session_id.as_deref()) + .unwrap_or(""), + ) } else { None }; @@ -1195,7 +1203,14 @@ fn record_hook_delivery( ); payload["session_id"] = serde_json::json!(session_id); payload["surface"] = serde_json::json!(surface); - let _ = project::log_telemetry_event(workspace, "context.injected", payload); + payload["cost_unit"] = serde_json::json!("rendered_utf8_bytes"); + let mut exposure = kimetsu_core::event::Event::new( + kimetsu_core::ids::RunId::new(), + "context.injected", + payload, + ); + exposure.payload["exposure_id"] = serde_json::json!(exposure.event_id.to_string()); + let _ = project::record_context_exposure(workspace, &exposure); } pub(crate) fn proactive_header(event: ProactiveEvent, loop_mode: bool) -> &'static str { diff --git a/crates/kimetsu-cli/src/commands/lifecycle.rs b/crates/kimetsu-cli/src/commands/lifecycle.rs index 2ad768f..52a3c2d 100644 --- a/crates/kimetsu-cli/src/commands/lifecycle.rs +++ b/crates/kimetsu-cli/src/commands/lifecycle.rs @@ -44,7 +44,12 @@ pub(crate) fn checkpoint_cmd(args: CheckpointArgs) -> KimetsuResult<()> { // Use capture_episode_now with an empty transcript (manual save does not // require a transcript — the note itself is sufficient context). - let ok = distiller::capture_episode_now(&workspace, "", note); + let ok = distiller::capture_episode_now_scoped( + &workspace, + "", + note, + args.task_id.as_deref().unwrap_or(""), + ); if ok { println!("[Kimetsu] Work checkpoint saved."); @@ -70,7 +75,10 @@ pub(crate) fn resume_cmd(args: ResumeArgs) -> KimetsuResult<()> { .workspace .unwrap_or_else(|| env::current_dir().unwrap_or_default()); - match kimetsu_brain::episode::load_live_episode_for_workspace(&workspace) { + match kimetsu_brain::episode::load_live_episode_for_workspace_scoped( + &workspace, + args.task_id.as_deref().unwrap_or(""), + ) { Ok(Some(ep)) => { println!("── Resume: last session ──────────────────────────────"); if !ep.task.is_empty() { diff --git a/crates/kimetsu-cli/src/distiller.rs b/crates/kimetsu-cli/src/distiller.rs index 444d661..f8b4d49 100644 --- a/crates/kimetsu-cli/src/distiller.rs +++ b/crates/kimetsu-cli/src/distiller.rs @@ -761,22 +761,22 @@ pub fn run_session_end_hook(workspace: &Path) { // Story 1.3: auto-capture episode at SessionEnd (best-effort, never fails // the hook). - capture_episode_at_session_end(workspace, transcript_path.unwrap_or("")); -} - -/// Capture a work episode at SessionEnd. Tries the cheap model first; -/// degrades gracefully to the rule-based fallback if none is configured or -/// if the model call fails. Best-effort — silently swallows all errors so -/// the session shutdown is never blocked. -pub fn capture_episode_at_session_end(workspace: &Path, transcript_path: &str) { - capture_episode_now(workspace, transcript_path, ""); + let identity = payload + .get("task_id") + .or_else(|| payload.get("session_id")) + .or_else(|| payload.get("worktree_id")) + .and_then(|v| v.as_str()) + .unwrap_or(""); + capture_episode_now_scoped(workspace, transcript_path.unwrap_or(""), "", identity); } -/// Capture an episode now (manual checkpoint or auto-capture). -/// -/// `note` is an optional annotation from the user. -/// Returns `true` if the episode was written successfully. -pub fn capture_episode_now(workspace: &Path, transcript_path: &str, note: &str) -> bool { +/// Capture a work episode within the exact caller-selected task/session lane. +pub fn capture_episode_now_scoped( + workspace: &Path, + transcript_path: &str, + note: &str, + identity: &str, +) -> bool { use kimetsu_brain::episode::{capture_episode, rule_based_episode}; use kimetsu_core::paths::ProjectPaths; @@ -795,13 +795,14 @@ pub fn capture_episode_now(workspace: &Path, transcript_path: &str, note: &str) // Try cheap model first; fall back to rule-based. Episode capture is // automatic (SessionEnd), so it goes through the tier gate. - let episode_payload = if let Some(resolved) = resolve_pipeline_distiller(workspace) { + let mut episode_payload = if let Some(resolved) = resolve_pipeline_distiller(workspace) { distill_episode_with_model(&view, &resolved, &repo_root, note) .unwrap_or_else(|| kimetsu_brain::episode::rule_based_episode(&view, &repo_root, note)) } else { rule_based_episode(&view, &repo_root, note) }; + episode_payload.identity = identity.to_string(); // Write the episode event. Best-effort. match capture_episode(workspace, episode_payload) { Ok(_id) => true, @@ -933,6 +934,7 @@ fn parse_episode_json( .to_string(); Some(kimetsu_brain::episode::EpisodePayload { + identity: String::new(), task, summary, open_threads, diff --git a/crates/kimetsu-cli/src/main.rs b/crates/kimetsu-cli/src/main.rs index f02d153..e06ff80 100644 --- a/crates/kimetsu-cli/src/main.rs +++ b/crates/kimetsu-cli/src/main.rs @@ -874,7 +874,11 @@ enum BrainCommand { /// kimetsu brain forget --yes /// kimetsu brain forget --yes --force-enabled Forget(ForgetArgs), - /// Record a ground-truth citation: mark that a memory materially helped. + /// List reversibly archived memories. + Archives, + /// Restore an archived memory without reopening temporal expiry. + Restore { memory_id: String }, + /// Record explicit reliance on a memory; this does not verify its truth. /// /// Writes a `memory.cited` event (raising use_count / usefulness), the same /// signal the MCP `kimetsu_brain_cite` tool records — exposed on the CLI so @@ -1256,6 +1260,9 @@ struct DigestArgs { /// Args for `kimetsu checkpoint`. #[derive(Debug, Args)] struct CheckpointArgs { + /// Stable task, session or worktree identity for this checkpoint lane. + #[arg(long, alias = "session-id", alias = "worktree-id")] + task_id: Option, /// Optional note to attach to this checkpoint. #[arg(value_name = "NOTE")] note: Option, @@ -1267,6 +1274,9 @@ struct CheckpointArgs { /// Args for `kimetsu resume`. #[derive(Debug, Args)] struct ResumeArgs { + /// Resume this exact task lane; never fall back to another task. + #[arg(long, alias = "session-id", alias = "worktree-id")] + task_id: Option, /// Override the brain workspace path (defaults to current directory). #[arg(long)] workspace: Option, @@ -1497,13 +1507,19 @@ struct ReinforceArgs { /// Args for `kimetsu brain benchmark-credit`. #[derive(Debug, Args)] struct BenchmarkCreditArgs { + /// Exact exposure_id from the context actually delivered before grading. + #[arg(long)] + exposure_id: Option, + /// Explicit failure outcome; omission of both outcome flags is unknown. + #[arg(long, conflicts_with = "passed")] + failed: bool, /// The task description / query the graded task represents. #[arg(long)] task: String, - /// Mark the task as PASSED — only passes produce a citation. + /// Record an observed pass association; never invent citations. #[arg(long)] passed: bool, - /// How many top-ranked memories to credit on a pass. + /// Legacy compatibility option; no post-outcome retrieval is performed. #[arg(long, default_value_t = 3)] top_k: usize, /// Override the brain workspace path (defaults to current directory). diff --git a/crates/kimetsu-cli/tests/cli_smoke.rs b/crates/kimetsu-cli/tests/cli_smoke.rs index 58e001a..f33e8d1 100644 --- a/crates/kimetsu-cli/tests/cli_smoke.rs +++ b/crates/kimetsu-cli/tests/cli_smoke.rs @@ -1308,3 +1308,62 @@ fn hardening_free_hooks_never_cue_host_after_resolution_or_stop() { fs::remove_dir_all(root).unwrap(); } } + +#[test] +fn hardening_episode_cli_identity_and_archive_restore() { + let (root, cache_home) = seeded_proactive_project("episode_archive"); + let run = |args: &[&str]| { + let output = Command::new(kimetsu_bin()) + .args(args) + .current_dir(&root) + .env("KIMETSU_USER_BRAIN", "0") + .env("KIMETSU_USER_BRAIN_DIR", &cache_home) + .env("KIMETSU_TIER", "free") + .env("KIMETSU_BRAIN_EMBEDDER", "noop") + .output() + .unwrap(); + assert!( + output.status.success(), + "{}", + String::from_utf8_lossy(&output.stderr) + ); + String::from_utf8(output.stdout).unwrap() + }; + run(&["checkpoint", "lane alpha", "--task-id", "alpha"]); + run(&["checkpoint", "lane beta", "--task-id", "beta"]); + assert!(run(&["resume", "--task-id", "alpha"]).contains("lane alpha")); + assert!(!run(&["resume", "--task-id", "alpha"]).contains("lane beta")); + assert!(!run(&["resume", "--task-id", "missing"]).contains("lane beta")); + let added = run(&[ + "brain", + "memory", + "add", + "--scope", + "project", + "--kind", + "fact", + "archive-cli-quokka", + ]); + let (_, _, c) = brain_project::load_project(&root).unwrap(); + let id: String = c + .query_row( + "SELECT memory_id FROM memories WHERE text='archive-cli-quokka'", + [], + |r| r.get(0), + ) + .unwrap_or_else(|e| panic!("{e}: {added}")); + drop(c); + run(&[ + "brain", + "memory", + "invalidate", + &id, + "--reason", + "forgotten", + ]); + assert!(run(&["brain", "archives"]).contains(&id)); + assert!(run(&["brain", "restore", &id]).contains("true")); + assert!(!run(&["brain", "archives"]).contains(&id)); + assert!(run(&["brain", "roi", "--json"]).contains("Assumption-based estimate")); + fs::remove_dir_all(root).unwrap(); +} diff --git a/crates/kimetsu-core/src/lib.rs b/crates/kimetsu-core/src/lib.rs index 96c296d..5cddb89 100644 --- a/crates/kimetsu-core/src/lib.rs +++ b/crates/kimetsu-core/src/lib.rs @@ -7,7 +7,7 @@ pub mod memory; pub mod paths; pub mod secret; -pub const KIMETSU_SCHEMA_VERSION: i64 = 13; +pub const KIMETSU_SCHEMA_VERSION: i64 = 14; /// The `project.toml` config-file format version. Deliberately decoupled /// from `KIMETSU_SCHEMA_VERSION` (the brain.db schema): the DB schema can /// advance via migrations without forcing every project.toml to be rewritten. diff --git a/docs/audits/2026-09-04-evidence-continuity-hardening.md b/docs/audits/2026-09-04-evidence-continuity-hardening.md new file mode 100644 index 0000000..5870a0c --- /dev/null +++ b/docs/audits/2026-09-04-evidence-continuity-hardening.md @@ -0,0 +1,15 @@ +# Evidence and continuity repair + +Citation records reliance on delivered context; successful-run attribution records observed association. Neither proves the memory is true or caused success. Origin penalties remain after citation/association; no verification channel was added. Public provenance audit groups expose `associated`, and externally sourced memories remain unvetted. + +Context delivery persists exact IDs and hydrated revision bindings under an exposure event ID and a real run ID. MCP context returns that ID inside its final serialized byte budget. Citation and benchmark outcome APIs resolve the durable exposure rather than retrieving again after the task. Unknown outcomes and absent/unbound exposures produce no automatic memory credit. Mixed claim revisions in one run fail closed for automatic attribution. Query storage continues to honor learning.store_queries. + +Schema v14 adds an optional work episode identity. An empty value is the legacy lane. Checkpoint/resume expose --task-id, with --session-id and --worktree-id aliases. Hook/MCP identity precedence is task_id, session_id, worktree_id; explicit identity requests never fall back to unrelated work. Episode writes and supersession are atomic within a lane and replayable. + +Forgetting remains opt-in archival. Candidates are revalidated under the same writer lock, including claim revision and meaningful recency. `brain archives` lists archives and `brain restore ` writes memory.restored. Restoration cannot reopen temporal expiry or revive invalid/superseded claims, and invalidity cannot be overwritten with an archive reason to evade that rule. + +Manual conflict decisions now persist self-contained conflict.resolved events atomically with losing-side retirement. Rebuild preserves kept_new, kept_existing, and kept_both. Similarity is only a detection signal. + +ROI constants remain nominal assumptions. Reports expose the model and assumption table, identify estimates, and keep recorded delivery cost units separate. UTF-8 byte bounds and heuristic token estimates are not measured model tokens or evidence of counterfactual savings. Existing association-based usefulness/confidence nudges remain heuristics. + +Focused regression evidence and exact API signatures are recorded in tmp-tests/task-5-report.md. No paid generations, live-brain edits, global configuration changes, or performance claims were involved. From 87b5dcac37748c57d71b0702d6e90a1e76458836 Mon Sep 17 00:00:00 2001 From: RodCor Date: Fri, 4 Sep 2026 23:14:01 -0300 Subject: [PATCH 13/34] Prevent rejected-claim restore and select explicit episode lanes consistently --- crates/kimetsu-brain/src/conflict.rs | 4 +- crates/kimetsu-brain/src/episode.rs | 13 +++++ .../src/hardening_evidence_tests.rs | 53 +++++++++++++++++++ crates/kimetsu-brain/src/project.rs | 25 +++++---- crates/kimetsu-brain/src/sync.rs | 15 ++++-- crates/kimetsu-chat/src/mcp_server.rs | 40 +++++++++++--- crates/kimetsu-cli/src/commands/brain.rs | 7 +-- crates/kimetsu-cli/src/commands/hooks.rs | 15 ++---- crates/kimetsu-cli/src/distiller.rs | 7 +-- crates/kimetsu-cli/tests/cli_smoke.rs | 20 +++++++ 10 files changed, 154 insertions(+), 45 deletions(-) diff --git a/crates/kimetsu-brain/src/conflict.rs b/crates/kimetsu-brain/src/conflict.rs index 4edab6d..c1a23c6 100644 --- a/crates/kimetsu-brain/src/conflict.rs +++ b/crates/kimetsu-brain/src/conflict.rs @@ -733,7 +733,9 @@ pub(crate) fn project_resolution( _ => None, }; if let Some(loser) = loser { - conn.execute("UPDATE memories SET invalidated_at=COALESCE(invalidated_at,?2),invalidated_reason=COALESCE(invalidated_reason,?3) WHERE memory_id=?1",params![loser,ts,format!("conflict {id} resolved as {resolution}")])?; + // Explicit rejection ends archival eligibility. Keeping a previous + // `forgotten` reason would let restore resurrect the rejected claim. + conn.execute("UPDATE memories SET invalidated_at=COALESCE(invalidated_at,?2),invalidated_reason=CASE WHEN invalidated_reason IS NULL OR invalidated_reason IN ('forgotten','forgotten/archived','forgotten_archived') THEN ?3 ELSE invalidated_reason END WHERE memory_id=?1",params![loser,ts,format!("conflict {id} resolved as {resolution}")])?; conn.execute("DELETE FROM memories_fts WHERE memory_id=?1", [loser])?; #[cfg(feature = "embeddings")] crate::ann::on_invalidate(conn, loser); diff --git a/crates/kimetsu-brain/src/episode.rs b/crates/kimetsu-brain/src/episode.rs index 023e39e..ba20c6b 100644 --- a/crates/kimetsu-brain/src/episode.rs +++ b/crates/kimetsu-brain/src/episode.rs @@ -28,6 +28,19 @@ use time::format_description::well_known::Rfc3339; use crate::project::{load_project, load_project_readonly}; use crate::projector; +/// One caller-selected lane, preferring task, session, then worktree identity. +/// Null, non-string and blank placeholders never hide a usable later key. +pub fn requested_identity(payload: &serde_json::Value) -> Option<&str> { + ["task_id", "session_id", "worktree_id"] + .into_iter() + .find_map(|key| { + payload + .get(key) + .and_then(serde_json::Value::as_str) + .filter(|id| !id.trim().is_empty()) + }) +} + // --------------------------------------------------------------------------- // Episode data types // --------------------------------------------------------------------------- diff --git a/crates/kimetsu-brain/src/hardening_evidence_tests.rs b/crates/kimetsu-brain/src/hardening_evidence_tests.rs index 8655c63..451ee47 100644 --- a/crates/kimetsu-brain/src/hardening_evidence_tests.rs +++ b/crates/kimetsu-brain/src/hardening_evidence_tests.rs @@ -16,6 +16,27 @@ fn accepted(id: &str) -> Event { json!({"memory_id":id,"scope":"project","kind":"fact","text":format!("quokka {id}")}), ) } +#[test] +fn hardening_identity_uses_first_usable_task_session_or_worktree() { + for (payload, expected) in [ + ( + json!({"task_id":"task","session_id":"session","worktree_id":"tree"}), + Some("task"), + ), + ( + json!({"task_id":null,"session_id":"session","worktree_id":"tree"}), + Some("session"), + ), + ( + json!({"task_id":42,"session_id":" ","worktree_id":"tree"}), + Some("tree"), + ), + (json!({"task_id":"","session_id":null}), None), + ] { + assert_eq!(episode::requested_identity(&payload), expected); + } +} + #[test] fn hardening_concurrent_episode_lanes_replay() { let c = conn(); @@ -128,6 +149,38 @@ fn hardening_archive_restore_replay_preserves_expiry_and_invalidity() { ); } } +#[test] +fn hardening_manual_conflict_rejection_cannot_restore_archived_loser() { + let c = conn(); + projector::apply_events( + &c, + &[ + accepted("a"), + accepted("b"), + event( + "memory.invalidated", + json!({"memory_id":"b","reason":"forgotten"}), + ), + ], + ) + .unwrap(); + c.execute("INSERT INTO memory_conflicts(conflict_id,new_memory_id,existing_memory_id,scope,kind,similarity,detected_at) VALUES('ab','a','b','project','fact',0.95,'2026-01-01T00:00:00Z')", []).unwrap(); + assert!(crate::conflict::resolve_conflict(&c, "ab", "kept_new").unwrap()); + for _ in 0..2 { + projector::apply_events(&c, &[event("memory.restored", json!({"memory_id":"b"}))]).unwrap(); + assert!( + c.query_row( + "SELECT invalidated_at IS NOT NULL FROM memories WHERE memory_id='b'", + [], + |r| r.get::<_, bool>(0) + ) + .unwrap(), + "explicitly rejected conflict loser regained validity through archive restore" + ); + projector::rebuild_in_place(&c).unwrap(); + } +} + #[test] fn hardening_manual_conflict_replay_and_atomic_validation() { let c = conn(); diff --git a/crates/kimetsu-brain/src/project.rs b/crates/kimetsu-brain/src/project.rs index 87c5b74..7860887 100644 --- a/crates/kimetsu-brain/src/project.rs +++ b/crates/kimetsu-brain/src/project.rs @@ -6443,10 +6443,9 @@ max_total_cost_usd = 250.0 .expect("stats") } - // Story 2.4: a standalone citation raises use_count + usefulness (outcome - // signal applied because the run_id is the sentinel). + // Standalone reliance metadata does not imply a successful outcome. #[test] - fn standalone_cite_raises_usefulness() { + fn standalone_cite_records_reliance_without_outcome_credit() { with_user_brain_disabled(|| { let root = test_root(); std::fs::create_dir_all(&root).expect("create root"); @@ -6463,14 +6462,18 @@ max_total_cost_usd = 250.0 record_mcp_citation(&root, &memory_id, None).expect("cite"); let (uc1, us1, cf1) = read_outcome_stats(&root, &memory_id); - assert_eq!(uc1, uc0 + 1, "use_count must increment on standalone cite"); - assert!(us1 > us0, "usefulness must rise: {us0} -> {us1}"); - // A fresh memory starts below the ceiling (DIRECT_ADD_CONFIDENCE), so a - // positive outcome nudges confidence UP toward 1.0 — letting a proven - // memory outrank a never-evaluated one. - assert!( - cf1 > cf0, - "confidence must rise toward 1.0 on a positive outcome: {cf0} -> {cf1}" + assert_eq!((uc1, us1, cf1), (uc0, us0, cf0)); + rebuild_projection(&root, false).unwrap(); + assert_eq!(read_outcome_stats(&root, &memory_id), (uc0, us0, cf0)); + let (_, _, conn) = load_project(&root).unwrap(); + assert_eq!( + conn.query_row( + "SELECT count(*) FROM memory_citations WHERE memory_id=?1", + [&memory_id], + |r| r.get::<_, i64>(0) + ) + .unwrap(), + 1 ); std::fs::remove_dir_all(&root).ok(); }); diff --git a/crates/kimetsu-brain/src/sync.rs b/crates/kimetsu-brain/src/sync.rs index 6e28c1d..bb72f84 100644 --- a/crates/kimetsu-brain/src/sync.rs +++ b/crates/kimetsu-brain/src/sync.rs @@ -874,7 +874,7 @@ mod tests { use kimetsu_core::event::Event; let a = make_conn(); let b = make_conn(); - let run = RunId(ulid::Ulid::nil()); // sentinel → standalone cite outcome + let run = RunId(ulid::Ulid::nil()); // legacy standalone reliance metadata let (m1, s1, s2) = ("mem-m1", "mem-s1", "mem-s2"); // Shared base: identical accepted events on both brains. @@ -946,7 +946,7 @@ mod tests { "later-HLC supersede wins deterministically" ); - // Additive field (use_count) converges; both cites counted. + // Reliance metadata converges without manufacturing outcome credit. let use_count = |c: &Connection| -> i64 { c.query_row( "SELECT use_count FROM memories WHERE memory_id = ?1", @@ -956,7 +956,16 @@ mod tests { .unwrap() }; assert_eq!(use_count(&a), use_count(&b), "use_count must converge"); - assert_eq!(use_count(&a), 2, "both brains' cites counted"); + assert_eq!(use_count(&a), 0, "citations alone are not outcome credit"); + for brain in [&a, &b] { + assert_eq!( + brain + .query_row("SELECT count(*) FROM memory_citations", [], |r| r + .get::<_, i64>(0)) + .unwrap(), + 2 + ); + } // Even order-sensitive confidence converges (same HLC replay order). let confidence = |c: &Connection| -> f64 { diff --git a/crates/kimetsu-chat/src/mcp_server.rs b/crates/kimetsu-chat/src/mcp_server.rs index af7136c..8d01d2d 100644 --- a/crates/kimetsu-chat/src/mcp_server.rs +++ b/crates/kimetsu-chat/src/mcp_server.rs @@ -672,12 +672,7 @@ fn take_session_warm_start(workspace: &Path, arguments: &Value) -> Option Value { { "name": "kimetsu_brain_status", "description": BRAIN_STATUS_DESCRIPTION, - "inputSchema": { "type": "object", "properties": {} } + "inputSchema": { "type": "object", "properties": { + "task_id": {"type":"string","description":"Optional task lane; takes precedence over session/worktree identity."}, + "session_id": {"type":"string","description":"Optional session lane when task_id is absent."}, + "worktree_id": {"type":"string","description":"Optional worktree lane when task/session identity is absent."}, + "task_id": {"type":"string","description":"Optional task lane; takes precedence over session/worktree identity."}, + "session_id": {"type":"string","description":"Optional session lane when task_id is absent."}, + "worktree_id": {"type":"string","description":"Optional worktree lane when task/session identity is absent."},} } }, { "name": "kimetsu_brain_context", @@ -2191,7 +2192,7 @@ fn tool_definitions() -> Value { "memory_id": { "type": "string" }, "reason": { "type": "string" } }, - "required": ["memory_id", "exposure_id"] + "required": ["memory_id"] } }, { @@ -2507,6 +2508,29 @@ mod tests { fs::remove_dir_all(root).expect("remove temp root"); } + #[test] + fn tool_required_arguments_are_declared_in_their_schema() { + let result = handle_mcp_method( + "tools/list", + json!({}), + Path::new("."), + &SkillConfig::default(), + ) + .unwrap(); + for tool in result["tools"].as_array().unwrap() { + if let Some(required) = tool["inputSchema"]["required"].as_array() { + for field in required { + let name = field.as_str().expect("required argument name"); + assert!( + tool["inputSchema"]["properties"].get(name).is_some(), + "tool {} requires undeclared argument {name}", + tool["name"] + ); + } + } + } + } + #[test] fn brain_insights_appears_in_tool_definitions() { let result = handle_mcp_method( diff --git a/crates/kimetsu-cli/src/commands/brain.rs b/crates/kimetsu-cli/src/commands/brain.rs index 218f901..d21ec90 100644 --- a/crates/kimetsu-cli/src/commands/brain.rs +++ b/crates/kimetsu-cli/src/commands/brain.rs @@ -564,12 +564,7 @@ pub(crate) fn brain_session_start_hook(workspace: &Path) -> KimetsuResult<()> { use std::io::Read; let _ = std::io::stdin().read_to_string(&mut input); let payload: serde_json::Value = serde_json::from_str(input.trim()).unwrap_or_default(); - let identity = payload - .get("task_id") - .or_else(|| payload.get("session_id")) - .or_else(|| payload.get("worktree_id")) - .and_then(|v| v.as_str()) - .unwrap_or(""); + let identity = kimetsu_brain::episode::requested_identity(&payload).unwrap_or(""); let Some(additional_context) = kimetsu_brain::digest::warm_start_block_scoped(workspace, identity) else { diff --git a/crates/kimetsu-cli/src/commands/hooks.rs b/crates/kimetsu-cli/src/commands/hooks.rs index 93ec9a8..323470e 100644 --- a/crates/kimetsu-cli/src/commands/hooks.rs +++ b/crates/kimetsu-cli/src/commands/hooks.rs @@ -43,6 +43,9 @@ pub(crate) fn brain_context_hook(args: ContextHookArgs) -> KimetsuResult<()> { .and_then(serde_json::Value::as_str) .filter(|s| !s.trim().is_empty()) .map(str::to_string); + let episode_identity = hook_payload + .as_ref() + .and_then(kimetsu_brain::episode::requested_identity); // Extract the prompt text from the hook payload let prompt = match &hook_payload { @@ -62,7 +65,7 @@ pub(crate) fn brain_context_hook(args: ContextHookArgs) -> KimetsuResult<()> { .ok() .map(|p| { let cache_dir = kimetsu_core::paths::user_cache_dir_for(&p.repo_root); - proactive_state::session_path(&cache_dir, session_id.as_deref()) + proactive_state::session_path(&cache_dir, episode_identity) }); let mut state = state_path .as_deref() @@ -75,15 +78,7 @@ pub(crate) fn brain_context_hook(args: ContextHookArgs) -> KimetsuResult<()> { // instead. Claude Code does not pass `--warm-on-first-prompt`: it already // gets the identical block from `brain session-start-hook`. let warm_start_block = if args.warm_on_first_prompt && state.warm_started_unix == 0 { - kimetsu_brain::digest::warm_start_block_scoped( - &workspace, - hook_payload - .as_ref() - .and_then(|p| p.get("task_id")) - .and_then(|v| v.as_str()) - .or(session_id.as_deref()) - .unwrap_or(""), - ) + kimetsu_brain::digest::warm_start_block_scoped(&workspace, episode_identity.unwrap_or("")) } else { None }; diff --git a/crates/kimetsu-cli/src/distiller.rs b/crates/kimetsu-cli/src/distiller.rs index f8b4d49..6e4c269 100644 --- a/crates/kimetsu-cli/src/distiller.rs +++ b/crates/kimetsu-cli/src/distiller.rs @@ -761,12 +761,7 @@ pub fn run_session_end_hook(workspace: &Path) { // Story 1.3: auto-capture episode at SessionEnd (best-effort, never fails // the hook). - let identity = payload - .get("task_id") - .or_else(|| payload.get("session_id")) - .or_else(|| payload.get("worktree_id")) - .and_then(|v| v.as_str()) - .unwrap_or(""); + let identity = kimetsu_brain::episode::requested_identity(&payload).unwrap_or(""); capture_episode_now_scoped(workspace, transcript_path.unwrap_or(""), "", identity); } diff --git a/crates/kimetsu-cli/tests/cli_smoke.rs b/crates/kimetsu-cli/tests/cli_smoke.rs index f33e8d1..79155c6 100644 --- a/crates/kimetsu-cli/tests/cli_smoke.rs +++ b/crates/kimetsu-cli/tests/cli_smoke.rs @@ -1334,6 +1334,26 @@ fn hardening_episode_cli_identity_and_archive_restore() { assert!(run(&["resume", "--task-id", "alpha"]).contains("lane alpha")); assert!(!run(&["resume", "--task-id", "alpha"]).contains("lane beta")); assert!(!run(&["resume", "--task-id", "missing"]).contains("lane beta")); + run(&["checkpoint", "unrelated legacy lane"]); + for (payload, expected, rejected) in [ + ( + r#"{"prompt":"hi","worktree_id":"alpha"}"#, + "lane alpha", + "lane beta", + ), + ( + r#"{"prompt":"hi","task_id":null,"session_id":null,"worktree_id":"beta"}"#, + "lane beta", + "lane alpha", + ), + ] { + let text = run_context_hook(&root, &cache_home, &["--warm-on-first-prompt"], payload); + assert!( + text.contains(expected), + "missing explicit lane {expected}: {text}" + ); + assert!(!text.contains(rejected) && !text.contains("unrelated legacy lane")); + } let added = run(&[ "brain", "memory", From b514085c773b0726e2d40eafa3b717ad7d572762 Mon Sep 17 00:00:00 2001 From: RodCor Date: Fri, 4 Sep 2026 23:20:19 -0300 Subject: [PATCH 14/34] Advertise episode identity on the context tool schemas --- crates/kimetsu-chat/src/mcp_server.rs | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/crates/kimetsu-chat/src/mcp_server.rs b/crates/kimetsu-chat/src/mcp_server.rs index 8d01d2d..d1fc9ad 100644 --- a/crates/kimetsu-chat/src/mcp_server.rs +++ b/crates/kimetsu-chat/src/mcp_server.rs @@ -1982,13 +1982,7 @@ fn tool_definitions() -> Value { { "name": "kimetsu_brain_status", "description": BRAIN_STATUS_DESCRIPTION, - "inputSchema": { "type": "object", "properties": { - "task_id": {"type":"string","description":"Optional task lane; takes precedence over session/worktree identity."}, - "session_id": {"type":"string","description":"Optional session lane when task_id is absent."}, - "worktree_id": {"type":"string","description":"Optional worktree lane when task/session identity is absent."}, - "task_id": {"type":"string","description":"Optional task lane; takes precedence over session/worktree identity."}, - "session_id": {"type":"string","description":"Optional session lane when task_id is absent."}, - "worktree_id": {"type":"string","description":"Optional worktree lane when task/session identity is absent."},} } + "inputSchema": { "type": "object", "properties": {} } }, { "name": "kimetsu_brain_context", @@ -1996,6 +1990,9 @@ fn tool_definitions() -> Value { "inputSchema": { "type": "object", "properties": { + "task_id": {"type":"string","description":"Optional task lane; takes precedence over session/worktree identity."}, + "session_id": {"type":"string","description":"Optional session lane when task_id is absent."}, + "worktree_id": {"type":"string","description":"Optional worktree lane when task/session identity is absent."}, "query": { "type": "string" }, "stage": { "type": "string", @@ -2048,6 +2045,9 @@ fn tool_definitions() -> Value { "inputSchema": { "type": "object", "properties": { + "task_id": {"type":"string","description":"Optional task lane; takes precedence over session/worktree identity."}, + "session_id": {"type":"string","description":"Optional session lane when task_id is absent."}, + "worktree_id": {"type":"string","description":"Optional worktree lane when task/session identity is absent."}, "task": { "type": "string" }, "query": { "type": "string", "description": "Alias for task for compatibility with generic brain helpers." }, "dataset": { "type": "string", "default": "terminal-bench/terminal-bench-2" }, @@ -2508,6 +2508,18 @@ mod tests { fs::remove_dir_all(root).expect("remove temp root"); } + #[test] + fn context_tool_catalog_advertises_episode_identity_lanes() { + let definitions = super::tool_definitions(); + for name in ["kimetsu_brain_context", "kimetsu_benchmark_context"] { + let tool = definitions.as_array().unwrap().iter().find(|t|t["name"] == name).unwrap(); + for field in ["task_id", "session_id", "worktree_id"] { + assert_eq!(tool["inputSchema"]["properties"][field]["type"], "string", + "{name} must advertise the supported {field} lane"); + } + } + } + #[test] fn tool_required_arguments_are_declared_in_their_schema() { let result = handle_mcp_method( From 2ef07e4e61cf1d7264f09b240d4115d01034731b Mon Sep 17 00:00:00 2001 From: RodCor Date: Fri, 4 Sep 2026 23:41:02 -0300 Subject: [PATCH 15/34] fix(brain): unify measured delivery and evidence-aware tuning --- crates/kimetsu-brain/src/context.rs | 7 + crates/kimetsu-brain/src/embeddings.rs | 143 ++++- crates/kimetsu-brain/src/eval.rs | 127 ++++- crates/kimetsu-brain/src/lib.rs | 1 + crates/kimetsu-brain/src/project.rs | 95 ++-- crates/kimetsu-brain/src/serving.rs | 375 +++++++++++++ crates/kimetsu-brain/src/tune.rs | 229 +++++--- crates/kimetsu-brain/src/tuneset.rs | 287 +++++----- crates/kimetsu-chat/src/mcp_server.rs | 229 ++++---- crates/kimetsu-cli/src/commands/bench.rs | 499 +++++++++++------- crates/kimetsu-cli/src/commands/brain.rs | 465 +++++++--------- crates/kimetsu-cli/src/embed_daemon/server.rs | 68 +-- crates/kimetsu-cli/src/main.rs | 21 +- crates/kimetsu-core/src/config.rs | 21 + crates/kimetsu-remote/src/lib.rs | 10 +- docs/canonical-evaluation.md | 17 + 16 files changed, 1702 insertions(+), 892 deletions(-) create mode 100644 crates/kimetsu-brain/src/serving.rs create mode 100644 docs/canonical-evaluation.md diff --git a/crates/kimetsu-brain/src/context.rs b/crates/kimetsu-brain/src/context.rs index 17320e0..4736b06 100644 --- a/crates/kimetsu-brain/src/context.rs +++ b/crates/kimetsu-brain/src/context.rs @@ -378,6 +378,9 @@ pub struct ContextRequest { /// from `BrokerSection.min_semantic_score` by the pipeline; callers /// that don't set it get the prior behaviour automatically. pub min_semantic_score: f32, + /// Explicit public override: None preserves legacy zero=inherited; Some(0) + /// disables, Some(-1) selects model auto, Some(positive) sets a floor. + pub min_semantic_score_override: Option, /// v1.0.0: absolute *lexical* relevance floor for memory candidates, /// as the fraction of the query's IDF-weighted discriminating power a /// memory must cover. Unlike `min_semantic_score` this needs no query @@ -388,6 +391,8 @@ pub struct ContextRequest { /// `..Default::default()` construction is unchanged. Populated from /// `BrokerSection.min_lexical_coverage` by the pipeline. pub min_lexical_coverage: f32, + /// Some(0) explicitly disables the lexical floor; None inherits legacy behavior. + pub min_lexical_coverage_override: Option, /// E3: inferred kind of the current task. Defaults to `Feature` /// (the neutral kind) so every existing `..Default::default()` /// construction is unchanged — Feature does NOT alter weights or @@ -408,6 +413,8 @@ pub struct ContextRequest { /// 0.0 (default) disables the gate. Populated from /// `BrokerSection.abstain_min_score` by the pipeline. pub abstain_evidence: f32, + /// Some(0) disables abstention; Some(-1) uses model auto; None inherits. + pub abstain_evidence_override: Option, } #[derive(Debug, Clone)] diff --git a/crates/kimetsu-brain/src/embeddings.rs b/crates/kimetsu-brain/src/embeddings.rs index ca31b5b..32f15fd 100644 --- a/crates/kimetsu-brain/src/embeddings.rs +++ b/crates/kimetsu-brain/src/embeddings.rs @@ -343,17 +343,8 @@ pub fn open_reranker_for_model(model_id: &str) -> Option> { } }; } - // Unknown → fallback to default curated turbo. - match fastembed_backend::FastembedReranker::try_open("jina-reranker-v1-turbo-en") { - Ok(r) => Some(Box::new(r) as Box), - Err(err) => { - eprintln!( - "kimetsu-brain: fallback reranker unavailable ({err}); \ - continuing without cross-encoder reranking" - ); - None - } - } + eprintln!("kimetsu-brain: unknown reranker {model_id:?}"); + None } #[cfg(not(feature = "embeddings"))] { @@ -362,6 +353,90 @@ pub fn open_reranker_for_model(model_id: &str) -> Option> { } } +pub fn reranker_is_off(model_id: &str) -> bool { + matches!( + model_id.trim().to_ascii_lowercase().as_str(), + "" | "off" | "none" | "noop" + ) +} + +/// Evaluation must never label a failed initialization as a measured CE run. +pub fn open_reranker_checked(model_id: &str) -> Result>, String> { + if reranker_is_off(model_id) { + return Ok(None); + } + open_reranker_for_model(model_id).map(Some).ok_or_else(|| { + format!("requested reranker {model_id:?} unavailable; no cross-encoder measurement") + }) +} + +type CachedReranker = Result>, String>; +#[derive(Default)] +struct RerankerCache(std::sync::Mutex>); +impl RerankerCache { + fn get(&self, id: &str, load: impl FnOnce(&str) -> CachedReranker) -> CachedReranker { + if reranker_is_off(id) { + return Ok(None); + } + let mut entries = self.0.lock().unwrap_or_else(|e| e.into_inner()); + entries + .entry(id.trim().to_string()) + .or_insert_with(|| load(id)) + .clone() + } +} +/// Process cache keyed by configured model, including failed loads. Explicit off +/// bypasses the cache. Lean serving is explicitly FTS-only; checked evaluation +/// above still rejects any requested CE measurement on lean builds. +pub fn open_cached_reranker(model_id: &str) -> CachedReranker { + static CACHE: std::sync::OnceLock = std::sync::OnceLock::new(); + CACHE + .get_or_init(RerankerCache::default) + .get(model_id, |id| { + #[cfg(feature = "embeddings")] + { + open_reranker_checked(id).map(|r| r.map(std::sync::Arc::from)) + } + #[cfg(not(feature = "embeddings"))] + { + let _ = id; + Ok(None) + } + }) +} + +#[cfg(test)] +mod configured_reranker_tests { + use super::*; + #[test] + fn configured_cache_reuses_model_and_off_never_loads() { + let cache = RerankerCache::default(); + let calls = std::sync::atomic::AtomicUsize::new(0); + let load = |_: &str| { + calls.fetch_add(1, std::sync::atomic::Ordering::SeqCst); + Ok(Some( + std::sync::Arc::new(StubReranker) as std::sync::Arc + )) + }; + let first = cache.get("configured", load).unwrap().unwrap(); + let second = cache.get("configured", load).unwrap().unwrap(); + assert!(std::sync::Arc::ptr_eq(&first, &second)); + assert_eq!(calls.load(std::sync::atomic::Ordering::SeqCst), 1); + assert!( + cache + .get("off", |_| panic!("off must not load")) + .unwrap() + .is_none() + ); + assert!(cache.get("failed", |_| Err("unavailable".into())).is_err()); + assert!( + cache + .get("failed", |_| panic!("failure must remain explicit")) + .is_err() + ); + } +} + /// Open the production-default embedder. /// /// Resolution (v0.4.3): @@ -441,6 +516,14 @@ pub fn open_embedder_for(config_enabled: bool) -> &'static dyn Embedder { /// embedder cached). Returns [`NoopEmbedder`] on the lean build or if /// the model fails to load. pub fn open_embedder_for_model(model_id: &str) -> Box { + let model_id = match canonical_embedder_id(model_id) { + Ok("noop") => return Box::new(NoopEmbedder), + Ok(id) => id, + Err(error) => { + eprintln!("{error}"); + return Box::new(NoopEmbedder); + } + }; #[cfg(feature = "embeddings")] { match fastembed_backend::FastembedEmbedder::try_open(model_id) { @@ -460,6 +543,44 @@ pub fn open_embedder_for_model(model_id: &str) -> Box Result<&'static str, EmbedderError> { + match id.trim().to_ascii_lowercase().as_str() { + "noop" | "off" | "none" | "0" | "false" | "no" => Ok("noop"), + "" | "default" | "bge-small" | "bge-small-en-v1.5" => Ok("bge-small-en-v1.5"), + "bge-m3" | "m3" => Ok("bge-m3"), + "jina-code" | "jina-v2-base-code" | "jina-embeddings-v2-base-code" => { + Ok("jina-v2-base-code") + } + _ => Err(EmbedderError::LoadFailed(format!( + "unknown requested embedder {id:?}" + ))), + } +} + +#[cfg(test)] +mod explicit_embedder_tests { + use super::*; + #[test] + fn aliases_and_disable_have_one_effective_model_identity() { + assert_eq!( + canonical_embedder_id("jina-code").unwrap(), + "jina-v2-base-code" + ); + assert_eq!(canonical_embedder_id("m3").unwrap(), "bge-m3"); + assert_eq!( + canonical_embedder_id("bge-small").unwrap(), + "bge-small-en-v1.5" + ); + for off in ["off", "noop", "false", "none", "0"] { + assert_eq!(canonical_embedder_id(off).unwrap(), "noop"); + assert!(open_embedder_for_model(off).is_noop()); + } + assert!(canonical_embedder_id("typo-not-a-model").is_err()); + } +} + /// v0.4.3: env-driven kill switch. Truthy values (1/true/yes/on) /// force-disable the embedder for this process; "noop", "off", /// "none" do the same. Anything else (or unset) leaves the diff --git a/crates/kimetsu-brain/src/eval.rs b/crates/kimetsu-brain/src/eval.rs index 7b38ff3..c3c3bd7 100644 --- a/crates/kimetsu-brain/src/eval.rs +++ b/crates/kimetsu-brain/src/eval.rs @@ -56,8 +56,11 @@ pub enum CaseKind { pub struct EvalCase { pub query: String, /// Keys from [`EvalMemory::key`] that are relevant to this query. - /// Empty = off-domain query (exercises noise floor, recall trivially 1.0). + /// Explicit empty = known negative. Missing is invalid fixture data. pub relevant: Vec, + /// Optional task/fact family. Shared IDs and normalized queries also group cases. + #[serde(default)] + pub family: String, /// Classification of this case. Defaults to [`CaseKind::Recall`]. /// Existing fixtures omit this field; `#[serde(default)]` keeps them valid. #[serde(default)] @@ -81,23 +84,100 @@ pub struct EvalFixture { /// Fraction of `relevant` items found in the **first `k`** positions of `ranked`. /// /// Each relevant key is counted at most once even if it appears multiple times -/// in `ranked`. Returns `1.0` when `relevant` is empty (trivial recall for -/// off-domain / noise queries). Returns `0.0` when `k == 0`. +/// in `ranked`. Empty relevance returns zero; exclude known negatives from +/// recall denominators and score their actual abstention separately. pub fn recall_at_k(ranked: &[String], relevant: &[String], k: usize) -> f64 { if relevant.is_empty() { - return 1.0; + return 0.0; } if k == 0 || ranked.is_empty() { return 0.0; } let window = &ranked[..k.min(ranked.len())]; + let relevant: std::collections::HashSet<_> = relevant.iter().collect(); let found = relevant .iter() - .filter(|r| window.iter().any(|w| w == *r)) + .filter(|r| window.iter().any(|w| w == **r)) .count(); found as f64 / relevant.len() as f64 } +/// Metrics describe delivered results, never the pre-budget candidate pool. +/// Missing class denominators serialize as null, not a perfect score. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct EvaluationMetrics { + pub positive_count: usize, + pub negative_count: usize, + pub recall_at_2: Option, + pub recall_at_4: Option, + pub hit_at_2: Option, + pub hit_at_4: Option, + pub mrr: Option, + pub negative_accuracy: Option, + pub false_injection_rate: Option, + /// Equal weight for positive MRR and negative abstention accuracy when both + /// exist; otherwise the observed component only. Not factual probability. + pub quality: Option, + pub mean_final_bound: f64, +} + +pub fn summarize_deliveries( + cases: &[&EvalCase], + ranked: &[Vec], + final_bounds: &[u32], +) -> Result { + if cases.len() != ranked.len() || cases.len() != final_bounds.len() { + return Err("every case requires one delivery and final cost measurement".into()); + } + let positives: Vec<_> = cases + .iter() + .zip(ranked) + .filter(|(c, _)| !c.relevant.is_empty()) + .collect(); + let negatives: Vec<_> = cases + .iter() + .zip(ranked) + .filter(|(c, _)| c.relevant.is_empty()) + .collect(); + let avg = |values: Vec| (!values.is_empty()).then(|| mean(&values)); + let recall = |k| { + avg(positives + .iter() + .map(|(c, r)| recall_at_k(r, &c.relevant, k)) + .collect()) + }; + let hit = |k| { + avg(positives + .iter() + .map(|(c, r)| f64::from(recall_at_k(r, &c.relevant, k) > 0.0)) + .collect()) + }; + let mrr = avg(positives.iter().map(|(c, r)| mrr(r, &c.relevant)).collect()); + let negative_accuracy = avg(negatives + .iter() + .map(|(_, r)| f64::from(r.is_empty())) + .collect()); + let quality = avg(mrr.into_iter().chain(negative_accuracy).collect()); + Ok(EvaluationMetrics { + positive_count: positives.len(), + negative_count: negatives.len(), + recall_at_2: recall(2), + recall_at_4: recall(4), + hit_at_2: hit(2), + hit_at_4: hit(4), + mrr, + negative_accuracy, + false_injection_rate: negative_accuracy.map(|a| 1.0 - a), + quality, + mean_final_bound: mean( + &final_bounds + .iter() + .map(|b| f64::from(*b)) + .collect::>(), + ), + }) +} + /// Mean Reciprocal Rank of the **first** relevant item in `ranked` (1-based). /// /// Returns `1/rank` where `rank` is the 1-based position of the first relevant @@ -192,10 +272,39 @@ mod tests { // ── recall_at_k ────────────────────────────────────────────────────────── #[test] - fn recall_at_k_empty_relevant_is_one() { - // Off-domain queries: no relevant items → trivially 1.0. - assert_eq!(recall_at_k(&s(&["a", "b"]), &[], 4), 1.0); - assert_eq!(recall_at_k(&[], &[], 4), 1.0); + fn recall_at_k_negative_has_no_vacuous_quality_credit() { + assert_eq!(recall_at_k(&s(&["a", "b"]), &[], 4), 0.0); + assert_eq!(recall_at_k(&[], &[], 4), 0.0); + } + + #[test] + fn delivered_metrics_separate_fraction_hit_and_known_negative_accuracy() { + let positive: EvalCase = + serde_json::from_value(serde_json::json!({"query":"two facts","relevant":["a","b"]})) + .unwrap(); + let negative: EvalCase = + serde_json::from_value(serde_json::json!({"query":"unanswerable","relevant":[]})) + .unwrap(); + let metrics = + summarize_deliveries(&[&positive, &negative], &[s(&["a"]), vec![]], &[512, 256]) + .unwrap(); + assert_eq!(metrics.recall_at_2, Some(0.5)); + assert_eq!(metrics.hit_at_2, Some(1.0)); + assert_eq!(metrics.mrr, Some(1.0)); + assert_eq!(metrics.negative_accuracy, Some(1.0)); + assert_eq!(metrics.quality, Some(1.0)); + assert_eq!(metrics.mean_final_bound, 384.0); + let injecting = summarize_deliveries( + &[&positive, &negative], + &[s(&["a"]), s(&["junk"])], + &[512, 512], + ) + .unwrap(); + assert_eq!(injecting.quality, Some(0.5)); + let only_negative = summarize_deliveries(&[&negative], &[vec![]], &[256]).unwrap(); + assert_eq!(only_negative.mrr, None); + assert_eq!(only_negative.recall_at_2, None); + assert!(summarize_deliveries(&[&positive], &[], &[]).is_err()); } #[test] diff --git a/crates/kimetsu-brain/src/lib.rs b/crates/kimetsu-brain/src/lib.rs index 50e6587..496672d 100644 --- a/crates/kimetsu-brain/src/lib.rs +++ b/crates/kimetsu-brain/src/lib.rs @@ -45,6 +45,7 @@ pub mod reinforce; pub mod roi; pub mod schema; pub(crate) mod scoring; +pub mod serving; pub mod skill_synthesis; /// Epic S3: personal brain sync — event-log replication. pub mod sync; diff --git a/crates/kimetsu-brain/src/project.rs b/crates/kimetsu-brain/src/project.rs index 7860887..6609f25 100644 --- a/crates/kimetsu-brain/src/project.rs +++ b/crates/kimetsu-brain/src/project.rs @@ -371,6 +371,44 @@ pub struct BrainSession { } impl BrainSession { + pub fn config(&self) -> &ProjectConfig { + &self.config + } + + /// Resolve explicit overrides before legacy sentinels. The explicit zero + /// survives a second resolution at the injected/production boundary. + pub fn resolve_request_floors(&self, request: &mut ContextRequest) { + let semantic = request.min_semantic_score_override.unwrap_or_else(|| { + if request.min_semantic_score == 0.0 { + self.config.broker.min_semantic_score + } else { + request.min_semantic_score + } + }); + request.min_semantic_score = if semantic < 0.0 { + let model = embeddings::resolve_embedder_id(Some(&self.config.embedder.model)); + if model.starts_with("bge") { 0.35 } else { 0.0 } + } else { + semantic + }; + request.min_lexical_coverage = request.min_lexical_coverage_override.unwrap_or_else(|| { + if request.min_lexical_coverage == 0.0 { + self.config.broker.min_lexical_coverage + } else { + request.min_lexical_coverage + } + }); + request.abstain_evidence = match request.abstain_evidence_override { + Some(v) if v >= 0.0 => v, + Some(_) => { + let mut cfg = self.config.clone(); + cfg.broker.abstain_min_score = -1.0; + resolved_abstain_evidence_for(&cfg) + } + None if request.abstain_evidence == 0.0 => self.resolved_abstain_evidence(), + None => request.abstain_evidence, + }; + } pub fn open(start: &Path) -> KimetsuResult { let (paths, config, conn) = load_project(start)?; // Read/write user brain — created on demand so a v0.4 binary @@ -437,25 +475,7 @@ impl BrainSession { &self, mut request: ContextRequest, ) -> KimetsuResult { - // v1.0.0: drive the lexical + semantic relevance floors from config - // unless the caller set its own (non-zero) values. - if request.min_lexical_coverage == 0.0 { - request.min_lexical_coverage = self.config.broker.min_lexical_coverage; - } - if request.min_semantic_score == 0.0 { - request.min_semantic_score = self.resolved_min_semantic_score(); - } - // v2.7: whole-retrieval abstention floor, now on the ABSOLUTE evidence - // cosine scale rather than the - // normalized composite — the composite's top candidate always carries - // relevance 1.0, so no composite threshold can express "nothing here - // is relevant" (measured: false-injection 1.00 on the workflow bench). - // 0.0 = off; explicit request values win; -1.0 in config = per-model - // auto. The env var exists so benchmarks can sweep without config - // edits. - if request.abstain_evidence == 0.0 { - request.abstain_evidence = self.resolved_abstain_evidence(); - } + self.resolve_request_floors(&mut request); let extras: Vec<&Connection> = self.user_conn.as_ref().into_iter().collect(); // v2.6: same override rule for the normalization mode — resolved onto // the request itself because that is where scoring reads it. @@ -484,23 +504,6 @@ impl BrainSession { ) } - /// v1.0.0: resolve the semantic floor for this session's embedder. The - /// config default is the AUTO sentinel (-1.0): cosine scales are - /// MODEL-DEPENDENT — 0.35 suits bge-family distributions, but the remote - /// benchmark showed the same floor killing relevant jina-v2 results - /// outright (MRR 0.90 → 0.77, recall@2 == recall@4) — so auto applies - /// the bge-calibrated floor only to bge models and disables it - /// elsewhere (jina-v2's own precision keeps noise low without it). - /// Explicit non-negative config values are used as-is for any model. - fn resolved_min_semantic_score(&self) -> f32 { - let configured = self.config.broker.min_semantic_score; - if configured >= 0.0 { - return configured; - } - let model = embeddings::resolve_embedder_id(Some(self.config.embedder.model.as_str())); - if model.starts_with("bge") { 0.35 } else { 0.0 } - } - /// v2.7: resolve the absolute abstention floor for this session's config. /// See [`resolved_abstain_evidence_for`]. pub fn resolved_abstain_evidence(&self) -> f32 { @@ -606,25 +609,7 @@ impl BrainSession { mut request: ContextRequest, embedder: &dyn embeddings::Embedder, ) -> KimetsuResult { - if request.min_lexical_coverage == 0.0 { - request.min_lexical_coverage = self.config.broker.min_lexical_coverage; - } - // v1.0.0: semantic floor from config too — this is the daemon's path, - // where a real query embedding makes the cosine floor effective. - if request.min_semantic_score == 0.0 { - request.min_semantic_score = self.resolved_min_semantic_score(); - } - // v2.7: whole-retrieval abstention floor, now on the ABSOLUTE evidence - // cosine scale rather than the - // normalized composite — the composite's top candidate always carries - // relevance 1.0, so no composite threshold can express "nothing here - // is relevant" (measured: false-injection 1.00 on the workflow bench). - // 0.0 = off; explicit request values win; -1.0 in config = per-model - // auto. The env var exists so benchmarks can sweep without config - // edits. - if request.abstain_evidence == 0.0 { - request.abstain_evidence = self.resolved_abstain_evidence(); - } + self.resolve_request_floors(&mut request); let extras: Vec<&Connection> = self.user_conn.as_ref().into_iter().collect(); // v2.6: same override rule for the normalization mode — resolved onto // the request itself because that is where scoring reads it. diff --git a/crates/kimetsu-brain/src/serving.rs b/crates/kimetsu-brain/src/serving.rs new file mode 100644 index 0000000..2876dc8 --- /dev/null +++ b/crates/kimetsu-brain/src/serving.rs @@ -0,0 +1,375 @@ +//! Canonical brain-context retrieval and final MCP delivery policy. Evaluators +//! supply the same request/configuration and measure this complete renderer. +use crate::context::{ + ContextBundle, ContextRequest, + delivery::{Delivery, compact_capsules, fit_json}, + rerank_and_arbitrate, +}; +use crate::embeddings::{Embedder, Reranker}; +use crate::project::BrainSession; +use kimetsu_core::KimetsuResult; +use serde_json::json; + +pub const RERANK_POOL: usize = 6; +/// A score threshold, not a calibrated relevance probability. +pub const RERANK_FLOOR: f32 = 0.30; +pub const DEFAULT_BUDGET: u32 = 6000; +pub const DEFAULT_CAP: usize = 3; +/// ULIDs on the serving surface have exactly this length. Evaluation does not +/// persist an exposure and therefore uses a deterministic placeholder. +pub const EVAL_EXPOSURE_ID: &str = "00000000000000000000000000"; + +/// Precompute once so a low-level fallback cannot disguise failed inference. +struct QueryVector<'a> { + inner: &'a dyn Embedder, + vector: Vec, +} +impl Embedder for QueryVector<'_> { + fn embed(&self, _: &str) -> Result, crate::embeddings::EmbedderError> { + Ok(self.vector.clone()) + } + fn model_id(&self) -> &str { + self.inner.model_id() + } + fn dim(&self) -> usize { + self.inner.dim() + } +} +struct CheckedScores<'a> { + model: &'a str, + scores: Vec, +} +impl Reranker for CheckedScores<'_> { + fn rerank(&self, _: &str, _: &[&str]) -> Result, crate::embeddings::EmbedderError> { + Ok(self.scores.clone()) + } + fn model_id(&self) -> &str { + self.model + } +} + +#[derive(Debug, Clone, Copy)] +pub struct ServingPolicy { + pub budget: u32, + pub cap: usize, + pub pool: usize, + pub rerank_floor: f32, +} +impl Default for ServingPolicy { + fn default() -> Self { + Self { + budget: DEFAULT_BUDGET, + cap: DEFAULT_CAP, + pool: RERANK_POOL, + rerank_floor: RERANK_FLOOR, + } + } +} +impl ServingPolicy { + pub fn prepare(&self, mut request: ContextRequest, reranking: bool) -> ContextRequest { + request.budget_tokens = if reranking { + self.budget.max(DEFAULT_BUDGET) + } else { + self.budget + }; + request.max_capsules = if reranking { + self.cap.max(self.pool) + } else { + self.cap + }; + request + } + pub fn arbitrate( + &self, + query: &str, + bundle: ContextBundle, + reranker: Option<&dyn Reranker>, + abstain: f32, + ) -> ContextBundle { + let mut bundle = rerank_and_arbitrate( + query, + bundle, + reranker, + abstain, + self.rerank_floor, + self.cap, + ); + if self.cap > 0 { + bundle.capsules.truncate(self.cap) + } + bundle + } + pub fn render(&self, mut bundle: ContextBundle, compress: bool, exposure_id: &str) -> Delivery { + if compress { + for c in &mut bundle.capsules { + c.summary = crate::context::compress_for_render(&c.summary, 3) + } + } + let count = bundle.capsules.len(); + fit_json(bundle.capsules.clone(), self.budget, |capsules| { + json!({ + "ok":true,"skipped":capsules.is_empty(),"exposure_id":exposure_id, + "capsule_count":capsules.len(),"excluded_count":bundle.excluded.len()+count-capsules.len(), + "capsules":compact_capsules(capsules),"partial_evidence":bundle.evidence_coverage<1.0 || capsules.len(), + exposure_id: &str, + ) -> KimetsuResult { + session.resolve_request_floors(&mut request); + let abstain = request.abstain_evidence; + let query = request.query.clone(); + let query_vector = if embedder.is_noop() { + None + } else { + let vector = embedder.embed(&query)?; + if vector.len() != embedder.dim() + || vector.is_empty() + || vector.iter().any(|x| !x.is_finite()) + || !vector.iter().any(|x| *x != 0.0) + { + return Err( + "embedder returned an invalid query vector; no semantic measurement".into(), + ); + } + Some(QueryVector { + inner: embedder, + vector, + }) + }; + let checked_embedder = query_vector + .as_ref() + .map(|v| v as &dyn Embedder) + .unwrap_or(embedder); + let bundle = session.retrieve_context_with_injected_embedder( + self.prepare(request, reranker.is_some()), + checked_embedder, + )?; + let checked_scores = if let Some(rr) = reranker.filter(|_| !bundle.capsules.is_empty()) { + let docs: Vec<_> = bundle.capsules.iter().map(|c| c.summary.as_str()).collect(); + let scores = rr.rerank(&query, &docs)?; + if scores.len() != docs.len() + || scores + .iter() + .any(|s| !s.is_finite() || !(0.0..=1.0).contains(s)) + { + return Err("reranker returned invalid scores; no cross-encoder measurement".into()); + } + Some(CheckedScores { + model: rr.model_id(), + scores, + }) + } else { + None + }; + let bundle = self.arbitrate( + &query, + bundle, + checked_scores.as_ref().map(|r| r as &dyn Reranker), + abstain, + ); + Ok(self.render( + bundle, + session.config().broker.compress_capsules, + exposure_id, + )) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + context::ContextCapsule, + embeddings::{StubEmbedder, StubReranker}, + project, + }; + use kimetsu_core::memory::{MemoryKind, MemoryScope}; + struct FailedEmbedder; + impl Embedder for FailedEmbedder { + fn embed(&self, _: &str) -> Result, crate::embeddings::EmbedderError> { + Err(crate::embeddings::EmbedderError::EmbedFailed( + "test failure".into(), + )) + } + fn model_id(&self) -> &str { + "failed" + } + fn dim(&self) -> usize { + 2 + } + } + struct MalformedReranker; + impl Reranker for MalformedReranker { + fn rerank( + &self, + _: &str, + _: &[&str], + ) -> Result, crate::embeddings::EmbedderError> { + Ok(vec![]) + } + fn model_id(&self) -> &str { + "malformed" + } + } + #[test] + fn production_and_eval_use_same_final_budget_and_arbitration_with_injected_models() { + crate::user_brain::with_user_brain_disabled(|| { + let root = std::env::temp_dir().join(format!("kimetsu-serving-{}", ulid::Ulid::new())); + kimetsu_core::paths::git_init_boundary(&root); + project::init_project(&root, false).unwrap(); + project::add_memory( + &root, + MemoryScope::Project, + MemoryKind::Fact, + "wal checkpoint protects sqlite commits", + ) + .unwrap(); + project::add_memory( + &root, + MemoryScope::Project, + MemoryKind::Fact, + "remote network bandwidth compression", + ) + .unwrap(); + let paths = kimetsu_core::paths::ProjectPaths::discover(&root).unwrap(); + let mut config = project::load_config(&paths).unwrap(); + config.broker.min_semantic_score = 0.73; + std::fs::write(&paths.project_toml, config.to_toml().unwrap()).unwrap(); + let session = BrainSession::open_readonly(&root).unwrap(); + let embedder = StubEmbedder::default(); + let rr = StubReranker; + let request = ContextRequest { + query: "wal checkpoint".into(), + stage: "localization".into(), + ..Default::default() + }; + assert!( + ServingPolicy::default() + .retrieve( + &session, + request.clone(), + &FailedEmbedder, + None, + EVAL_EXPOSURE_ID + ) + .is_err(), + "failed inference cannot become successful semantic measurement" + ); + assert!( + ServingPolicy::default() + .retrieve( + &session, + request, + &embedder, + Some(&MalformedReranker), + EVAL_EXPOSURE_ID + ) + .is_err(), + "malformed CE cannot become successful reranker measurement" + ); + for budget in [1, 600, 1200, 6000] { + let policy = ServingPolicy { + budget, + ..Default::default() + }; + let req = ContextRequest { + query: "wal checkpoint".into(), + stage: "localization".into(), + min_score: 0.15, + ..Default::default() + }; + let eval = policy + .retrieve( + &session, + req.clone(), + &embedder, + Some(&rr), + EVAL_EXPOSURE_ID, + ) + .unwrap(); + let mut resolved = req; + session.resolve_request_floors(&mut resolved); + let abstain = resolved.abstain_evidence; + let bundle = session + .retrieve_context_with_injected_embedder( + policy.prepare(resolved, true), + &embedder, + ) + .unwrap(); + let production = policy.render( + policy.arbitrate("wal checkpoint", bundle, Some(&rr), abstain), + false, + EVAL_EXPOSURE_ID, + ); + let mut produced = production.payload.clone(); + let mut measured = eval.payload.clone(); + for payload in [&mut produced, &mut measured] { + if let Some(caps) = payload["capsules"].as_array_mut() { + for c in caps { + c["id"] = json!(EVAL_EXPOSURE_ID); + } + } + } + assert_eq!(produced, measured); + assert_eq!( + eval.payload["used_tokens"], + json!(crate::context::delivery::serialized_output_tokens( + &eval.payload + )) + ); + if budget == 1 { + assert!(eval.capsules.is_empty()); + assert_eq!(eval.payload["error"], "budget_too_small"); + } + } + let mut request = ContextRequest::default(); + session.resolve_request_floors(&mut request); + assert_eq!(request.min_semantic_score, 0.73); + request.min_semantic_score_override = Some(0.0); + session.resolve_request_floors(&mut request); + assert_eq!(request.min_semantic_score, 0.0); + request.min_semantic_score_override = Some(-1.0); + session.resolve_request_floors(&mut request); + assert!(matches!(request.min_semantic_score, 0.35 | 0.0)); + }); + } + #[test] + fn reranker_floor_and_final_serialization_reject_candidates_before_measurement() { + let capsules = [("hit", "wal checkpoint"), ("noise", "remote network")] + .into_iter() + .map(|(id, text)| { + let mut c = ContextCapsule::wire_minimal(text.into(), "memory".into(), 0.8); + c.id = id.into(); + c.expansion_handle = format!("memory:{id}"); + c + }) + .collect(); + let bundle = ContextBundle { + stage: "localization".into(), + budget_tokens: 6000, + used_tokens: 0, + capsules, + excluded: vec![], + skipped: false, + top_score: 0.8, + top_abs_evidence: -1.0, + evidence_coverage: 1.0, + uncovered_terms: vec![], + chronological: false, + }; + let policy = ServingPolicy::default(); + let selected = policy.arbitrate("wal checkpoint", bundle, Some(&StubReranker), 0.0); + assert_eq!(selected.capsules.len(), 1); + assert_eq!(selected.capsules[0].id, "hit"); + let delivery = policy.render(selected, false, EVAL_EXPOSURE_ID); + assert_eq!(delivery.capsules.len(), 1); + assert!(!delivery.payload.to_string().contains("remote network")); + } +} diff --git a/crates/kimetsu-brain/src/tune.rs b/crates/kimetsu-brain/src/tune.rs index 7a4ef7d..879dac5 100644 --- a/crates/kimetsu-brain/src/tune.rs +++ b/crates/kimetsu-brain/src/tune.rs @@ -14,7 +14,8 @@ //! - RERANK_POOL (compile-time const in the daemon) — deferred. //! //! Objective (S2.3): -//! mean_MRR - cost_weight * mean_injected_tokens - REGRET_PENALTY_WEIGHT * regret_rate +//! quality - cost_weight * mean_final_serialized_UTF8_bound +//! Default cost_weight = 0.05 / 6000; historical regret is diagnostic only. //! //! S2.1 Re-tune triggers: //! - Corpus milestone: ≥50 memories added since last tune. @@ -41,22 +42,9 @@ pub const RETUNE_REGRET_RATE_THRESHOLD: f64 = 0.10; /// Used to report the cost of a full embedder switch in the advisor output. pub const REINDEX_TOKENS_PER_1K_MEMORIES: u64 = 2_000; -/// S2.3 Regret penalty weight in the tune objective. -/// -/// Weighting rationale: -/// A floor config that generates a regret has caused the model to work -/// harder than necessary (re-discover context that the brain dropped). -/// We penalise the *rate* of regrets (regrets / served events) rather than -/// the raw count so that the penalty is comparable across eval sets of -/// different sizes. -/// -/// Weight = 0.5 was chosen so that a 100 % regret rate (pathological) -/// shifts the objective by −0.5, roughly equivalent to a 0.5-rank MRR -/// drop. At realistic rates (< 10 %) the penalty is < 0.05 — meaningful -/// signal without overwhelming the MRR term. -pub const REGRET_PENALTY_WEIGHT: f64 = 0.5; - -// ─── Sweep parameter space ──────────────────────────────────────────────────── +/// Explicit policy, not a fitted optimum: a full delivery budget costs 0.05 quality units. +pub const DEFAULT_COST_LAMBDA: f64 = 0.05; +pub const DEFAULT_COST_WEIGHT: f64 = DEFAULT_COST_LAMBDA / 6000.0; pub const LEXICAL_FLOORS: &[f32] = &[0.3, 0.4, 0.5, 0.6]; pub const SEMANTIC_FLOORS: &[f32] = &[-1.0, 0.0, 0.25, 0.35, 0.45]; @@ -123,7 +111,7 @@ pub struct ComboResult { pub combo: TuneCombo, pub mean_mrr: f64, pub mean_tokens: f64, - /// mean_mrr − cost_weight * mean_tokens + /// quality minus per-unit cost weight times final serialized UTF-8 bound pub objective: f64, } @@ -142,6 +130,9 @@ pub struct TuneHistoryEntry { /// `None` for history entries written before S2 (backward compat). #[serde(default, skip_serializing_if = "Option::is_none")] pub memory_count_at_tune: Option, + /// None marks historical objectives whose units/policy were not recorded. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub measurement: Option, } // ─── S2.1: Re-tune trigger state ───────────────────────────────────────────── @@ -220,7 +211,7 @@ pub fn compute_retune_trigger( )?; let recent_served_count: u64 = conn.query_row( - "SELECT COUNT(*) FROM events WHERE kind = 'context.served' AND ts >= ?1", + "SELECT COUNT(*) FROM events WHERE ts>=?1 AND kind=CASE WHEN EXISTS(SELECT 1 FROM events WHERE kind='context.injected' AND ts>=?1) THEN 'context.injected' ELSE 'context.served' END", rusqlite::params![cutoff_iso], |r| r.get(0), )?; @@ -343,40 +334,27 @@ pub fn compute_model_advisor( /// Compute the tuning objective for a combo result. /// -/// `objective = mean_mrr - cost_weight * mean_tokens` +/// `objective = quality - cost_weight * mean_final_bound`; legacy callers retain +/// their explicit per-unit coefficient. CLI uses DEFAULT_COST_WEIGHT. pub fn compute_objective(mean_mrr: f64, mean_tokens: f64, cost_weight: f64) -> f64 { mean_mrr - cost_weight * mean_tokens } -/// S2.3: Compute the tuning objective with a regret penalty term. -/// -/// Extended objective: -/// ```text -/// objective = mean_mrr -/// - cost_weight * mean_tokens -/// - REGRET_PENALTY_WEIGHT * regret_rate -/// ``` -/// -/// `regret_rate` = regrets_for_this_combo / total_served_events. -/// A floor configuration that drops capsules later cited by the model -/// incurs a higher `regret_rate` and is penalised. -/// -/// Weight: [`REGRET_PENALTY_WEIGHT`] = 0.5 — see module docs for -/// calibration rationale. +/// Compatibility wrapper. Historical regret is diagnostic only: it is not +/// candidate-specific evidence and cannot affect candidate scores. pub fn compute_objective_with_regret( - mean_mrr: f64, - mean_tokens: f64, + quality: f64, + mean_bound: f64, cost_weight: f64, - regret_rate: f64, + _regret_rate: f64, ) -> f64 { - mean_mrr - cost_weight * mean_tokens - REGRET_PENALTY_WEIGHT * regret_rate + compute_objective(quality, mean_bound, cost_weight) } /// Count `retrieval.regret` events in `conn` within an optional ISO-8601 /// timestamp window `[since, until]`. /// -/// Used by the sweep to collect regret signal per evaluation window so the -/// objective function can penalise floor configs that generated regrets. +/// Historical diagnostic and retune trigger only; never a candidate objective term. pub fn count_regret_events( conn: &rusqlite::Connection, since: Option<&str>, @@ -413,11 +391,11 @@ pub fn count_regret_events( /// Split cases into (train, holdout) using a deterministic seed derived from /// `case_count`. 80 % train, 20 % holdout. Indices into `cases` are returned. /// -/// The split is stable: the same set of N cases always produces the same -/// train/holdout partition regardless of case order. +/// Legacy index-only API. Evaluation uses grouped_train_holdout_split, which +/// accepts case identities and prevents family leakage. pub fn train_holdout_split(case_count: usize) -> (Vec, Vec) { - if case_count == 0 { - return (Vec::new(), Vec::new()); + if case_count < 2 { + return ((0..case_count).collect(), Vec::new()); } let holdout_size = (case_count / 5).max(1); // ≥1 holdout // Deterministic: pick every 5th index as holdout. @@ -427,6 +405,85 @@ pub fn train_holdout_split(case_count: usize) -> (Vec, Vec) { (train, holdout) } +/// Connected components join aliases sharing task family, normalized query, +/// or any relevant/stale ID. Sorting component identities makes the 80/20 split +/// deterministic under input permutations. One component has no holdout. +#[derive(Debug)] +pub struct FamilySplit { + pub train: Vec, + pub holdout: Vec, + pub family_count: usize, +} +pub fn grouped_train_holdout_split(cases: &[crate::eval::EvalCase]) -> FamilySplit { + use std::collections::{BTreeMap, BTreeSet}; + let mut parents: Vec = (0..cases.len()).collect(); + fn root(parents: &[usize], mut i: usize) -> usize { + while parents[i] != i { + i = parents[i] + } + i + } + let mut owners = BTreeMap::::new(); + let keys: Vec> = cases + .iter() + .map(|c| { + let mut keys = BTreeSet::new(); + keys.insert(format!( + "q:{}", + c.query + .split_whitespace() + .collect::>() + .join(" ") + .to_lowercase() + )); + if !c.family.trim().is_empty() { + keys.insert(format!("f:{}", c.family.trim())); + } + for id in c.relevant.iter().chain(&c.stale) { + keys.insert(format!("m:{id}")); + } + keys + }) + .collect(); + for (i, case_keys) in keys.iter().enumerate() { + for key in case_keys { + if let Some(&other) = owners.get(key) { + let a = root(&parents, i); + let b = root(&parents, other); + parents[a] = b; + } else { + owners.insert(key.clone(), i); + } + } + } + let mut components = BTreeMap::, Vec)>::new(); + for (i, case_keys) in keys.into_iter().enumerate() { + let entry = components.entry(root(&parents, i)).or_default(); + entry + .0 + .extend(case_keys.into_iter().filter(|k| !k.starts_with("m:"))); + entry.1.push(i); + } + let mut groups: Vec<_> = components.into_values().collect(); + groups.sort_by(|a, b| a.0.cmp(&b.0)); + let count = groups.len(); + let mut split = FamilySplit { + train: Vec::new(), + holdout: Vec::new(), + family_count: count, + }; + for (i, (_, ids)) in groups.into_iter().enumerate() { + if count > 1 && i % 5 == 0 { + split.holdout.extend(ids) + } else { + split.train.extend(ids) + } + } + split.train.sort_unstable(); + split.holdout.sort_unstable(); + split +} + /// Select the best combo from a slice of `ComboResult` by objective score. /// Returns `None` when the slice is empty. pub fn select_winner(results: &[ComboResult]) -> Option<&ComboResult> { @@ -542,6 +599,63 @@ mod tests { assert!(holdout.is_empty()); } + #[test] + fn single_family_cannot_supply_an_independent_holdout() { + let (train, holdout) = train_holdout_split(1); + assert_eq!(train, vec![0]); + assert!(holdout.is_empty()); + } + + #[test] + fn split_membership_is_independent_of_input_order() { + let queries = ["alpha", "bravo", "charlie", "delta", "echo"]; + let mut reversed = queries; + reversed.reverse(); + let held = |q: &[&str]| { + let cases: Vec<_> = q + .iter() + .map(|query| { + serde_json::from_value(serde_json::json!({"query":query,"relevant":[]})) + .unwrap() + }) + .collect(); + let indexes = grouped_train_holdout_split(&cases).holdout; + indexes + .into_iter() + .map(|i| q[i].to_string()) + .collect::>() + }; + assert_eq!(held(&queries), held(&reversed)); + } + + #[test] + fn overlapping_aliases_and_task_families_never_leak_into_holdout() { + let cases: Vec = serde_json::from_value(serde_json::json!([ + {"query":"a","relevant":["one"]}, + {"query":"b","relevant":["two"],"stale":["one"]}, + {"query":"c","relevant":["two"],"family":"task"}, + {"query":"d","relevant":[],"family":"task"}, + {"query":"e","relevant":[]} + ])) + .unwrap(); + let split = grouped_train_holdout_split(&cases); + assert_eq!(split.family_count, 2); + let in_holdout = split.holdout.contains(&0); + for i in 1..4 { + assert_eq!(split.holdout.contains(&i), in_holdout); + } + let one = grouped_train_holdout_split(&cases[..4]); + assert!(one.holdout.is_empty()); + assert_eq!(one.train.len(), 4); + } + + #[test] + fn explicit_default_cost_policy_uses_budget_fraction_units() { + let score = compute_objective(0.75, 512.0, DEFAULT_COST_WEIGHT); + assert!((score - 0.7457333333333333).abs() < 1e-12); + assert_eq!(compute_objective(1.0, 6000.0, DEFAULT_COST_WEIGHT), 0.95); + } + #[test] fn select_winner_picks_highest_objective() { let combos = vec![ @@ -596,6 +710,7 @@ mod tests { holdout_mrr: 0.70, baseline_holdout_objective: 0.45, memory_count_at_tune: None, + measurement: None, }; append_tune_history(&tmp, entry.clone()).unwrap(); @@ -628,29 +743,17 @@ mod tests { } #[test] - fn compute_objective_with_regret_penalises_high_rate() { + fn historical_regret_cannot_change_candidate_objective() { let base = compute_objective(0.75, 500.0, 0.005); let with_regret = compute_objective_with_regret(0.75, 500.0, 0.005, 0.10); - // penalty = 0.5 * 0.10 = 0.05 - assert!( - with_regret < base, - "positive regret_rate must reduce the objective" - ); - assert!( - (base - with_regret - REGRET_PENALTY_WEIGHT * 0.10).abs() < 1e-9, - "penalty term must equal REGRET_PENALTY_WEIGHT * regret_rate" - ); + assert_eq!(with_regret, base); } #[test] - fn compute_objective_with_regret_full_rate_shifts_by_weight() { - // regret_rate = 1.0 → penalty = REGRET_PENALTY_WEIGHT + fn even_full_historical_regret_is_diagnostic_only() { let base = compute_objective(0.8, 0.0, 0.0); let with_full = compute_objective_with_regret(0.8, 0.0, 0.0, 1.0); - assert!( - (base - with_full - REGRET_PENALTY_WEIGHT).abs() < 1e-9, - "100% regret rate shifts objective by REGRET_PENALTY_WEIGHT" - ); + assert_eq!(with_full, base); } // ─── S2.1: RetuneTriggerState ───────────────────────────────────────────── @@ -716,6 +819,7 @@ mod tests { holdout_mrr: 0.7, baseline_holdout_objective: 0.45, memory_count_at_tune: Some(0), + measurement: None, }; append_tune_history(&paths.kimetsu_dir, entry).expect("append"); @@ -754,7 +858,7 @@ mod tests { let run_id = RunId::new(); let served_ev = Event::new( run_id, - "context.served", + "context.injected", serde_json::json!({"query_hash":"abc","capsule_count":1,"skipped":false}), ); projector::apply_events(&conn, &[served_ev]).expect("seed served"); @@ -858,6 +962,7 @@ mod tests { holdout_mrr: 0.70, baseline_holdout_objective: 0.45, memory_count_at_tune: Some(123), + measurement: None, }; append_tune_history(&tmp, entry).unwrap(); diff --git a/crates/kimetsu-brain/src/tuneset.rs b/crates/kimetsu-brain/src/tuneset.rs index 67ab004..7970832 100644 --- a/crates/kimetsu-brain/src/tuneset.rs +++ b/crates/kimetsu-brain/src/tuneset.rs @@ -1,172 +1,92 @@ -//! v1.5: personal eval-set builder for `kimetsu brain tune`. -//! -//! Walks `context.served` events that carry a raw `query` field (present when -//! `store_queries = true` in `project.toml`). Joins to `memory_citations` via -//! `session_id` (exact match) or, when session_id is absent, a ±30-minute -//! time window. A served event becomes a POSITIVE eval case when ≥1 citation -//! occurred in that window. Zero-citation served events are counted as noise -//! (used only for cost statistics; they do NOT appear in the `cases` vec). -//! -//! Deduplication: when the same query text appears multiple times (the same -//! task is worked on across sessions), only the latest served event is kept. - -use rusqlite::Connection; -use time::OffsetDateTime; -use time::format_description::well_known::Rfc3339; - +//! Personal weak reliance labels from exact delivered exposure + claim revision. +//! Uncited observations are unknown, never negative gold. Raw queries exist only +//! after learning.store_queries opt-in at the producer. Legacy time joins are ignored. use crate::eval::EvalCase; use kimetsu_core::KimetsuResult; +use rusqlite::Connection; +#[cfg(test)] +use time::{OffsetDateTime, format_description::well_known::Rfc3339}; -/// Output of [`build_personal_eval`]. #[derive(Debug, Clone, Default)] pub struct PersonalEval { - /// Positive eval cases (query + relevant memory ids). + /// Weak reliance labels, not verified relevance. pub cases: Vec, - /// Number of served events with zero subsequent citations (noise pool). + /// Compatibility name: count of unknown uncited/unusable observations, NOT noise gold. pub noise_count: usize, - /// RFC-3339 timestamp of the oldest positive served event, if any. pub oldest: Option, - /// RFC-3339 timestamp of the newest positive served event, if any. pub newest: Option, } -/// Build a personal eval set from the events already in `conn`. -/// -/// Parameters: -/// - `window_secs`: maximum seconds between a served event and a citation for -/// them to be considered linked when no `session_id` is available (default 1800 = 30 min). -pub fn build_personal_eval(conn: &Connection, window_secs: i64) -> KimetsuResult { - // 1. Collect served events that carry a query. - let mut stmt = conn.prepare( - "SELECT payload_json, ts FROM events - WHERE kind = 'context.served' - ORDER BY ts DESC", - )?; - let rows = stmt.query_map([], |row| { - Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?)) +/// window_secs is retained for source compatibility and intentionally ignored. +pub fn build_personal_eval(conn: &Connection, _window_secs: i64) -> KimetsuResult { + let mut stmt = conn.prepare("SELECT event_id,run_id,payload_json,ts FROM events WHERE kind='context.injected' ORDER BY ts DESC,event_id DESC")?; + let rows = stmt.query_map([], |r| { + Ok(( + r.get::<_, String>(0)?, + r.get::<_, String>(1)?, + r.get::<_, String>(2)?, + r.get::<_, String>(3)?, + )) })?; - - // Dedup by query text: keep latest ts for each unique query. - let mut seen_queries: std::collections::HashMap = - std::collections::HashMap::new(); + let mut seen = std::collections::HashSet::new(); + let mut result = PersonalEval::default(); for row in rows { - let (payload_json, ts) = row?; - let payload: serde_json::Value = - serde_json::from_str(&payload_json).unwrap_or(serde_json::Value::Null); - let Some(query) = payload.get("query").and_then(|v| v.as_str()) else { - continue; // no raw query stored → skip + let (exposure, run, payload, ts) = row?; + let payload: serde_json::Value = serde_json::from_str(&payload)?; + let Some(query) = payload["query"].as_str().filter(|q| !q.trim().is_empty()) else { + continue; }; - if !seen_queries.contains_key(query) { - seen_queries.insert(query.to_string(), (ts, payload)); + if !seen.insert(query.to_string()) { + continue; } - } - - if seen_queries.is_empty() { - return Ok(PersonalEval::default()); - } - - // 2. For each unique served event, find citations in-window. - let mut cases: Vec = Vec::new(); - let mut noise_count = 0usize; - let mut oldest: Option = None; - let mut newest: Option = None; - - for (query, (ts, payload)) in &seen_queries { - let session_id = payload.get("session_id").and_then(|v| v.as_str()); - - // Find citation memory_ids linked to this served event. - let relevant = citations_for_served(conn, ts, session_id, window_secs)?; - - if relevant.is_empty() { - noise_count += 1; - } else { - // Track oldest/newest timestamps. - if oldest.as_deref().map(|o| ts.as_str() < o).unwrap_or(true) { - oldest = Some(ts.clone()); - } - if newest.as_deref().map(|n| ts.as_str() > n).unwrap_or(true) { - newest = Some(ts.clone()); + let mut citations = conn.prepare("SELECT payload_json FROM events WHERE kind='memory.cited' AND run_id=?1 AND json_extract(payload_json,'$.exposure_id')=?2 AND json_extract(payload_json,'$.evidence_kind')='reliance'")?; + let cited = + citations.query_map(rusqlite::params![run, exposure], |r| r.get::<_, String>(0))?; + let mut relevant = std::collections::BTreeSet::new(); + for cited in cited { + let cited: serde_json::Value = serde_json::from_str(&cited?)?; + let Some(id) = cited["memory_id"].as_str() else { + continue; + }; + let Some(revision) = cited["revision_event_id"].as_str() else { + continue; + }; + if payload["memory_revisions"][id].as_str() != Some(revision) + || !payload["memory_ids"] + .as_array() + .is_some_and(|ids| ids.iter().any(|x| x.as_str() == Some(id))) + || crate::projector::claim_revision_at(conn, id, None)? != revision + { + continue; } - cases.push(EvalCase { - query: query.clone(), - relevant, - kind: Default::default(), - stale: Vec::new(), - }); + relevant.insert(id.to_string()); } - } - - Ok(PersonalEval { - cases, - noise_count, - oldest, - newest, - }) -} - -/// Collect distinct memory_ids cited in-window relative to a served event. -/// -/// Strategy: -/// 1. If session_id is present: find all `memory.cited` events whose -/// payload `session_id` matches. (Citation events emitted by the MCP -/// `kimetsu_brain_cite` tool carry `session_id` in their payload.) -/// → NOT currently stored there; fall through to time-window. -/// 2. Time-window fallback: find `memory_citations` rows whose `cited_at` -/// falls within [ts − window_secs, ts + window_secs]. -/// -/// Note on design: `memory_citations` has `cited_at` (RFC-3339 text) and -/// `run_id`. We cannot reliably join on `run_id` for MCP cites (sentinel -/// run_id shared by all). So the primary join key is `session_id` from the -/// event payload when available; otherwise the time window is used. -fn citations_for_served( - conn: &Connection, - served_ts: &str, - session_id: Option<&str>, - window_secs: i64, -) -> KimetsuResult> { - // Try session_id join first (citations from the same Claude Code session). - if let Some(sid) = session_id { - let mut stmt = conn.prepare( - "SELECT DISTINCT mc.memory_id - FROM memory_citations mc - JOIN events e ON e.run_id = mc.run_id - AND e.kind = 'memory.cited' - WHERE json_extract(e.payload_json, '$.session_id') = ?1", - )?; - let ids: Vec = stmt - .query_map([sid], |row| row.get(0))? - .filter_map(|r| r.ok()) - .collect(); - if !ids.is_empty() { - return Ok(ids); + if relevant.is_empty() { + result.noise_count += 1; + continue; + } + if result.oldest.as_ref().is_none_or(|old| &ts < old) { + result.oldest = Some(ts.clone()) + } + if result.newest.as_ref().is_none_or(|new| &ts > new) { + result.newest = Some(ts.clone()) } - // Fall through: session_id join found nothing (MCP cites without session_id - // in payload, or old agent cites) — use time window below. + result.cases.push(EvalCase { + query: query.into(), + relevant: relevant.into_iter().collect(), + family: payload["task_id"] + .as_str() + .filter(|s| !s.trim().is_empty()) + .unwrap_or("") + .into(), + kind: Default::default(), + stale: Vec::new(), + }); } - - // Time-window fallback: parse the served ts, compute bounds. - let served_dt = OffsetDateTime::parse(served_ts, &Rfc3339) - .map_err(|e| format!("parse served_ts {served_ts:?}: {e}"))?; - let lo = (served_dt - time::Duration::seconds(window_secs)) - .format(&Rfc3339) - .map_err(|e| format!("format lo: {e}"))?; - let hi = (served_dt + time::Duration::seconds(window_secs)) - .format(&Rfc3339) - .map_err(|e| format!("format hi: {e}"))?; - - let mut stmt = conn.prepare( - "SELECT DISTINCT memory_id FROM memory_citations - WHERE cited_at >= ?1 AND cited_at <= ?2", - )?; - let ids: Vec = stmt - .query_map([&lo, &hi], |row| row.get(0))? - .filter_map(|r| r.ok()) - .collect(); - Ok(ids) + result.cases.sort_by(|a, b| a.query.cmp(&b.query)); + Ok(result) } -// ─── Unit tests ─────────────────────────────────────────────────────────────── - #[cfg(test)] mod tests { use super::*; @@ -283,11 +203,9 @@ mod tests { seed_memory_cited(&conn, &mid, 5 * 60); let eval = build_personal_eval(&conn, 1800).expect("build"); - assert_eq!(eval.cases.len(), 1, "should have 1 positive case"); - assert_eq!(eval.cases[0].query, "find files fast"); assert!( - eval.cases[0].relevant.contains(&mid), - "memory_id must be in relevant" + eval.cases.is_empty(), + "nearby legacy citations are not linked evidence" ); assert_eq!(eval.noise_count, 0); std::fs::remove_dir_all(&root).ok(); @@ -314,7 +232,10 @@ mod tests { 0, "out-of-window citation → no positive case" ); - assert_eq!(eval.noise_count, 1, "should count 1 noise entry"); + assert_eq!( + eval.noise_count, 0, + "legacy retrieval is not a canonical exposure" + ); std::fs::remove_dir_all(&root).ok(); }); } @@ -337,8 +258,66 @@ mod tests { let eval = build_personal_eval(&conn, 1800).expect("build"); // Dedup: both map to same query string → one case (the latest ts kept). - assert_eq!(eval.cases.len(), 1, "dedup must produce exactly 1 case"); + assert_eq!( + eval.cases.len(), + 0, + "legacy time joins cannot produce labels" + ); std::fs::remove_dir_all(&root).ok(); }); } + + #[test] + fn exact_exposure_citation_labels_only_its_query_and_current_claim() { + with_user_brain_disabled(|| { + let root = test_root(); + init_project(&root, false).unwrap(); + let mid = add_memory( + &root, + MemoryScope::Project, + MemoryKind::Fact, + "the durable label", + ) + .unwrap(); + let (_, _, conn) = crate::project::load_project(&root).unwrap(); + let revision = projector::claim_revision_at(&conn, &mid, None).unwrap(); + let exposed = |query: &str| { + Event::new( + RunId::new(), + "context.injected", + serde_json::json!({ + "query":query,"memory_ids":[mid],"memory_revisions":{mid.clone():revision}, + "session_id":"shared-session","task_id":"shared-task" + }), + ) + }; + let a = exposed("cited query"); + let b = exposed("uncited query"); + crate::project::record_context_exposure(&root, &a).unwrap(); + crate::project::record_context_exposure(&root, &b).unwrap(); + crate::project::record_exposure_citation( + &root, + &a.event_id.to_string(), + &mid, + Some("used"), + ) + .unwrap(); + let eval = build_personal_eval(&conn, 1800).unwrap(); + assert_eq!(eval.cases.len(), 1); + assert_eq!(eval.cases[0].query, "cited query"); + assert_eq!(eval.cases[0].relevant, vec![mid.clone()]); + assert_eq!( + eval.noise_count, 1, + "uncited is unknown, never a negative gold label" + ); + crate::project::edit_memory(&root, &mid, Some("a corrected proposition"), None) + .unwrap(); + let revised = build_personal_eval(&conn, 1800).unwrap(); + assert!( + revised.cases.is_empty(), + "old reliance cannot label a corrected proposition" + ); + assert_eq!(revised.noise_count, 2); + }); + } } diff --git a/crates/kimetsu-chat/src/mcp_server.rs b/crates/kimetsu-chat/src/mcp_server.rs index d1fc9ad..ae20bbd 100644 --- a/crates/kimetsu-chat/src/mcp_server.rs +++ b/crates/kimetsu-chat/src/mcp_server.rs @@ -681,13 +681,38 @@ fn take_session_warm_start(workspace: &Path, arguments: &Value) -> Option Value { - brain_context_tool_with_warm( + stdio_brain_context_with_loader( workspace, arguments, - None, - take_session_warm_start(workspace, arguments), + kimetsu_brain::embeddings::open_cached_reranker, ) - .unwrap_or_else(|e| { +} + +fn stdio_brain_context_with_loader( + workspace: &Path, + arguments: &Value, + load: impl FnOnce( + &str, + ) + -> Result>, String>, +) -> Value { + let result = (|| -> Result { + let paths = + kimetsu_core::paths::ProjectPaths::discover(workspace).map_err(|e| e.to_string())?; + let config = project::load_config(&paths).map_err(|e| e.to_string())?; + let reranker = if kimetsu_brain::embeddings::reranker_is_off(&config.embedder.reranker) { + None + } else { + load(&config.embedder.reranker)? + }; + brain_context_tool_with_warm( + workspace, + arguments, + reranker.as_deref(), + take_session_warm_start(workspace, arguments), + ) + })(); + result.unwrap_or_else(|e| { bounded_context_error(arguments, 6000, brain_unavailable_json(workspace, &e)) }) } @@ -731,11 +756,11 @@ fn record_context_delivery( /// Candidate pool the remote reranker judges before truncating to the caller's /// cap. Mirrors `RERANK_POOL` in `kimetsu-cli/src/embed_daemon/server.rs`. -pub const REMOTE_RERANK_POOL: usize = 6; +pub const REMOTE_RERANK_POOL: usize = kimetsu_brain::serving::RERANK_POOL; /// Sigmoid-score floor for the remote reranker — capsules scored below this /// are noise. Mirrors `RERANK_FLOOR` in `kimetsu-cli/src/embed_daemon/server.rs`. -pub const REMOTE_RERANK_FLOOR: f32 = 0.30; +pub const REMOTE_RERANK_FLOOR: f32 = kimetsu_brain::serving::RERANK_FLOOR; /// Transport-agnostic body of the `kimetsu_brain_context` tool. /// @@ -826,98 +851,57 @@ fn brain_context_tool_with_warm( config_ambient, ); - // When reranking, over-fetch a larger candidate pool so the cross-encoder - // sees enough diversity before truncating to `cap`, and bump the token - // budget so the pool isn't starved. Same logic as the embed daemon. - let (fetch_cap, fetch_budget) = if reranker.is_some() { - (cap.max(REMOTE_RERANK_POOL), budget_tokens.max(6000)) - } else { - (cap, budget_tokens) + let policy = kimetsu_brain::serving::ServingPolicy { + budget: budget_tokens, + cap, + ..Default::default() }; - let request = ContextRequest { stage: stage.to_string(), - query: effective_query.clone(), - budget_tokens: fetch_budget, + query: effective_query, tags, min_score, - max_capsules: fetch_cap, prefer_roles, + min_semantic_score_override: arguments + .get("min_semantic_score") + .and_then(Value::as_f64) + .map(|v| v as f32), + min_lexical_coverage_override: arguments + .get("min_lexical_coverage") + .and_then(Value::as_f64) + .map(|v| v as f32), + abstain_evidence_override: arguments + .get("abstain_evidence") + .and_then(Value::as_f64) + .map(|v| v as f32), ..Default::default() }; - - match project::retrieve_context_readonly_with_request(workspace, request) { - // v2.7: rerank + evidence-band arbitration before the skipped check — - // a band bundle the cross-encoder rejects becomes a skipped bundle - // here, taking the same zero-token path a hard-gated retrieval does. - Ok(bundle) => { - let abstain = kimetsu_core::paths::ProjectPaths::discover(workspace) - .ok() - .and_then(|paths| project::load_config(&paths).ok()) - .map(|cfg| kimetsu_brain::project::resolved_abstain_evidence_for(&cfg)) - .unwrap_or(0.0); - let bundle = kimetsu_brain::context::rerank_and_arbitrate( - &effective_query, - bundle, - reranker, - abstain, - REMOTE_RERANK_FLOOR, - cap, - ); - let mut bundle = bundle; - - // v1.5 (Story 2.1): render-time compression. Load compress_capsules - // best-effort — any config error means no compression (safe default). - // Ranking is NEVER affected; this runs after retrieval + reranking. - let compress = kimetsu_core::paths::ProjectPaths::discover(workspace) - .ok() - .and_then(|paths| project::load_config(&paths).ok()) - .map(|cfg| cfg.broker.compress_capsules) - .unwrap_or(false); - if compress { - use kimetsu_brain::context::compress_for_render; - for capsule in &mut bundle.capsules { - capsule.summary = compress_for_render(&capsule.summary, 3); - } - } - - use kimetsu_brain::context::delivery::{ - add_optional_field, compact_capsules, fit_json, - }; - let exposure = kimetsu_core::event::Event::new( - kimetsu_core::ids::RunId::new(), - "context.injected", - json!({}), - ); - let count = bundle.capsules.len(); - let mut delivery = fit_json(bundle.capsules.clone(), budget_tokens, |capsules| { - json!({ - "ok": true, - "skipped": capsules.is_empty(), - "exposure_id": exposure.event_id.to_string(), - "capsule_count": capsules.len(), - "excluded_count": bundle.excluded.len() + count - capsules.len(), - "capsules": compact_capsules(capsules), - "partial_evidence": bundle.evidence_coverage < 1.0 || capsules.len() < count, - }) - }); - if let Some(block) = warm_start { - add_optional_field( - &mut delivery, - "warm_start", - json!({"context":block}), - budget_tokens, - ); - } - record_context_delivery(workspace, arguments, &delivery, "brain_context", exposure); - Ok(delivery.payload) - } - Err(err) => Ok(bounded_context_error( - arguments, - 6000, - brain_unavailable_json(workspace, &err.to_string()), - )), + let session = kimetsu_brain::project::BrainSession::open_readonly(workspace) + .map_err(|e| e.to_string())?; + let exposure = kimetsu_core::event::Event::new( + kimetsu_core::ids::RunId::new(), + "context.injected", + json!({}), + ); + let mut delivery = policy + .retrieve( + &session, + request, + kimetsu_brain::embeddings::open_embedder_for(session.config().embedder.enabled), + reranker, + &exposure.event_id.to_string(), + ) + .map_err(|e| e.to_string())?; + if let Some(block) = warm_start { + kimetsu_brain::context::delivery::add_optional_field( + &mut delivery, + "warm_start", + json!({"context":block}), + budget_tokens, + ); } + record_context_delivery(workspace, arguments, &delivery, "brain_context", exposure); + Ok(delivery.payload) } /// v0.6: general-purpose capture tool. Records a concrete, reusable @@ -2000,6 +1984,9 @@ fn tool_definitions() -> Value { }, "budget_tokens": { "type": "integer", "minimum": 0, "maximum": 30000, "description": "Final serialized MCP content budget, accounted as a conservative UTF-8 byte upper bound. Impossible tiny budgets return budget_too_small with the actual response bound." }, "min_score": { "type": "number", "minimum": 0.0, "maximum": 1.0, "description": "Skip threshold — if the best capsule scores below this, return no capsules. Response framing still consumes tokens. Default 0.15." }, + "min_semantic_score": {"type":"number","minimum":-1.0,"maximum":1.0,"description":"Omit to inherit config; 0 explicitly disables; -1 selects model auto; positive sets cosine floor."}, + "min_lexical_coverage": {"type":"number","minimum":0.0,"maximum":1.0,"description":"Omit to inherit config; 0 explicitly disables; positive sets lexical coverage floor."}, + "abstain_evidence": {"type":"number","minimum":-1.0,"maximum":1.0,"description":"Omit to inherit config; 0 explicitly disables; -1 selects model auto; positive sets evidence floor."}, "max_capsules": { "type": "integer", "minimum": 1, "maximum": 20, "description": "Hard cap on returned capsules. Default 3." }, "tags": { "type": "array", "items": { "type": "string" }, "description": "Domain-hint tags. Capsules whose text contains any of these get a 1.4× score boost." }, "prefer_roles": { "type": "array", "items": { "type": "string" }, "description": "Boost capsules whose kind matches (e.g. [\"semantic_operator\",\"anti_pattern\"] for bench use)." } @@ -2512,10 +2499,17 @@ mod tests { fn context_tool_catalog_advertises_episode_identity_lanes() { let definitions = super::tool_definitions(); for name in ["kimetsu_brain_context", "kimetsu_benchmark_context"] { - let tool = definitions.as_array().unwrap().iter().find(|t|t["name"] == name).unwrap(); + let tool = definitions + .as_array() + .unwrap() + .iter() + .find(|t| t["name"] == name) + .unwrap(); for field in ["task_id", "session_id", "worktree_id"] { - assert_eq!(tool["inputSchema"]["properties"][field]["type"], "string", - "{name} must advertise the supported {field} lane"); + assert_eq!( + tool["inputSchema"]["properties"][field]["type"], "string", + "{name} must advertise the supported {field} lane" + ); } } } @@ -2912,6 +2906,57 @@ mod tests { }); } + #[test] + fn stdio_uses_configured_reranker_off_and_initialization_error_explicitly() { + kimetsu_brain::user_brain::with_user_brain_disabled(|| { + let root = temp_root("stdio-configured-reranker"); + fs::create_dir_all(&root).unwrap(); + project::init_project(&root, false).unwrap(); + project::add_memory( + &root, + MemoryScope::Project, + MemoryKind::Fact, + "ripgrep search files efficiently", + ) + .unwrap(); + let paths = kimetsu_core::paths::ProjectPaths::discover(&root).unwrap(); + let mut config = project::load_config(&paths).unwrap(); + config.embedder.reranker = "chosen-model".into(); + config.retrieval.level = "custom".into(); + fs::write( + &paths.project_toml, + toml::to_string_pretty(&config).unwrap(), + ) + .unwrap(); + let arguments = + json!({"query":"ripgrep search","include_ambient":false,"budget_tokens":6000}); + let result = stdio_brain_context_with_loader(&root, &arguments, |id| { + assert_eq!(id, "chosen-model"); + Ok(Some(std::sync::Arc::new( + kimetsu_brain::embeddings::StubReranker, + ))) + }); + assert_eq!(result["capsule_count"], 1); + let failed = stdio_brain_context_with_loader(&root, &arguments, |_| { + Err("model initialization failed".into()) + }); + assert!(failed.get("error").is_some()); + assert!(failed.to_string().contains("model initialization failed")); + config.embedder.reranker = "off".into(); + config.retrieval.level = "deep".into(); + fs::write( + &paths.project_toml, + toml::to_string_pretty(&config).unwrap(), + ) + .unwrap(); + let off = stdio_brain_context_with_loader(&root, &arguments, |_| { + panic!("explicit off must bypass loader") + }); + assert_eq!(off["ok"], true); + assert_eq!(off["capsule_count"], 1); + }); + } + #[test] fn benchmark_context_returns_playbook_and_enforces_task_memory() { // v0.4.1: this test writes a GlobalUser benchmark memory and diff --git a/crates/kimetsu-cli/src/commands/bench.rs b/crates/kimetsu-cli/src/commands/bench.rs index 6154d20..3d23299 100644 --- a/crates/kimetsu-cli/src/commands/bench.rs +++ b/crates/kimetsu-cli/src/commands/bench.rs @@ -136,7 +136,7 @@ pub(crate) fn brain_eval_inner(args: EvalArgs) -> KimetsuResult<()> { embedder: &dyn kimetsu_brain::embeddings::Embedder, reranker: Option<&dyn kimetsu_brain::embeddings::Reranker>, pool: usize, - rerank_floor: f32, + _rerank_floor: f32, rerank_cap: usize| -> KimetsuResult<(Vec>, u128)> { let session = BrainSession::open_readonly(&tmp_root) @@ -145,27 +145,29 @@ pub(crate) fn brain_eval_inner(args: EvalArgs) -> KimetsuResult<()> { let t0 = Instant::now(); let mut per_case_ranked: Vec> = Vec::new(); - for (ci, case) in fixture.cases.iter().enumerate() { - let fetch_cap = pool; + for (ci, _case) in fixture.cases.iter().enumerate() { + let policy = kimetsu_brain::serving::ServingPolicy { + pool, + cap: if rerank_cap == 0 { + kimetsu_brain::serving::DEFAULT_CAP + } else { + rerank_cap + }, + ..Default::default() + }; let request = ContextRequest { - stage: "localization".to_string(), + stage: "localization".into(), query: retrieval_queries[ci].clone(), - budget_tokens: 6000, - max_capsules: fetch_cap, - min_semantic_score: 0.0, // disable floor for eval recall - min_lexical_coverage: 0.0, // disable floor for eval recall + min_score: 0.15, ..Default::default() }; - let mut bundle = session - .retrieve_context_with_injected_embedder(request, embedder) - .map_err(|e| format!("{mode_label} retrieve: {e}"))?; - - // Apply reranker when present. - if let Some(rr) = reranker { - bundle.capsules = - rerank_capsules(&case.query, bundle.capsules, rr, rerank_floor, rerank_cap); - } - + let bundle = policy.retrieve( + &session, + request, + embedder, + reranker, + kimetsu_brain::serving::EVAL_EXPOSURE_ID, + )?; // Map capsule expansion_handle "memory:" → fixture key. let ranked_keys: Vec = bundle .capsules @@ -190,24 +192,27 @@ pub(crate) fn brain_eval_inner(args: EvalArgs) -> KimetsuResult<()> { // for pool-size experiments. let pool = args.pool.max(1); let rerank_floor = 0.30f32; - let rerank_cap = 4usize; + let rerank_cap = kimetsu_brain::serving::DEFAULT_CAP; print!("running fts mode..."); let (fts_ranked, fts_ms) = run_mode("fts", &NoopEmbedder, None, pool, 0.0, 0)?; println!(" done ({fts_ms} ms)"); print!("running semantic mode (loading embedder)..."); - let semantic_embedder = open_embedder_for_model("bge-small-en-v1.5"); - let (sem_ranked, sem_ms) = - run_mode("semantic", semantic_embedder.as_ref(), None, pool, 0.0, 0)?; + let semantic_embedder = kimetsu_brain::embeddings::open_default_embedder(); + if semantic_embedder.is_noop() { + return Err("semantic embedder unavailable; no semantic measurement".into()); + } + let (sem_ranked, sem_ms) = run_mode("semantic", semantic_embedder, None, pool, 0.0, 0)?; println!(" done ({sem_ms} ms)"); print!("running semantic+rerank mode (loading reranker)..."); - let reranker_opt = open_reranker_for_model("jina-reranker-v1-turbo-en"); + let reranker_opt = + kimetsu_brain::embeddings::open_reranker_checked("ms-marco-tinybert-l-2-v2")?; let reranker_ref: Option<&dyn kimetsu_brain::embeddings::Reranker> = reranker_opt.as_deref(); let (rr_ranked, rr_ms) = run_mode( "semantic+rerank", - semantic_embedder.as_ref(), + semantic_embedder, reranker_ref, pool, rerank_floor, @@ -333,30 +338,26 @@ pub(crate) fn brain_eval_inner(args: EvalArgs) -> KimetsuResult<()> { let mut rerank_times_ms: Vec = Vec::new(); for case in fixture.cases.iter() { - let request = kimetsu_brain::context::ContextRequest { - stage: "localization".to_string(), + let policy = kimetsu_brain::serving::ServingPolicy { + pool, + cap: rerank_cap, + ..Default::default() + }; + let request = ContextRequest { + stage: "localization".into(), query: case.query.clone(), - budget_tokens: 6000, - max_capsules: pool, - min_semantic_score: 0.0, - min_lexical_coverage: 0.0, + min_score: 0.15, ..Default::default() }; - let mut bundle = session - .retrieve_context_with_injected_embedder(request, semantic_embedder.as_ref()) - .map_err(|e| format!("{rr_id} retrieve: {e}"))?; - - // Time only the rerank step. let rr_start = Instant::now(); - if !eval_cases[per_case_ranked.len()].relevant.is_empty() { - bundle.capsules = - rerank_capsules(&case.query, bundle.capsules, rr, rerank_floor, rerank_cap); - rerank_times_ms.push(rr_start.elapsed().as_millis()); - } else { - // Noise case: still rerank so we get noise metric. - bundle.capsules = - rerank_capsules(&case.query, bundle.capsules, rr, rerank_floor, rerank_cap); - } + let bundle = policy.retrieve( + &session, + request, + semantic_embedder, + Some(rr), + kimetsu_brain::serving::EVAL_EXPOSURE_ID, + )?; + rerank_times_ms.push(rr_start.elapsed().as_millis()); let ranked_keys: Vec = bundle .capsules @@ -616,6 +617,7 @@ pub(crate) fn brain_bench_orchestrate(args: BrainBenchArgs) -> KimetsuResult<()> println!("output: {}", out_dir.display()); println!(); + let mut successful = std::collections::HashSet::new(); let mut combo_idx = 0usize; for &embedder in &embedders { for &reranker in &rerankers { @@ -645,6 +647,7 @@ pub(crate) fn brain_bench_orchestrate(args: BrainBenchArgs) -> KimetsuResult<()> let elapsed = t0.elapsed().as_secs_f64(); if status.success() { + successful.insert((embedder.to_string(), reranker.to_string())); println!("done ({elapsed:.1}s)"); } else { println!("FAILED (exit={status})"); @@ -693,6 +696,9 @@ pub(crate) fn brain_bench_orchestrate(args: BrainBenchArgs) -> KimetsuResult<()> let mut rows: Vec = Vec::new(); for &embedder in &embedders { for &reranker in &rerankers { + if !successful.contains(&(embedder.to_string(), reranker.to_string())) { + continue; + } let safe_emb = embedder.replace(['/', '.', ' '], "-"); let safe_rr = reranker.replace(['/', '.', ' '], "-"); let fname = format!("combo-{safe_emb}-{safe_rr}.json"); @@ -818,6 +824,27 @@ pub(crate) fn process_rss_mb(_pid: u32) -> Option { /// Remote bench: spawn kimetsu-remote, seed a temp brain, measure HTTP MCP retrieval. #[cfg(feature = "embeddings")] pub(crate) fn brain_bench_remote(args: BrainBenchArgs) -> KimetsuResult<()> { + for id in args + .embedders + .split(',') + .map(str::trim) + .filter(|s| !s.is_empty()) + { + kimetsu_brain::embeddings::canonical_embedder_id(id)?; + } + let remote_rerankers: Vec<_> = args + .rerankers + .split(',') + .map(str::trim) + .filter(|s| !s.is_empty()) + .collect(); + if remote_rerankers.len() != 1 { + return Err("remote bench requires one explicit --rerankers value per run".into()); + } + let remote_reranker = remote_rerankers[0]; + if args.pool != kimetsu_brain::serving::RERANK_POOL { + return Err("remote serving uses the canonical pool; --pool must be 6".into()); + } use kimetsu_brain::eval::EvalFixture; use kimetsu_brain::project::{add_memory, init_project}; use kimetsu_core::memory::{MemoryKind, MemoryScope}; @@ -894,7 +921,7 @@ pub(crate) fn brain_bench_remote(args: BrainBenchArgs) -> KimetsuResult<()> { .collect(); println!( - "brain bench --remote: {} embedder(s) (server reranks with --reranker default jina-tiny)", + "brain bench --remote: {} embedder(s) (server uses the explicit --rerankers selection)", embedders.len() ); println!( @@ -915,14 +942,18 @@ pub(crate) fn brain_bench_remote(args: BrainBenchArgs) -> KimetsuResult<()> { obtained: Vec, hit_at_2: bool, hit_at_4: bool, + recall_at_2: f64, + recall_at_4: f64, mrr: f64, latency_ms: u128, + final_bound: Option, error: Option, } #[derive(serde::Serialize)] struct RemoteComboResult { embedder: String, + reranker: String, seed_ms: u128, rss_after_warm_mb: Option, peak_rss_mb: Option, @@ -931,7 +962,7 @@ pub(crate) fn brain_bench_remote(args: BrainBenchArgs) -> KimetsuResult<()> { concurrent: RemoteConcurrentStats, } - #[derive(serde::Serialize)] + #[derive(Clone, serde::Serialize)] struct RemoteComboSummary { recall_at_2: f64, recall_at_4: f64, @@ -940,6 +971,15 @@ pub(crate) fn brain_bench_remote(args: BrainBenchArgs) -> KimetsuResult<()> { p95_latency_ms: f64, noise_capsules: f64, error_cases: usize, + positive_count: usize, + negative_count: usize, + negative_accuracy: Option, + false_injection_rate: Option, + mean_final_bound: Option, + cost_measurement_count: usize, + cost_unit: &'static str, + hit_at_2: Option, + hit_at_4: Option, } #[derive(serde::Serialize)] @@ -1024,6 +1064,8 @@ pub(crate) fn brain_bench_remote(args: BrainBenchArgs) -> KimetsuResult<()> { .arg(&data_dir) .arg("--token") .arg(token) + .arg("--reranker") + .arg(remote_reranker) .arg("--rate-limit") .arg("0") .env("KIMETSU_BRAIN_EMBEDDER", embedder_id) @@ -1082,100 +1124,138 @@ pub(crate) fn brain_bench_remote(args: BrainBenchArgs) -> KimetsuResult<()> { let auth_header = format!("Bearer {token}"); // Helper: call kimetsu_brain_context over HTTP, return (obtained_keys, latency_ms, error). - let call_context = |query: &str, id: u64| -> (Vec, u128, Option) { - let body = serde_json::json!({ - "jsonrpc": "2.0", - "id": id, - "method": "tools/call", - "params": { - "name": "kimetsu_brain_context", - "arguments": { - "query": query, - "budget_tokens": 6000, - "max_capsules": 4 + let call_context = + |query: &str, id: u64| -> (Vec, u128, Option, Option) { + let body = serde_json::json!({ + "jsonrpc": "2.0", + "id": id, + "method": "tools/call", + "params": { + "name": "kimetsu_brain_context", + "arguments": { + "query": query, + "budget_tokens": 6000, + "max_capsules": args.cap, + "include_ambient":false + } } - } - }); - let t0 = Instant::now(); - let resp = client - .post(&mcp_url) - .header("Authorization", &auth_header) - .header("Content-Type", "application/json") - .json(&body) - .send(); - let latency_ms = t0.elapsed().as_millis(); - - let resp = match resp { - Ok(r) => r, - Err(e) => return (vec![], latency_ms, Some(format!("HTTP error: {e}"))), - }; + }); + let t0 = Instant::now(); + let resp = client + .post(&mcp_url) + .header("Authorization", &auth_header) + .header("Content-Type", "application/json") + .json(&body) + .send(); + let latency_ms = t0.elapsed().as_millis(); + + let resp = match resp { + Ok(r) => r, + Err(e) => return (vec![], latency_ms, Some(format!("HTTP error: {e}")), None), + }; - let json: serde_json::Value = match resp.json() { - Ok(v) => v, - Err(e) => return (vec![], latency_ms, Some(format!("JSON parse error: {e}"))), - }; + let json: serde_json::Value = match resp.json() { + Ok(v) => v, + Err(e) => { + return ( + vec![], + latency_ms, + Some(format!("JSON parse error: {e}")), + None, + ); + } + }; - // Check for JSON-RPC error - if let Some(err_obj) = json.get("error") { - let msg = err_obj - .get("message") - .and_then(|m| m.as_str()) - .unwrap_or("unknown error"); - return (vec![], latency_ms, Some(format!("RPC error: {msg}"))); - } + // Check for JSON-RPC error + if let Some(err_obj) = json.get("error") { + let msg = err_obj + .get("message") + .and_then(|m| m.as_str()) + .unwrap_or("unknown error"); + return (vec![], latency_ms, Some(format!("RPC error: {msg}")), None); + } - // Parse the result: result.content[0].text → JSON string → capsules - let text = json - .get("result") - .and_then(|r| r.get("content")) - .and_then(|c| c.get(0)) - .and_then(|c| c.get("text")) - .and_then(|t| t.as_str()) - .unwrap_or(""); - - if text.is_empty() { - return (vec![], latency_ms, Some("empty text in result".to_string())); - } + // Parse the result: result.content[0].text → JSON string → capsules + let text = json + .get("result") + .and_then(|r| r.get("content")) + .and_then(|c| c.get(0)) + .and_then(|c| c.get("text")) + .and_then(|t| t.as_str()) + .unwrap_or(""); + + if text.is_empty() { + return ( + vec![], + latency_ms, + Some("empty text in result".to_string()), + None, + ); + } - let inner: serde_json::Value = match serde_json::from_str(text) { - Ok(v) => v, - Err(e) => return (vec![], latency_ms, Some(format!("inner JSON parse: {e}"))), - }; + let inner: serde_json::Value = match serde_json::from_str(text) { + Ok(v) => v, + Err(e) => { + return ( + vec![], + latency_ms, + Some(format!("inner JSON parse: {e}")), + None, + ); + } + }; - // skipped case → no capsules (intentional, not an error) - if inner - .get("skipped") - .and_then(|v| v.as_bool()) - .unwrap_or(false) - { - return (vec![], latency_ms, None); - } + let final_bound = Some(kimetsu_brain::context::delivery::serialized_output_tokens( + &inner, + )); + if inner.get("ok").and_then(|v| v.as_bool()) == Some(false) + || inner.get("error").is_some() + { + return ( + vec![], + latency_ms, + Some("tool delivery error".into()), + final_bound, + ); + } + // skipped case → no capsules (intentional, not an error) + if inner + .get("skipped") + .and_then(|v| v.as_bool()) + .unwrap_or(false) + { + return (vec![], latency_ms, None, final_bound); + } - let capsules = inner - .get("capsules") - .and_then(|c| c.as_array()) - .cloned() - .unwrap_or_default(); + let capsules = inner + .get("capsules") + .and_then(|c| c.as_array()) + .cloned() + .unwrap_or_default(); - let keys: Vec = capsules - .iter() - .filter_map(|cap| { - cap.get("expansion_handle") - .and_then(|h| h.as_str()) - .and_then(|h| h.strip_prefix("memory:")) - .and_then(|id| id_to_key.get(id)) - .cloned() - }) - .collect(); + let keys: Vec = capsules + .iter() + .filter_map(|cap| { + cap.get("expansion_handle") + .and_then(|h| h.as_str()) + .map(|handle| { + handle + .strip_prefix("memory:") + .and_then(|id| id_to_key.get(id)) + .cloned() + .unwrap_or_else(|| handle.to_string()) + }) + }) + .collect(); - (keys, latency_ms, None) - }; + (keys, latency_ms, None, final_bound) + }; let mut case_results: Vec = Vec::new(); let mut seq_latencies: Vec = Vec::new(); for (idx, case) in fixture.cases.iter().enumerate() { - let (obtained, latency_ms, error) = call_context(&case.query, idx as u64); + let (obtained, latency_ms, error, final_bound) = call_context(&case.query, idx as u64); seq_latencies.push(latency_ms); let hit_at_2 = if case.relevant.is_empty() { @@ -1193,11 +1273,14 @@ pub(crate) fn brain_bench_remote(args: BrainBenchArgs) -> KimetsuResult<()> { case_results.push(RemoteCaseResult { query: case.query.clone(), expected: case.relevant.clone(), + recall_at_2: kimetsu_brain::eval::recall_at_k(&obtained, &case.relevant, 2), + recall_at_4: kimetsu_brain::eval::recall_at_k(&obtained, &case.relevant, 4), obtained, hit_at_2, hit_at_4, mrr: mrr_val, latency_ms, + final_bound, error, }); } @@ -1242,6 +1325,7 @@ pub(crate) fn brain_bench_remote(args: BrainBenchArgs) -> KimetsuResult<()> { continue; } + let cap = args.cap; let handle = std::thread::spawn(move || { for case_idx in start..end { let (i, ref query) = cases[case_idx]; @@ -1254,7 +1338,8 @@ pub(crate) fn brain_bench_remote(args: BrainBenchArgs) -> KimetsuResult<()> { "arguments": { "query": query, "budget_tokens": 6000, - "max_capsules": 4 + "max_capsules": cap, + "include_ambient":false } } }); @@ -1324,20 +1409,12 @@ pub(crate) fn brain_bench_remote(args: BrainBenchArgs) -> KimetsuResult<()> { let recall_at_2 = if signal_cases.is_empty() { 0.0 } else { - signal_cases - .iter() - .map(|(_, r)| if r.hit_at_2 { 1.0f64 } else { 0.0 }) - .sum::() - / signal_cases.len() as f64 + signal_cases.iter().map(|(_, r)| r.recall_at_2).sum::() / signal_cases.len() as f64 }; let recall_at_4 = if signal_cases.is_empty() { 0.0 } else { - signal_cases - .iter() - .map(|(_, r)| if r.hit_at_4 { 1.0f64 } else { 0.0 }) - .sum::() - / signal_cases.len() as f64 + signal_cases.iter().map(|(_, r)| r.recall_at_4).sum::() / signal_cases.len() as f64 }; let mrr_avg = if signal_cases.is_empty() { 0.0 @@ -1369,6 +1446,62 @@ pub(crate) fn brain_bench_remote(args: BrainBenchArgs) -> KimetsuResult<()> { let error_cases = case_results.iter().filter(|r| r.error.is_some()).count(); let summary = RemoteComboSummary { + positive_count: signal_cases.len(), + negative_count: noise_cases.len(), + negative_accuracy: if noise_cases.is_empty() { + None + } else { + Some( + noise_cases + .iter() + .filter(|(_, r)| r.error.is_none() && r.obtained.is_empty()) + .count() as f64 + / noise_cases.len() as f64, + ) + }, + false_injection_rate: if noise_cases.is_empty() { + None + } else { + Some( + noise_cases + .iter() + .filter(|(_, r)| !r.obtained.is_empty()) + .count() as f64 + / noise_cases.len() as f64, + ) + }, + cost_measurement_count: case_results + .iter() + .filter(|r| r.final_bound.is_some()) + .count(), + mean_final_bound: { + let costs: Vec<_> = case_results + .iter() + .filter_map(|r| r.final_bound.map(f64::from)) + .collect(); + if costs.is_empty() { + None + } else { + Some(kimetsu_brain::eval::mean(&costs)) + } + }, + cost_unit: "serialized_utf8_byte_bound", + hit_at_2: if signal_cases.is_empty() { + None + } else { + Some( + signal_cases.iter().filter(|(_, r)| r.hit_at_2).count() as f64 + / signal_cases.len() as f64, + ) + }, + hit_at_4: if signal_cases.is_empty() { + None + } else { + Some( + signal_cases.iter().filter(|(_, r)| r.hit_at_4).count() as f64 + / signal_cases.len() as f64, + ) + }, recall_at_2, recall_at_4, mrr: mrr_avg, @@ -1397,19 +1530,12 @@ pub(crate) fn brain_bench_remote(args: BrainBenchArgs) -> KimetsuResult<()> { // ── 11. Write per-embedder JSON ─────────────────────────────────────── let combo = RemoteComboResult { embedder: embedder_id.to_string(), + reranker: remote_reranker.to_string(), seed_ms, rss_after_warm_mb: rss_after_warm, peak_rss_mb: peak_rss, cases: case_results, - summary: RemoteComboSummary { - recall_at_2, - recall_at_4, - mrr: mrr_avg, - mean_latency_ms, - p95_latency_ms, - noise_capsules, - error_cases, - }, + summary: summary.clone(), concurrent: RemoteConcurrentStats { mean_ms: conc_mean_ms, p95_ms: conc_p95_ms, @@ -1433,12 +1559,7 @@ pub(crate) fn brain_bench_remote(args: BrainBenchArgs) -> KimetsuResult<()> { } // ── 12. Write summary table ─────────────────────────────────────────────── - let caveat = "\ -> **NOTE — remote production floors**: the remote path applies `min_lexical_coverage = 0.5` and \ -the AUTO semantic floor (0.35 on bge-family, 0.0 elsewhere — cosine scales are model-dependent). \ -Quality numbers are **NOT** directly comparable to the local bench's floors-off results — noise \ -cases dropped by the floors are intentional precision wins, not recall failures. The remote server \ -reranks with `--reranker` (default `jina-reranker-v1-tiny-en`, operator-level, `off` disables).\n"; + let caveat = "> Canonical brain-context delivery: production floors, rerank score floor, capsule admission and serialized UTF-8 bound. Compare only matching model, budget, pool, cap and configuration. Missing positive/negative denominators are reported in JSON.\n"; let header = format!( "| {:<25} | {:>8} | {:>8} | {:>7} | {:>9} | {:>8} | {:>12} | {:>10} | {:>14} | {:>11} | {:>11} |", @@ -1529,6 +1650,7 @@ pub(crate) fn brain_bench_single(args: BrainBenchArgs) -> KimetsuResult<()> { .unwrap_or("off") .to_string(); + let effective_embedder = kimetsu_brain::embeddings::canonical_embedder_id(&embedder_id)?; // ── 1. Load fixture ─────────────────────────────────────────────────────── let fixture_text = std::fs::read_to_string(&args.dataset) .map_err(|e| format!("cannot read dataset {}: {e}", args.dataset.display()))?; @@ -1565,19 +1687,17 @@ pub(crate) fn brain_bench_single(args: BrainBenchArgs) -> KimetsuResult<()> { unsafe { std::env::set_var("KIMETSU_BRAIN_EMBEDDER", &embedder_id); } - let embedder = open_embedder_for_model(&embedder_id); + let embedder = open_embedder_for_model(effective_embedder); let embedder_load_ms = t_emb.elapsed().as_millis(); let rss_after_emb = rss_mb(); // ── 3. Load reranker ────────────────────────────────────────────────────── let rss_before_rr = rss_mb(); let t_rr = Instant::now(); - let reranker_box: Option> = if reranker_id == "off" - { - None - } else { - open_reranker_for_model(&reranker_id) - }; + let reranker_box = kimetsu_brain::embeddings::open_reranker_checked(&reranker_id)?; + if embedder.is_noop() && effective_embedder != "noop" { + return Err("requested embedder unavailable; no semantic measurement".into()); + } let reranker_load_ms = t_rr.elapsed().as_millis(); let rss_after_rr = rss_mb(); @@ -1692,8 +1812,11 @@ pub(crate) fn brain_bench_single(args: BrainBenchArgs) -> KimetsuResult<()> { obtained: Vec, hit_at_2: bool, hit_at_4: bool, + recall_at_2: f64, + recall_at_4: f64, mrr: f64, latency_ms: u128, + final_bound: u32, /// v1.5 (Story 2.1): mean rendered tokens across the returned capsules /// after compress_for_render(3) vs raw token estimates. raw_tokens_mean: f64, @@ -1709,26 +1832,24 @@ pub(crate) fn brain_bench_single(args: BrainBenchArgs) -> KimetsuResult<()> { for case in &fixture.cases { let t0 = Instant::now(); + let policy = kimetsu_brain::serving::ServingPolicy { + pool: args.pool, + cap: args.cap, + ..Default::default() + }; let request = ContextRequest { - stage: "localization".to_string(), + stage: "localization".into(), query: case.query.clone(), - budget_tokens: 6000, - max_capsules: args.pool, - min_semantic_score: 0.0, - min_lexical_coverage: 0.0, + min_score: 0.15, ..Default::default() }; - let mut bundle = session - .retrieve_context_with_injected_embedder(request, embedder.as_ref()) - .map_err(|e| format!("retrieve: {e}"))?; - - // Apply reranker or truncate. - if let Some(ref rr) = reranker_box { - bundle.capsules = - rerank_capsules(&case.query, bundle.capsules, rr.as_ref(), 0.0, args.cap); - } else { - bundle.capsules.truncate(args.cap); - } + let bundle = policy.retrieve( + &session, + request, + embedder.as_ref(), + reranker_box.as_deref(), + kimetsu_brain::serving::EVAL_EXPOSURE_ID, + )?; let latency_ms = t0.elapsed().as_millis(); latencies_ms.push(latency_ms); @@ -1806,8 +1927,11 @@ pub(crate) fn brain_bench_single(args: BrainBenchArgs) -> KimetsuResult<()> { obtained, hit_at_2, hit_at_4, + recall_at_2: kimetsu_brain::eval::recall_at_k(&obtained_keys, &case.relevant, 2), + recall_at_4: kimetsu_brain::eval::recall_at_k(&obtained_keys, &case.relevant, 4), mrr: mrr_val, latency_ms, + final_bound: bundle.payload["used_tokens"].as_u64().unwrap_or(0) as u32, raw_tokens_mean, rendered_tokens_mean, stale_hit, @@ -1832,20 +1956,12 @@ pub(crate) fn brain_bench_single(args: BrainBenchArgs) -> KimetsuResult<()> { let recall_at_2 = if signal_cases.is_empty() { 0.0 } else { - signal_cases - .iter() - .map(|(_, r)| if r.hit_at_2 { 1.0f64 } else { 0.0 }) - .sum::() - / signal_cases.len() as f64 + signal_cases.iter().map(|(_, r)| r.recall_at_2).sum::() / signal_cases.len() as f64 }; let recall_at_4 = if signal_cases.is_empty() { 0.0 } else { - signal_cases - .iter() - .map(|(_, r)| if r.hit_at_4 { 1.0f64 } else { 0.0 }) - .sum::() - / signal_cases.len() as f64 + signal_cases.iter().map(|(_, r)| r.recall_at_4).sum::() / signal_cases.len() as f64 }; let mrr_avg = if signal_cases.is_empty() { 0.0 @@ -1921,6 +2037,8 @@ pub(crate) fn brain_bench_single(args: BrainBenchArgs) -> KimetsuResult<()> { // ── 7. Write combo JSON ─────────────────────────────────────────────────── let combo_json = serde_json::json!({ "embedder": embedder_id, + "embedder_actual":embedder.model_id(),"embedding_dimension":embedder.dim(), + "reranker_actual":reranker_box.as_ref().map(|r|r.model_id()).unwrap_or("off"), "reranker": reranker_id, "embedder_load_ms": embedder_load_ms, "reranker_load_ms": reranker_load_ms, @@ -1931,7 +2049,18 @@ pub(crate) fn brain_bench_single(args: BrainBenchArgs) -> KimetsuResult<()> { "peak_rss_mb": peak, "seed_ms": seed_ms, "cases": case_results, + "measurement_policy":"canonical_brain_context_v1", + "ambient":false,"warm_start":false, + "cost_unit":"serialized_utf8_byte_bound", + "budget":6000,"pool":args.pool,"cap":args.cap,"rerank_floor":kimetsu_brain::serving::RERANK_FLOOR, "summary": { + "positive_count":signal_cases.len(),"negative_count":noise_cases.len(), + "negative_accuracy":if noise_cases.is_empty() {None}else{Some(noise_cases.iter().filter(|(_,r)|r.obtained.is_empty()).count() as f64/noise_cases.len() as f64)}, + "false_injection_rate":if noise_cases.is_empty() {None}else{Some(noise_cases.iter().filter(|(_,r)|!r.obtained.is_empty()).count() as f64/noise_cases.len() as f64)}, + "mean_final_bound":kimetsu_brain::eval::mean(&case_results.iter().map(|r|f64::from(r.final_bound)).collect::>()), + "hit_at_2":if signal_cases.is_empty() {None}else{Some(signal_cases.iter().filter(|(_,r)|r.hit_at_2).count() as f64/signal_cases.len() as f64)}, + "hit_at_4":if signal_cases.is_empty() {None}else{Some(signal_cases.iter().filter(|(_,r)|r.hit_at_4).count() as f64/signal_cases.len() as f64)}, + "legacy_capsule_token_estimates":"whitespace heuristics, not delivery cost", "recall_at_2": recall_at_2, "recall_at_4": recall_at_4, "mrr": mrr_avg, diff --git a/crates/kimetsu-cli/src/commands/brain.rs b/crates/kimetsu-cli/src/commands/brain.rs index d21ec90..01758b9 100644 --- a/crates/kimetsu-cli/src/commands/brain.rs +++ b/crates/kimetsu-cli/src/commands/brain.rs @@ -2532,7 +2532,7 @@ pub(crate) fn brain_tune(args: TuneArgs) -> KimetsuResult<()> { let noise_count = eval.noise_count; let readiness = if positive_count >= 30 { - "READY — enough cases for a meaningful sweep." + "READY for weak-positive diagnostics; independent families and negative gold are required for apply." } else { "accumulating — synthetic fixture will be used for the sweep (< 30 positive cases)." }; @@ -2541,8 +2541,8 @@ pub(crate) fn brain_tune(args: TuneArgs) -> KimetsuResult<()> { let kind_coverage = kind_coverage_from_eval(&conn, &eval.cases); println!("=== kimetsu brain tune --status ==="); - println!("Positive cases (query + ≥1 cited memory): {positive_count}"); - println!("Noise entries (served, no citation): {noise_count}"); + println!("Weak reliance cases (query + exact cited claim): {positive_count}"); + println!("Unknown exposures (no usable exact citation): {noise_count}"); if let Some(o) = &eval.oldest { println!("Oldest positive case: {o}"); } @@ -2691,328 +2691,248 @@ pub(crate) fn brain_tune_sweep( args: TuneArgs, eval: kimetsu_brain::tuneset::PersonalEval, ) -> KimetsuResult<()> { - use kimetsu_brain::context::{ContextRequest, rerank_capsules}; - use kimetsu_brain::embeddings::{open_embedder_for, open_reranker_for_model}; - use kimetsu_brain::eval::{mean, mrr}; - use kimetsu_brain::project::BrainSession; - use kimetsu_brain::tune::{ - ComboResult, TuneCombo, TuneHistoryEntry, append_tune_history, - compute_objective_with_regret, count_regret_events, select_winner, train_holdout_split, + use kimetsu_brain::{ + context::ContextRequest, + embeddings::{open_embedder_for, open_reranker_checked}, + eval::{EvaluationMetrics, summarize_deliveries}, + project::BrainSession, + serving::{EVAL_EXPOSURE_ID, ServingPolicy}, + tune::{ + ComboResult, TuneCombo, TuneHistoryEntry, append_tune_history, compute_objective, + grouped_train_holdout_split, select_winner, + }, }; use std::collections::HashMap; use time::format_description::well_known::Rfc3339; - + if !args.cost_weight.is_finite() || args.cost_weight < 0.0 { + return Err("cost_weight must be finite and nonnegative".into()); + } let config = project::load_config(paths)?; - // Tune against the PRODUCTION retrieval pipeline: the same embedder - // resolution as retrieve_context_with_request. On embeddings builds this - // loads the real model (semantic floors only discriminate with real - // cosines); lean builds degrade to Noop and sweep FTS-only — the status - // output should make that visible to the user. let embedder = open_embedder_for(config.embedder.enabled); - if embedder.is_noop() { - println!( - "note: lean build/embedder disabled — sweeping FTS-only retrieval \ - (semantic floor values will not differentiate)" - ); - } + let policy = ServingPolicy::default(); let current_combo = TuneCombo { min_lexical_coverage: config.broker.min_lexical_coverage, min_semantic_score: config.broker.min_semantic_score, reranker_id: config.embedder.reranker.clone(), fusion: config.broker.fusion.clone(), }; - - // Choose eval cases: personal if READY, else fall back to fixture. - let fallback_fixture_path = std::path::Path::new("fixtures/eval-retrieval.json"); - let (cases, using_personal) = if eval.cases.len() >= 30 { - (eval.cases.clone(), true) + struct FixtureRoot(std::path::PathBuf); + impl Drop for FixtureRoot { + fn drop(&mut self) { + let _ = std::fs::remove_dir_all(&self.0); + } + } + let mut fixture_root = None; + let using_personal = eval.cases.len() >= 30; + let cases = if using_personal { + eval.cases.clone() } else { - // Load the committed fixture. - if !fallback_fixture_path.exists() { + let fixture_path = std::path::Path::new("fixtures/eval-retrieval.json"); + if !fixture_path.exists() { println!( - "note: fewer than 30 personal eval cases ({}) and no fixture at {}. \ - Sweep skipped. Accumulate more sessions with store_queries=true.", - eval.cases.len(), - fallback_fixture_path.display() + "Fewer than 30 personal weak-label cases and no fixture; no measurable sweep." ); return Ok(()); } - let text = std::fs::read_to_string(fallback_fixture_path) - .map_err(|e| format!("read fixture: {e}"))?; let fixture: kimetsu_brain::eval::EvalFixture = - serde_json::from_str(&text).map_err(|e| format!("parse fixture: {e}"))?; - // Fixture uses key-based relevance, not memory_ids. For the sweep - // we need memory_ids. We cannot map them here (fixture is hermetic). - // Instead: use fixture cases as-is for MRR calculation but note that - // relevant ids won't match real DB memories → MRR will be 0. - // The sweep is still meaningful for comparing COMBOS relatively. - let eval_cases: Vec = fixture - .cases - .into_iter() - .map(|c| kimetsu_brain::eval::EvalCase { - query: c.query, - relevant: c.relevant, - kind: Default::default(), - stale: Vec::new(), - }) - .collect(); - (eval_cases, false) + serde_json::from_str(&std::fs::read_to_string(fixture_path)?)?; + let root = std::env::temp_dir().join(format!("kimetsu-tune-fixture-{}", ulid::Ulid::new())); + kimetsu_core::paths::git_init_boundary(&root); + let guard = FixtureRoot(root.clone()); + project::init_project(&root, false)?; + let fixture_paths = kimetsu_core::paths::ProjectPaths::discover(&root)?; + let mut fixture_config = config.clone(); + fixture_config.kimetsu.use_user_brain = false; + std::fs::write( + &fixture_paths.project_toml, + toml::to_string_pretty(&fixture_config)?, + )?; + let mut ids = HashMap::new(); + for mem in &fixture.memories { + let id = project::add_memory_with_validity( + &root, + MemoryScope::Project, + MemoryKind::Fact, + &mem.text, + None, + mem.valid_to.as_deref(), + )?; + ids.insert(mem.key.clone(), id); + } + let (_, _, conn) = project::load_project(&root)?; + for mem in &fixture.memories { + if let Some(next) = &mem.superseded_by_key { + let survivor = ids + .get(next) + .ok_or("fixture references missing superseding key")?; + conn.execute( + "UPDATE memories SET superseded_by=?2 WHERE memory_id=?1", + rusqlite::params![ids[&mem.key], survivor], + )?; + } + } + let mut cases = fixture.cases; + for case in &mut cases { + for id in case.relevant.iter_mut().chain(&mut case.stale) { + *id = ids + .get(id) + .ok_or("fixture references missing memory key")? + .clone(); + } + } + println!( + "Using a hermetic seeded fixture: {} positive/negative cases; personal weak labels {}, unknown {}. Fixture results cannot authorize changes to this brain.", + cases.len(), + eval.cases.len(), + eval.noise_count + ); + fixture_root = Some(guard); + cases }; - - if !using_personal { + let evaluation_workspace = fixture_root + .as_ref() + .map(|g| g.0.as_path()) + .unwrap_or(workspace); + let split = grouped_train_holdout_split(&cases); + if split.train.is_empty() || split.holdout.is_empty() { println!( - "note: fewer than 30 personal eval cases ({}). Using fixture file for relative sweep.", - eval.cases.len() + "Only {} independent families; no independent train/holdout comparison is available. No validated tuning recommendation.", + split.family_count ); - // Fix 3: guard --apply behind personal data. - // In fixture mode MRR≡0 for every combo (fixture IDs don't match real - // memories), so the objective degenerates to pure token-minimisation. - // Applying the resulting floors would optimise for fewer tokens at the - // cost of recall. Refuse --apply until the user has ≥30 cited cases. - if args.apply { - println!( - "note: fixture mode is relative-only — --apply refused. \ - Accumulate ≥30 cited cases first (see `kimetsu brain tune --status`)." - ); - return Ok(()); + return Ok(()); + } + let train_cases: Vec<_> = split.train.iter().map(|&i| &cases[i]).collect(); + let holdout_cases: Vec<_> = split.holdout.iter().map(|&i| &cases[i]).collect(); + let mut reranker_cache = HashMap::new(); + for id in kimetsu_brain::tune::RERANKER_IDS + .iter() + .copied() + .chain(std::iter::once(current_combo.reranker_id.as_str())) + { + if !reranker_cache.contains_key(id) { + reranker_cache.insert(id.to_string(), open_reranker_checked(id)); } } - - let n = cases.len(); - if n == 0 { - println!("No eval cases available. Run more sessions with store_queries=true."); + if let Some(Err(error)) = reranker_cache.get(¤t_combo.reranker_id) { + println!("Baseline unavailable: {error}. No measured comparison or recommendation."); return Ok(()); } - - let (train_idx, holdout_idx) = train_holdout_split(n); - let train_cases: Vec<&kimetsu_brain::eval::EvalCase> = - train_idx.iter().map(|&i| &cases[i]).collect(); - let holdout_cases: Vec<&kimetsu_brain::eval::EvalCase> = - holdout_idx.iter().map(|&i| &cases[i]).collect(); - - println!( - "Sweep: {} combos × {} train / {} holdout cases", - kimetsu_brain::tune::TuneCombo::all_combos().len(), - train_cases.len(), - holdout_cases.len() - ); - - // Cache reranker handles (load once, reuse). - let mut reranker_cache: HashMap>> = - HashMap::new(); - for rr_id in kimetsu_brain::tune::RERANKER_IDS { - let rr: Option> = if *rr_id == "off" { - None - } else { - open_reranker_for_model(rr_id) - }; - reranker_cache.insert(rr_id.to_string(), rr); - } - - // Helper: evaluate one combo over a slice of cases. - let evaluate_cases = - |combo: &TuneCombo, case_slice: &[&kimetsu_brain::eval::EvalCase]| -> (f64, f64) { - let session = match BrainSession::open_readonly(workspace) { - Ok(s) => s, - Err(_) => return (0.0, 0.0), + let session = BrainSession::open_readonly(evaluation_workspace)?; + let evaluate_cases = |combo: &TuneCombo, + cases: &[&kimetsu_brain::eval::EvalCase]| + -> KimetsuResult { + let rr = reranker_cache + .get(&combo.reranker_id) + .ok_or("missing reranker")? + .as_ref() + .map_err(|e| e.clone())? + .as_deref(); + let mut ranked = Vec::new(); + let mut costs = Vec::new(); + for case in cases { + let request = ContextRequest { + query: case.query.clone(), + stage: "localization".into(), + min_score: 0.15, + min_semantic_score_override: Some(combo.min_semantic_score), + min_lexical_coverage_override: Some(combo.min_lexical_coverage), + fusion: combo.fusion.clone(), + ..Default::default() }; - let rr_ref = reranker_cache - .get(&combo.reranker_id) - .and_then(|r| r.as_deref()); - let rerank_floor = 0.30f32; - let rerank_cap = 4usize; - let pool = 8usize; - - let mut mrr_vals: Vec = Vec::new(); - let mut token_vals: Vec = Vec::new(); - - for case in case_slice { - let request = ContextRequest { - stage: "localization".to_string(), - query: case.query.clone(), - budget_tokens: 6000, - max_capsules: pool, - min_semantic_score: combo.min_semantic_score, - min_lexical_coverage: combo.min_lexical_coverage, - fusion: combo.fusion.clone(), - ..Default::default() - }; - let mut bundle = - match session.retrieve_context_with_injected_embedder(request, embedder) { - Ok(b) => b, - Err(_) => continue, - }; - if let Some(rr) = rr_ref { - bundle.capsules = - rerank_capsules(&case.query, bundle.capsules, rr, rerank_floor, rerank_cap); - } - - let ranked_ids: Vec = bundle + let delivery = policy.retrieve(&session, request, embedder, rr, EVAL_EXPOSURE_ID)?; + ranked.push( + delivery .capsules .iter() - .filter_map(|c| { + .map(|c| { c.expansion_handle .strip_prefix("memory:") - .map(str::to_string) + .unwrap_or(&c.expansion_handle) + .to_string() }) - .collect(); - - let mrr_val = mrr(&ranked_ids, &case.relevant); - mrr_vals.push(mrr_val); - - let tokens: f64 = bundle - .capsules - .iter() - .map(|c| c.token_estimate as f64) - .sum(); - token_vals.push(tokens); - } - - (mean(&mrr_vals), mean(&token_vals)) - }; - - // S2.3: Compute global regret rate from the DB for the objective penalty. - // We use the ALL-TIME regret / served ratio here (the sweep window is the - // full personal eval set, which spans all time). - // Best-effort: if the DB cannot be opened, regret_rate and memory_count - // degrade gracefully to 0 (objective falls back to v1.5 formula). - let (global_regret_rate, current_memory_count) = { - match kimetsu_brain::project::load_project_readonly(workspace) { - Ok((_paths_ro, _cfg_ro, conn_ro)) => { - let total_regrets = count_regret_events(&conn_ro, None, None).unwrap_or(0); - let total_served: u64 = conn_ro - .query_row( - "SELECT COUNT(*) FROM events WHERE kind = 'context.served'", - [], - |r| r.get(0), - ) - .unwrap_or(0); - let regret_rate = if total_served > 0 { - total_regrets as f64 / total_served as f64 - } else { - 0.0 - }; - let mem_count: u64 = conn_ro - .query_row( - "SELECT COUNT(*) FROM memories WHERE invalidated_at IS NULL", - [], - |r| r.get(0), - ) - .unwrap_or(0); - (regret_rate, mem_count) - } - Err(_) => (0.0_f64, 0_u64), + .collect(), + ); + costs.push( + delivery.payload["used_tokens"] + .as_u64() + .ok_or("missing delivery cost")? as u32, + ); } + Ok(summarize_deliveries(cases, &ranked, &costs)?) }; - - // Evaluate current config on holdout for baseline. - let (baseline_holdout_mrr, baseline_holdout_tokens) = - evaluate_cases(¤t_combo, &holdout_cases); - let baseline_holdout_obj = compute_objective_with_regret( - baseline_holdout_mrr, - baseline_holdout_tokens, - args.cost_weight, - global_regret_rate, - ); - - // Sweep all combos on TRAIN set. - let all_combos = TuneCombo::all_combos(); - let mut combo_results: Vec = Vec::new(); - - for (i, combo) in all_combos.iter().enumerate() { - if i % 10 == 0 { - print!("\r sweeping combo {}/{} ...", i + 1, all_combos.len()); - use std::io::Write; - let _ = std::io::stdout().flush(); + let objective = |m: &EvaluationMetrics| { + compute_objective( + m.quality.unwrap_or(0.0), + m.mean_final_bound, + args.cost_weight, + ) + }; + let baseline = evaluate_cases(¤t_combo, &holdout_cases)?; + let baseline_holdout_obj = objective(&baseline); + let mut combo_results = Vec::new(); + let mut measurements = HashMap::new(); + for combo in TuneCombo::all_combos() { + if reranker_cache + .get(&combo.reranker_id) + .is_none_or(|r| r.is_err()) + { + continue; } - let (mmrr, mtok) = evaluate_cases(combo, &train_cases); - // S2.3: include regret penalty in the objective. - let obj = compute_objective_with_regret(mmrr, mtok, args.cost_weight, global_regret_rate); + let metrics = evaluate_cases(&combo, &train_cases)?; combo_results.push(ComboResult { combo: combo.clone(), - mean_mrr: mmrr, - mean_tokens: mtok, - objective: obj, + mean_mrr: metrics.mrr.unwrap_or(0.0), + mean_tokens: metrics.mean_final_bound, + objective: objective(&metrics), }); + measurements.insert(serde_json::to_string(&combo)?, metrics); } - println!(); - - let winner = match select_winner(&combo_results) { - Some(w) => w, - None => { - println!("No combos evaluated. Nothing to tune."); - return Ok(()); - } + let Some(winner) = select_winner(&combo_results) else { + println!("No available models produced measurements."); + return Ok(()); }; - - // Evaluate winner on HOLDOUT (with regret penalty for consistency). - let (holdout_mrr, holdout_tokens) = evaluate_cases(&winner.combo, &holdout_cases); - let holdout_obj = compute_objective_with_regret( - holdout_mrr, - holdout_tokens, - args.cost_weight, - global_regret_rate, - ); + let train_metrics = &measurements[&serde_json::to_string(&winner.combo)?]; + let holdout = evaluate_cases(&winner.combo, &holdout_cases)?; + let holdout_mrr = holdout.mrr.unwrap_or(0.0); + let holdout_obj = objective(&holdout); let improvement = holdout_obj - baseline_holdout_obj; - - println!(); - println!("=== Tune Sweep Results ==="); - println!( - "Current config: lex={:.2} sem={:.3} rr={}", - current_combo.min_lexical_coverage, - current_combo.min_semantic_score, - current_combo.reranker_id - ); - println!( - "Best combo: lex={:.2} sem={:.3} rr={}", - winner.combo.min_lexical_coverage, - winner.combo.min_semantic_score, - winner.combo.reranker_id - ); + let current_memory_count = project::load_project_readonly(workspace)?.2.query_row( + "SELECT COUNT(*) FROM memories WHERE invalidated_at IS NULL", + [], + |r| r.get::<_, u64>(0), + )?; + let measurement = serde_json::json!({"policy":"canonical_brain_context_v1","cost_unit":"serialized_utf8_byte_bound","budget":policy.budget,"cap":policy.cap,"pool":policy.pool,"rerank_floor":policy.rerank_floor,"cost_weight_per_unit":args.cost_weight,"lambda":args.cost_weight*f64::from(policy.budget),"quality_formula":"mean of available positive MRR and known-negative abstention accuracy","embedder_actual":embedder.model_id(),"family_count":split.family_count,"train":train_metrics,"holdout":holdout,"baseline_holdout":baseline,"historical_regret":"diagnostic_only","ambient":"disabled; effective query is the fixture/stored query","warm_start":"not replayed","labels":if using_personal {"weak_reliance"}else{"explicit_fixture"}}); println!( - "Train objective: {:.4} (MRR {:.4}, avg_tokens {:.1})", - winner.objective, winner.mean_mrr, winner.mean_tokens + "Sweep: {} available combos, {} train / {} holdout cases in {} independent families", + combo_results.len(), + train_cases.len(), + holdout_cases.len(), + split.family_count ); + println!("Best combo: {}", serde_json::to_string(&winner.combo)?); println!( - "Holdout objective: {:.4} vs baseline {:.4} (improvement: {:+.4})", - holdout_obj, baseline_holdout_obj, improvement + "Train objective {:.6}; holdout {:.6} vs baseline {:.6} (difference {:+.6})", + winner.objective, holdout_obj, baseline_holdout_obj, improvement ); - + println!("{}", serde_json::to_string_pretty(&measurement)?); if improvement < 0.01 { - println!(); + println!("No change recommended: held-out objective difference is below 0.01."); + return Ok(()); + } + if !using_personal || train_metrics.negative_count == 0 || holdout.negative_count == 0 { println!( - "verdict: no change recommended (holdout improvement {improvement:+.4} < 0.01 threshold)" + "Diagnostic comparison only: application requires personal data and explicit negative coverage in both partitions. No validated all-query improvement is claimed." ); return Ok(()); } - - println!(); - // Reranker change recommendation (never auto-applied). if winner.combo.reranker_id != current_combo.reranker_id { println!( - "note: reranker change recommended ({} → {}) — apply manually after \ - downloading the model and restarting the MCP daemon.", - current_combo.reranker_id, winner.combo.reranker_id + "Winning reranker differs; no partial configuration application is measured. Apply a complete reviewed configuration manually." ); + return Ok(()); } - if !args.apply { - if !using_personal { - println!( - "note: fixture mode — results are relative only; \ - --apply is disabled until you have ≥30 cited cases." - ); - } - println!( - "DRY RUN — to apply: kimetsu brain tune --apply\n\ - (lex {:.2}→{:.2}, sem {:.3}→{:.3}, fusion {}→{})", - current_combo.min_lexical_coverage, - winner.combo.min_lexical_coverage, - current_combo.min_semantic_score, - winner.combo.min_semantic_score, - current_combo.fusion, - winner.combo.fusion, - ); + println!("Dry run; use --apply to save the complete evaluated configuration."); return Ok(()); } @@ -3060,6 +2980,7 @@ pub(crate) fn brain_tune_sweep( baseline_holdout_objective: baseline_holdout_obj, // S2.1: record corpus size so re-tune trigger can detect growth. memory_count_at_tune: Some(current_memory_count), + measurement: Some(measurement), }; append_tune_history(&paths.kimetsu_dir, history_entry)?; diff --git a/crates/kimetsu-cli/src/embed_daemon/server.rs b/crates/kimetsu-cli/src/embed_daemon/server.rs index 266c900..f0448e9 100644 --- a/crates/kimetsu-cli/src/embed_daemon/server.rs +++ b/crates/kimetsu-cli/src/embed_daemon/server.rs @@ -17,10 +17,10 @@ use std::time::Instant; /// noise 0) at half the rerank latency (~44ms vs ~95ms per query) — the /// earlier pool-shrink regression was the snippet truncation, not the pool. /// NOTE: summaries must stay FULL — truncating them cratered recall. -const RERANK_POOL: usize = 6; +const RERANK_POOL: usize = kimetsu_brain::serving::RERANK_POOL; /// Sigmoid-score floor — capsules the cross-encoder judges below this are noise. -const RERANK_FLOOR: f32 = 0.30; +const RERANK_FLOOR: f32 = kimetsu_brain::serving::RERANK_FLOOR; /// Process-global state shared by all worker threads. pub struct DaemonState { @@ -61,30 +61,17 @@ impl DaemonState { }; // Clone query before it's moved into the request so we can pass it to // the reranker after retrieval. - let query = args.query.clone(); + let cap = args.max_capsules; - // When reranking, over-fetch a larger candidate pool so the - // cross-encoder sees enough diversity before truncating to `cap`. - let fetch_cap = if self.reranker.is_some() { - cap.max(RERANK_POOL) - } else { - cap - }; - // Bump the token budget so the pool isn't budget-starved before the - // reranker sees it. - let budget = if self.reranker.is_some() { - (if args.budget_tokens == 0 { - 2000 - } else { - args.budget_tokens - }) - .max(6000) - } else { - if args.budget_tokens == 0 { + let policy = kimetsu_brain::serving::ServingPolicy { + budget: if args.budget_tokens == 0 { 2000 } else { args.budget_tokens - } + }, + cap, + pool: RERANK_POOL, + rerank_floor: RERANK_FLOOR, }; let request = ContextRequest { stage: if args.stage.is_empty() { @@ -93,26 +80,25 @@ impl DaemonState { args.stage }, query: args.query, - budget_tokens: budget, + min_score: args.min_score, - max_capsules: fetch_cap, + tags: args.tags, ..Default::default() }; - match session.retrieve_context_with_injected_embedder(request, self.embedder.as_ref()) { + match policy.retrieve( + &session, + request, + self.embedder.as_ref(), + self.reranker.as_deref(), + kimetsu_brain::serving::EVAL_EXPOSURE_ID, + ) { Ok(bundle) => { - // v2.7: rerank + evidence-band arbitration (see - // `rerank_and_arbitrate`). A band bundle the cross-encoder - // rejects goes back over the wire as skipped, exactly like a - // hard-gated one. - let bundle = kimetsu_brain::context::rerank_and_arbitrate( - &query, - bundle, - self.reranker.as_deref(), - session.resolved_abstain_evidence(), - RERANK_FLOOR, - cap, - ); + if let Some(error) = bundle.payload.get("error") { + return proto::Response::Error { + message: error.to_string(), + }; + } proto::Response::Capsules { capsules: bundle .capsules @@ -126,8 +112,12 @@ impl DaemonState { score: c.score, }) .collect(), - skipped: bundle.skipped, - top_score: bundle.top_score, + skipped: bundle.capsules.is_empty(), + top_score: bundle + .capsules + .iter() + .map(|c| c.score) + .fold(0.0_f32, f32::max), } } Err(e) => proto::Response::Error { diff --git a/crates/kimetsu-cli/src/main.rs b/crates/kimetsu-cli/src/main.rs index e06ff80..4a18584 100644 --- a/crates/kimetsu-cli/src/main.rs +++ b/crates/kimetsu-cli/src/main.rs @@ -1125,8 +1125,8 @@ struct EvalArgs { #[arg(long, default_value = "")] rerankers: String, /// Candidate-pool size handed to the reranker before truncating to the - /// cap (mirrors the daemon's RERANK_POOL; 12 is the production value). - #[arg(long, default_value_t = 12)] + /// cap (the canonical default is 6). + #[arg(long, default_value_t = 6)] pool: usize, /// HyDE: expand each case query with a hypothetical answer from the cheap /// model before retrieval, to measure the recall lift on oblique queries. @@ -1141,19 +1141,16 @@ struct BrainBenchArgs { #[arg(long, default_value = "bench/dataset.json")] dataset: PathBuf, /// Comma-separated embedder ids to sweep. - #[arg(long, default_value = "bge-small-en-v1.5,jina-v2-base-code")] + #[arg(long, default_value = "bge-small-en-v1.5")] embedders: String, /// Comma-separated reranker ids to sweep. - #[arg( - long, - default_value = "off,jina-reranker-v1-turbo-en,jina-reranker-v1-tiny-en,ms-marco-tinybert-l-2-v2,ms-marco-minilm-l-4-v2" - )] + #[arg(long, default_value = "ms-marco-tinybert-l-2-v2")] rerankers: String, /// Candidate-pool size passed to retrieval before reranking. - #[arg(long, default_value_t = 12usize)] + #[arg(long, default_value_t = 6usize)] pool: usize, /// Final capsule cap after reranking. - #[arg(long, default_value_t = 4usize)] + #[arg(long, default_value_t = 3usize)] cap: usize, /// Directory to write per-combo JSON files and summary.md. #[arg(long, default_value = "bench/results")] @@ -1371,9 +1368,9 @@ struct TuneArgs { /// Show personal eval-set statistics without running the sweep. #[arg(long)] status: bool, - /// Cost penalty weight per estimated token injected per query. - /// Default 0.005 ≈ one MRR rank position ≈ 200 tokens. - #[arg(long, default_value_t = 0.005f64)] + /// Cost penalty per serialized UTF-8 byte upper bound, not billed tokens. + /// Default is explicit policy lambda 0.05 / delivery budget 6000. + #[arg(long, default_value_t = kimetsu_brain::tune::DEFAULT_COST_WEIGHT)] cost_weight: f64, /// Apply the winning config to project.toml (without this flag, dry-run only). #[arg(long)] diff --git a/crates/kimetsu-core/src/config.rs b/crates/kimetsu-core/src/config.rs index 3dc86b4..bb19538 100644 --- a/crates/kimetsu-core/src/config.rs +++ b/crates/kimetsu-core/src/config.rs @@ -122,6 +122,12 @@ impl ProjectConfig { if !self.embedder.enabled { return; } + // An explicit reranker opt-out outranks presets just like the embedder + // opt-out above. Nondefault models still require level="custom". + let reranker_off = matches!( + self.embedder.reranker.trim().to_ascii_lowercase().as_str(), + "" | "off" | "none" | "noop" + ); match self.retrieval.level.as_str() { "basic" => { self.embedder.enabled = false; @@ -141,6 +147,9 @@ impl ProjectConfig { } _ => {} // "custom" or unknown: leave as configured } + if reranker_off { + self.embedder.reranker = "off".into(); + } } /// True when the configured level enables HyDE query expansion. @@ -1786,6 +1795,18 @@ max_total_cost_usd = 250.0 assert!(!unknown.embedder.enabled, "unknown level must be a no-op"); } + #[test] + fn retrieval_level_never_reenables_explicit_reranker_off() { + for level in ["deep", "advanced"] { + let mut config = ProjectConfig::default_for_project("off"); + config.retrieval.level = level.into(); + config.embedder.reranker = "off".into(); + config.apply_retrieval_level(); + assert_eq!(config.embedder.reranker, "off"); + assert!(config.embedder.enabled); + } + } + /// The `[embedder] enabled = false` off-switch outranks every level /// preset: `level = "deep"` (or any other) must never re-enable a /// disabled embedder on config load. Regression test for the W3.1 diff --git a/crates/kimetsu-remote/src/lib.rs b/crates/kimetsu-remote/src/lib.rs index 4d1b113..8fe0d73 100644 --- a/crates/kimetsu-remote/src/lib.rs +++ b/crates/kimetsu-remote/src/lib.rs @@ -82,7 +82,15 @@ pub fn run_serve(args: config::ServeArgs) -> Result<(), String> { // On lean builds `open_reranker_for_model` always returns None. #[cfg(feature = "embeddings")] let reranker = { - let rr = kimetsu_brain::embeddings::open_reranker_for_model(&args.reranker); + if kimetsu_brain::embeddings::embedder_enabled_for_config(true) + && kimetsu_brain::embeddings::open_default_embedder().is_noop() + { + return Err( + "requested embedder failed to initialize; refusing a mislabeled semantic server" + .into(), + ); + } + let rr = kimetsu_brain::embeddings::open_reranker_checked(&args.reranker)?; match &rr { Some(r) => tracing::info!( model = r.model_id(), diff --git a/docs/canonical-evaluation.md b/docs/canonical-evaluation.md new file mode 100644 index 0000000..547561e --- /dev/null +++ b/docs/canonical-evaluation.md @@ -0,0 +1,17 @@ +# Canonical retrieval evaluation + +Brain context serving, the tuner, and local evaluation share `ServingPolicy`: pool 6, final cap 3, reranker score floor 0.30, and a default delivery budget of 6000. Explicit benchmark pool/cap experiments are reported as overrides. The final compact MCP renderer determines delivered capsules and cost after reranking, arbitration, compression, and admission. Cost is a conservative **serialized UTF-8 byte upper bound**, including the MCP text envelope and escaping. It is not billed tokens or a tokenizer measurement. + +Parity means the same effective query, configuration, models, cap, budget, and rendering inputs. Offline comparisons disable ambient augmentation and session warm-start; they cannot replay an unstored historical workspace snapshot. MCP can augment its query before this boundary and add a warm-start block through the final-budget helper. Evaluation substitutes a deterministic 26-character exposure ID for the real 26-character ULID. Capsule IDs are random but have the same serialized length. + +An omitted request floor inherits configuration. Explicit semantic/abstention `0` disables it, `-1` selects model auto, and a positive value sets the floor. The Rust request's optional override fields preserve the legacy numeric field's zero-as-inherited behavior. Lexical `0` also has an explicit override. A nondefault reranker requires `retrieval.level = "custom"`; explicit reranker `off` overrides all presets. Stdio caches configured rerankers across calls, including failed loads. Requested model initialization or inference failures are errors, not successful cross-encoder/semantic measurements. Lean serving remains explicitly lexical-only. Model aliases normalize before explicit loading, and benchmark artifacts include actual model provenance. + +Positive recall is the fraction of relevant IDs delivered, distinct from hit rate. MRR and recall use only positive cases. An explicitly empty fixture relevance list is a known negative, scored on abstention and false injection. Missing class denominators are reported separately. Uncited personal interactions are unknown. Personal labels are weak reliance observations joined by exact exposure ID, run, delivered memory ID, and claim revision; reliance on an old corrected claim cannot label its replacement relevant. Query capture remains opt-in. + +The default objective is `quality - 0.05 * mean_final_bound / 6000`. Quality is the mean of the available positive-MRR and known-negative-abstention components, giving them equal weight when both exist. The coefficient 0.05 is an explicit policy choice, not a fitted optimum or calibrated probability. For positive MRR 0.5, negative accuracy 1.0, and mean bound 512, quality is 0.75 and the objective is **0.7457333333**. A whole 6000-unit budget costs 0.05 quality units. Historical regret remains a diagnostic/retune trigger and never changes a candidate objective. + +The `--cost-weight` API retains its per-unit meaning; its default is now `0.05 / 6000`. New tune-history entries include measurement policy, units, coefficient, components, and model identity. Historical entries still deserialize with absent measurement metadata; their objective values are not comparable to the new policy. + +Train/holdout splitting joins connected aliases sharing a normalized query, task family, or any relevant/stale memory ID. Components are assigned deterministically from stable query/family identities, independent of input order and freshly seeded memory IDs. A single component has no independent holdout. + +Small personal datasets use a separately seeded fixture with mapped IDs, never fixture IDs against unrelated live memory rows. Fixture-only results cannot authorize applying settings to a personal brain. Application also requires explicit personal negative coverage in both partitions; the current reliance channel supplies positives and unknowns, so it cannot by itself authorize all-query automatic tuning. Such runs are diagnostic, not validated deployment recommendations. No real-model optimum or end-to-end causal benefit is established by unit tests. From 9965795656300dce975874a93390568515f38cce Mon Sep 17 00:00:00 2001 From: RodCor Date: Fri, 4 Sep 2026 23:42:29 -0300 Subject: [PATCH 16/34] fix(eval): preserve fixture expiry and supersession --- crates/kimetsu-cli/src/commands/bench.rs | 39 +++++++++++++++++++++--- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/crates/kimetsu-cli/src/commands/bench.rs b/crates/kimetsu-cli/src/commands/bench.rs index 3d23299..b710603 100644 --- a/crates/kimetsu-cli/src/commands/bench.rs +++ b/crates/kimetsu-cli/src/commands/bench.rs @@ -54,11 +54,11 @@ pub(crate) fn brain_eval_inner(args: EvalArgs) -> KimetsuResult<()> { let fixture: EvalFixture = serde_json::from_str(&fixture_text) .map_err(|e| format!("invalid fixture JSON in {}: {e}", fixture_path.display()))?; - // Validate: every relevant key must exist in memories. + // Validate every referenced key before seeding the temporary brain. let all_keys: std::collections::HashSet<&str> = fixture.memories.iter().map(|m| m.key.as_str()).collect(); for case in &fixture.cases { - for rel in &case.relevant { + for rel in case.relevant.iter().chain(&case.stale) { if !all_keys.contains(rel.as_str()) { return Err(format!( "fixture validation error: relevant key {:?} in query {:?} does not exist in memories", @@ -68,6 +68,15 @@ pub(crate) fn brain_eval_inner(args: EvalArgs) -> KimetsuResult<()> { } } } + for memory in &fixture.memories { + if let Some(survivor) = &memory.superseded_by_key { + if !all_keys.contains(survivor.as_str()) { + return Err( + format!("fixture references missing superseding key {survivor:?}").into(), + ); + } + } + } println!( "eval fixture: {} memories, {} cases", @@ -94,10 +103,32 @@ pub(crate) fn brain_eval_inner(args: EvalArgs) -> KimetsuResult<()> { ); let mut key_to_id: HashMap = HashMap::new(); for mem in &fixture.memories { - let memory_id = add_memory(&tmp_root, MemoryScope::Project, MemoryKind::Fact, &mem.text) - .map_err(|e| format!("add_memory {:?}: {e}", mem.key))?; + let memory_id = project::add_memory_with_validity( + &tmp_root, + MemoryScope::Project, + MemoryKind::Fact, + &mem.text, + None, + mem.valid_to.as_deref(), + ) + .map_err(|e| format!("add_memory {:?}: {e}", mem.key))?; key_to_id.insert(mem.key.clone(), memory_id); } + // Match the worker/tuner fixture contract: expiry is recorded at capture; + // supersession is applied after all survivor keys have their generated IDs. + { + let (_, _, conn) = project::load_project(&tmp_root)?; + for mem in &fixture.memories { + if let Some(survivor) = &mem.superseded_by_key { + let id = &key_to_id[&mem.key]; + conn.execute( + "UPDATE memories SET superseded_by=?2 WHERE memory_id=?1", + rusqlite::params![id, key_to_id[survivor]], + )?; + conn.execute("DELETE FROM memories_fts WHERE memory_id=?1", [id])?; + } + } + } // Build key → id lookup from the map (for ranking back to keys). let id_to_key: HashMap = key_to_id From 9c130deffc8fb81c771b65288599d59ee0d0e8f6 Mon Sep 17 00:00:00 2001 From: RodCor Date: Fri, 4 Sep 2026 23:50:01 -0300 Subject: [PATCH 17/34] fix(sync): replay merged lifecycle imports atomically --- crates/kimetsu-brain/src/projector.rs | 2 +- crates/kimetsu-brain/src/sync.rs | 402 +++++++++++++++++++++++--- tmp-tests/sync-followup-report.md | 18 ++ 3 files changed, 376 insertions(+), 46 deletions(-) create mode 100644 tmp-tests/sync-followup-report.md diff --git a/crates/kimetsu-brain/src/projector.rs b/crates/kimetsu-brain/src/projector.rs index 5f7d4a1..2d18e3c 100644 --- a/crates/kimetsu-brain/src/projector.rs +++ b/crates/kimetsu-brain/src/projector.rs @@ -109,7 +109,7 @@ pub fn rebuild_in_place(conn: &Connection) -> KimetsuResult { } /// Caller holds the SQLite writer lock across snapshot, reset and replay. -fn replay_locked(conn: &Connection) -> KimetsuResult { +pub(crate) fn replay_locked(conn: &Connection) -> KimetsuResult { let events = read_events_ordered(conn)?; let existing = { let mut stmt = conn.prepare("SELECT memory_id FROM memories")?; diff --git a/crates/kimetsu-brain/src/sync.rs b/crates/kimetsu-brain/src/sync.rs index bb72f84..1977a9f 100644 --- a/crates/kimetsu-brain/src/sync.rs +++ b/crates/kimetsu-brain/src/sync.rs @@ -10,6 +10,8 @@ /// - `memory.proposed` /// - `memory.rejected` /// - `memory.invalidated` +/// - `memory.restored` +/// - `memory.corrected` /// - `memory.cited` /// - `memory.superseded` /// @@ -45,7 +47,7 @@ /// 2. For every OTHER subdirectory (= other machine), read batches after /// the locally stored cursor for that machine, import them (idempotent), /// and advance the cursor. -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; use std::fs; use std::io::{BufRead, BufReader, Write as IoWrite}; use std::path::{Path, PathBuf}; @@ -72,6 +74,7 @@ const SYNC_ALLOWED_KINDS: &[&str] = &[ "memory.proposed", "memory.rejected", "memory.invalidated", + "memory.restored", "memory.corrected", "memory.cited", "memory.superseded", @@ -353,12 +356,15 @@ pub struct ImportSummary { /// dedup; we additionally count skips for reporting. /// /// When `dry_run` is true, parse and count but do NOT write anything. +/// Otherwise, stage the entire batch and replay the merged durable log under +/// one writer lock. Historical edits must not project against today's state. pub fn import_events( conn: &Connection, jsonl: &str, dry_run: bool, ) -> KimetsuResult { - let mut summary = ImportSummary::default(); + let mut excluded = 0; + let mut events = Vec::new(); for (line_no, line) in jsonl.lines().enumerate() { let line = line.trim(); if line.is_empty() { @@ -371,39 +377,57 @@ pub fn import_events( // already filters, but a hand-crafted batch might not). if !is_sync_allowed(&se.kind) { // Skip silently — telemetry/local kinds should never appear. - summary.skipped += 1; + excluded += 1; continue; } let event: Event = Event::try_from(se) .map_err(|e| format!("sync import: invalid event on line {}: {e}", line_no + 1))?; - // Check whether event_id already exists. - let exists: bool = conn - .query_row( - "SELECT 1 FROM events WHERE event_id = ?1", - rusqlite::params![event.event_id.to_string()], - |_| Ok(true), - ) - .optional()? - .unwrap_or(false); + events.push(event); + } - if exists { - summary.skipped += 1; - continue; - } + let mut summary = ImportSummary::default(); + let mut import = |c: &Connection| -> KimetsuResult<()> { + summary = ImportSummary { + applied: 0, + skipped: excluded, + }; + let mut seen = BTreeSet::new(); + for event in &events { + // Both the count and insertion run under the writer lock. Include + // in-batch duplicates in dry-run counts without writing them. + let exists: bool = !seen.insert(event.event_id.to_string()) + || c.query_row( + "SELECT 1 FROM events WHERE event_id = ?1", + rusqlite::params![event.event_id.to_string()], + |_| Ok(true), + ) + .optional()? + .unwrap_or(false); - if dry_run { + if exists { + summary.skipped += 1; + continue; + } + if !dry_run { + let redacted = Event { + payload: redact_event_payload(event), + ..event.clone() + }; + projector::insert_event(c, &redacted)?; + } summary.applied += 1; - continue; } - - // Apply through the projector: inserts into events table + projects - // into derived tables. The projector's `apply_events` wraps in a - // transaction; we call it one event at a time to keep the - // applied/skipped tally accurate. - projector::apply_events(conn, &[event])?; - summary.applied += 1; + if !dry_run && summary.applied > 0 { + projector::replay_locked(c)?; + } + Ok(()) + }; + if dry_run { + import(conn)?; + } else { + projector::with_write_txn(conn, import)?; } Ok(summary) } @@ -422,6 +446,10 @@ pub fn import_events_from_file( path: &Path, dry_run: bool, ) -> KimetsuResult { + import_events(conn, &read_batch_file(path)?, dry_run) +} + +fn read_batch_file(path: &Path) -> KimetsuResult { let file = fs::File::open(path) .map_err(|e| format!("sync import: cannot open {:?}: {e}", path.display()))?; let reader = BufReader::new(file); @@ -431,7 +459,7 @@ pub fn import_events_from_file( buf.push_str(&l); buf.push('\n'); } - import_events(conn, &buf, dry_run) + Ok(buf) } // --------------------------------------------------------------------------- @@ -548,9 +576,20 @@ pub fn pull_machine_batches( since_cursor: i64, dry_run: bool, ) -> KimetsuResult<(ImportSummary, i64)> { + let (jsonl, cursor) = read_machine_batches(sync_dir, source_machine_id, since_cursor)?; + Ok((import_events(conn, &jsonl, dry_run)?, cursor)) +} + +/// Read every pending batch before importing: prerequisites can be in a later +/// batch or on another peer, so directory sync merges these before replay. +fn read_machine_batches( + sync_dir: &Path, + source_machine_id: &str, + since_cursor: i64, +) -> KimetsuResult<(String, i64)> { let machine_dir = sync_dir.join(source_machine_id); if !machine_dir.exists() { - return Ok((ImportSummary::default(), since_cursor)); + return Ok((String::new(), since_cursor)); } // Collect batch files, parse their numeric stem (= the export cursor at @@ -579,17 +618,15 @@ pub fn pull_machine_batches( } batches.sort_by_key(|(c, _)| *c); - let mut total = ImportSummary::default(); + let mut jsonl = String::new(); let mut new_cursor = since_cursor; for (cursor_val, batch_path) in &batches { - let batch_summary = import_events_from_file(conn, batch_path, dry_run)?; - total.applied += batch_summary.applied; - total.skipped += batch_summary.skipped; + jsonl.push_str(&read_batch_file(batch_path)?); if *cursor_val > new_cursor { new_cursor = *cursor_val; } } - Ok((total, new_cursor)) + Ok((jsonl, new_cursor)) } /// Full sync cycle: @@ -637,33 +674,30 @@ pub fn sync_dir( } other_machines.sort(); // deterministic order + let mut incoming = String::new(); for other_id in &other_machines { let since = cursors.cursor_for(other_id); - let (pull_summary, new_cursor) = - pull_machine_batches(conn, sync_dir, other_id, since, dry_run)?; - total_applied += pull_summary.applied; - total_skipped += pull_summary.skipped; + let (jsonl, new_cursor) = read_machine_batches(sync_dir, other_id, since)?; if !dry_run && new_cursor > since { cursors.set_cursor(other_id, new_cursor); machines_pulled.push(other_id.clone()); - } else if dry_run && (pull_summary.applied + pull_summary.skipped) > 0 { + } else if dry_run && !jsonl.trim().is_empty() { machines_pulled.push(other_id.clone()); } + incoming.push_str(&jsonl); } + // Commit all peer events and their causal projection together before + // advancing pull cursors. A failed import leaves both unchanged. + let pull_summary = import_events(conn, &incoming, dry_run)?; + total_applied = pull_summary.applied; + total_skipped = pull_summary.skipped; + if !dry_run && !machines_pulled.is_empty() { cursors.save(cursors_path)?; } } - // Slice B: total-order replay. After importing peer events (which were - // applied incrementally in arrival order), re-project the merged log in HLC - // order so this brain converges to the SAME state every peer reaches, - // independent of import order. Skipped when nothing was pulled. - if !dry_run && total_applied > 0 { - projector::rebuild_in_place(conn)?; - } - Ok(SyncReport { pushed: push_summary.exported, pulled_applied: total_applied, @@ -822,6 +856,284 @@ mod tests { conn } + fn wire(events: &[Event]) -> String { + events + .iter() + .map(|event| serde_json::to_string(&SyncEvent::from(event)).unwrap()) + .collect::>() + .join("\n") + } + + #[test] + fn sync_replays_historical_correction_before_local_retirement() { + for reason in ["retired", "forgotten/archived"] { + let a = make_conn(); + let b = make_conn(); + let run = RunId::new(); + let accepted = Event::new( + run, + "memory.accepted", + json!({"memory_id":"m", "text":"old claim", "scope":"project", "kind":"fact"}), + ); + apply_events(&a, std::slice::from_ref(&accepted)).unwrap(); + apply_events(&b, &[accepted]).unwrap(); + let exposure = Event::new(run, "context.injected", json!({"memory_ids":["m"]})); + apply_events(&b, std::slice::from_ref(&exposure)).unwrap(); + let correction = Event::new( + run, + "memory.corrected", + json!({"memory_id":"m", "text":"corrected claim"}), + ); + apply_events(&a, std::slice::from_ref(&correction)).unwrap(); + apply_events( + &b, + &[Event::new( + run, + "memory.invalidated", + json!({"memory_id":"m", "reason":reason}), + )], + ) + .unwrap(); + let tmp = tempfile::tempdir().unwrap(); + let sd = tmp.path().join("sync"); + push_machine_batch(&a, &sd, "a", 0, false).unwrap(); + let cp = tmp.path().join("b-cursors.json"); + let report = sync_dir(&b, &sd, "b", &cp, false) + .expect("historical correction must replay before retirement"); + assert_eq!((report.pulled_applied, report.pulled_skipped), (1, 1)); + for _ in 0..2 { + let state: (String, String) = b + .query_row( + "SELECT text, invalidated_reason FROM memories WHERE memory_id='m'", + [], + |r| Ok((r.get(0)?, r.get(1)?)), + ) + .unwrap(); + assert_eq!(state, ("corrected claim".into(), reason.into())); + let binding: String = b.query_row("SELECT json_extract(payload_json,'$.memory_revisions.m') FROM events WHERE event_id=?1", [exposure.event_id.to_string()], |r| r.get(0)).unwrap(); + assert_eq!(binding, "baseline:m"); + assert_eq!( + b.query_row( + "SELECT count(*) FROM memories_fts WHERE memory_id='m'", + [], + |r| r.get::<_, i64>(0) + ) + .unwrap(), + 0 + ); + projector::rebuild_in_place(&b).unwrap(); + } + assert_eq!(SyncCursors::load(&cp).unwrap().cursor_for("a"), 2); + assert_eq!( + sync_dir(&b, &sd, "b", &cp, false).unwrap().pulled_applied, + 0 + ); + } + } + + #[test] + fn sync_archive_restore_round_trip() { + let a = make_conn(); + let b = make_conn(); + let run = RunId::new(); + apply_events(&a, &[Event::new(run, "memory.accepted", json!({"memory_id":"m", "text":"restorable claim", "scope":"project", "kind":"fact"}))]).unwrap(); + let tmp = tempfile::tempdir().unwrap(); + let sd = tmp.path().join("sync"); + let ca = tmp.path().join("a.json"); + let cb = tmp.path().join("b.json"); + sync_dir(&a, &sd, "a", &ca, false).unwrap(); + sync_dir(&b, &sd, "b", &cb, false).unwrap(); + for (kind, archived) in [("memory.invalidated", true), ("memory.restored", false)] { + apply_events( + &a, + &[Event::new( + run, + kind, + json!({"memory_id":"m", "reason":"forgotten/archived"}), + )], + ) + .unwrap(); + assert!(sync_dir(&a, &sd, "a", &ca, false).unwrap().pushed > 0); + assert_eq!( + sync_dir(&b, &sd, "b", &cb, false).unwrap().pulled_applied, + 1 + ); + let actual: bool = b + .query_row( + "SELECT invalidated_at IS NOT NULL FROM memories WHERE memory_id='m'", + [], + |r| r.get(0), + ) + .unwrap(); + assert_eq!(actual, archived); + assert_eq!( + b.query_row( + "SELECT count(*) FROM memories_fts WHERE memory_id='m'", + [], + |r| r.get::<_, i64>(0) + ) + .unwrap(), + if archived { 0 } else { 1 } + ); + } + assert_eq!( + sync_dir(&b, &sd, "b", &cb, false).unwrap().pulled_applied, + 0 + ); + } + + #[test] + fn sync_import_failure_rolls_back_entire_batch() { + for malformed_json in [false, true] { + let conn = make_conn(); + let run = RunId::new(); + let accepted = Event::new( + run, + "memory.accepted", + json!({"memory_id":"m", "text":"kept claim", "scope":"project", "kind":"fact"}), + ); + let bad = Event::new( + run, + "memory.corrected", + json!({"memory_id":"missing", "text":"bad claim"}), + ); + let input = if malformed_json { + format!("{}\n{{bad", wire(&[accepted])) + } else { + wire(&[accepted, bad]) + }; + assert!(import_events(&conn, &input, false).is_err()); + for table in ["events", "memories", "memory_revisions", "memories_fts"] { + assert_eq!( + conn.query_row(&format!("SELECT count(*) FROM {table}"), [], |r| r + .get::<_, i64>(0)) + .unwrap(), + 0, + "{table} must roll back" + ); + } + } + } + + #[test] + fn sync_directory_merges_peer_dependencies_before_replay() { + let conn = make_conn(); + let run = RunId::new(); + let accepted = Event::new( + run, + "memory.accepted", + json!({"memory_id":"m", "text":"old", "scope":"project", "kind":"fact"}), + ); + let correction = Event::new( + run, + "memory.corrected", + json!({"memory_id":"m", "text":"new"}), + ); + let tmp = tempfile::tempdir().unwrap(); + let sd = tmp.path().join("sync"); + atomic_write(&sd.join("a/2.jsonl"), wire(&[correction]).as_bytes()).unwrap(); + atomic_write(&sd.join("z/1.jsonl"), wire(&[accepted]).as_bytes()).unwrap(); + let cp = tmp.path().join("cursors.json"); + let report = sync_dir(&conn, &sd, "local", &cp, false) + .expect("replay must include all peers before resolving dependencies"); + assert_eq!(report.pulled_applied, 2); + assert_eq!( + conn.query_row("SELECT text FROM memories WHERE memory_id='m'", [], |r| { + r.get::<_, String>(0) + }) + .unwrap(), + "new" + ); + let cursors = SyncCursors::load(&cp).unwrap(); + assert_eq!((cursors.cursor_for("a"), cursors.cursor_for("z")), (2, 1)); + } + + #[test] + fn sync_directory_failure_preserves_projection_and_pull_cursors() { + let conn = make_conn(); + let run = RunId::new(); + let accepted = Event::new( + run, + "memory.accepted", + json!({"memory_id":"m", "text":"old", "scope":"project", "kind":"fact"}), + ); + apply_events(&conn, &[accepted]).unwrap(); + let correction = Event::new( + run, + "memory.corrected", + json!({"memory_id":"m", "text":"new"}), + ); + let bad = Event::new( + run, + "memory.corrected", + json!({"memory_id":"missing", "text":"bad"}), + ); + let tmp = tempfile::tempdir().unwrap(); + let sd = tmp.path().join("sync"); + atomic_write(&sd.join("a/2.jsonl"), wire(&[correction]).as_bytes()).unwrap(); + atomic_write(&sd.join("z/3.jsonl"), wire(&[bad]).as_bytes()).unwrap(); + let cp = tmp.path().join("cursors.json"); + assert!(sync_dir(&conn, &sd, "local", &cp, false).is_err()); + assert_eq!( + conn.query_row("SELECT text FROM memories WHERE memory_id='m'", [], |r| { + r.get::<_, String>(0) + }) + .unwrap(), + "old" + ); + assert_eq!( + conn.query_row("SELECT count(*) FROM events", [], |r| r.get::<_, i64>(0)) + .unwrap(), + 1 + ); + let cursors = SyncCursors::load(&cp).unwrap(); + assert_eq!((cursors.cursor_for("a"), cursors.cursor_for("z")), (0, 0)); + } + + #[test] + fn sync_import_refuses_to_erase_unlogged_memory() { + let conn = make_conn(); + conn.execute("INSERT INTO memories(memory_id,scope,kind,text,normalized_text,confidence,provenance_snapshot_json,created_at) VALUES ('legacy','global_user','fact','original','original',0.7,'{}','2020-01-01T00:00:00Z')", []).unwrap(); + let accepted = Event::new( + RunId::new(), + "memory.accepted", + json!({"memory_id":"m", "text":"new", "scope":"project", "kind":"fact"}), + ); + let error = import_events(&conn, &wire(&[accepted]), false).unwrap_err(); + assert!(error.to_string().contains("absent from replay")); + assert_eq!( + conn.query_row("SELECT count(*) FROM events", [], |r| r.get::<_, i64>(0)) + .unwrap(), + 0 + ); + assert_eq!( + conn.query_row( + "SELECT text FROM memories WHERE memory_id='legacy'", + [], + |r| r.get::<_, String>(0) + ) + .unwrap(), + "original" + ); + } + + #[test] + fn sync_import_counts_duplicate_lines_in_dry_run_and_commit() { + let conn = make_conn(); + let accepted = Event::new( + RunId::new(), + "memory.accepted", + json!({"memory_id":"m", "text":"new", "scope":"project", "kind":"fact"}), + ); + let input = wire(&[accepted.clone(), accepted]); + for dry in [true, false] { + let summary = import_events(&conn, &input, dry).unwrap(); + assert_eq!((summary.applied, summary.skipped), (1, 1)); + } + let summary = import_events(&conn, &input, false).unwrap(); + assert_eq!((summary.applied, summary.skipped), (0, 2)); + } + fn seed_events(conn: &Connection) -> (RunId, String, String) { let run_id = RunId::new(); let mem_id_a = format!("mem-{}", ulid::Ulid::new()); diff --git a/tmp-tests/sync-followup-report.md b/tmp-tests/sync-followup-report.md new file mode 100644 index 0000000..749fab3 --- /dev/null +++ b/tmp-tests/sync-followup-report.md @@ -0,0 +1,18 @@ +# Sync integration follow-up + +Scope: final-integration-review.md findings 6 and 7. Owned source is sync.rs plus the visibility of projector::replay_locked; no unrelated implementation edits, live brain/config access, model loads, downloads, or subagents. + +The importer previously projected individual historical events against the current projection before directory sync reached its final replay. A correction predating an already applied retirement therefore failed. Individual commits also retained earlier imported events if a subsequent JSON line or projection failed, and pull cursors were persisted before the final rebuild. memory.restored was missing from the replication allowlist. + +The importer now parses the batch first, stages unseen redacted events under BEGIN IMMEDIATE, and replays the merged durable log before committing. Duplicate counts are calculated under that same writer lock; dry-run uses an in-batch ID set so its counts match committed imports. Directory sync reads all pending peer batches before one import, permitting dependencies across peer directories and avoiding partial peer imports. Pull cursors are persisted only after a successful database commit. The existing replay helper preserves the missing-unlogged-memory guard and causal context.injected revision binding. Restoration now exports and imports as a durable lifecycle event. + +Seven new behavior regressions cover correction-before-retirement (both explicit invalidation and archival), archive/restore replication, whole-batch rollback on malformed JSON or invalid projections, prerequisites in a later peer directory, cross-peer rollback with unchanged pull cursors, legacy unlogged-memory protection, and consistent duplicate counts. The correction test also checks the earlier exposure's baseline revision binding, FTS exclusion, replay stability, and cursor/idempotency behavior. + +Verification on 2026-09-04: + +- RED: `cargo test --offline --locked -p kimetsu-brain --lib sync::tests` — 8 existing passed, all 7 new tests failed for their intended defect. Log: tmp-tests/sync-followup-red.log. This first run used the default worktree target before root clarified the shared target; no dependencies were downloaded. +- GREEN with `CARGO_TARGET_DIR=E:/Kimetsu/target`: same command — 15 passed, 0 failed. Log: tmp-tests/sync-followup-green.log. +- Covering with the same target: `cargo test --offline --locked -p kimetsu-brain --lib projector::` — 37 passed, 0 failed, including trace-import revision binding, missing-memory guard, correction rollback, and writer-lock snapshot regressions. Log: tmp-tests/sync-followup-projector.log. +- `rustfmt --edition 2024 crates/kimetsu-brain/src/sync.rs` and scoped `git diff --check` completed successfully. Git only reports the repository's LF-to-CRLF conversion warning. + +Limits: whole-log replay and pending batches require memory proportional to their size, consistent with the existing maintenance replay approach. Cursor files and SQLite cannot commit atomically; a cursor-file write failure after the database commit is recoverable by idempotent re-import. The independent push phase may already have published a local batch when a pull fails. Existing legacy HLC synthesis/order limitations and incomplete durable-log migration remain unchanged. Root owns independent integration review, the full suite, feature-enabled tests, and empirical model/latency validation. From 73d2dd23ccd964a519d73c0a5b49542c6c233303 Mon Sep 17 00:00:00 2001 From: RodCor Date: Fri, 4 Sep 2026 23:52:20 -0300 Subject: [PATCH 18/34] validate warm-start claims and isolate task delivery --- crates/kimetsu-brain/src/digest.rs | 274 ++++++++++++++--------- crates/kimetsu-brain/src/user_profile.rs | 30 ++- crates/kimetsu-chat/src/mcp_server.rs | 142 ++++++++++-- crates/kimetsu-cli/src/commands/bench.rs | 28 +-- crates/kimetsu-cli/src/commands/hooks.rs | 2 +- docs/canonical-evaluation.md | 2 + docs/warm-start-contract.md | 11 + 7 files changed, 341 insertions(+), 148 deletions(-) create mode 100644 docs/warm-start-contract.md diff --git a/crates/kimetsu-brain/src/digest.rs b/crates/kimetsu-brain/src/digest.rs index a302df3..7a7962d 100644 --- a/crates/kimetsu-brain/src/digest.rs +++ b/crates/kimetsu-brain/src/digest.rs @@ -3,12 +3,11 @@ //! Builds a compact ~400-token digest of the current repo state: //! - top-usefulness memories (conventions/facts that matter most) //! - repo manifest summary (Cargo.toml, package.json, …) -//! - recent run focus ("current focus" from run history) +//! Task focus is delivered separately through identity-scoped resume. //! -//! The digest is cached in `.kimetsu/digest.md`, keyed by a SHA-256 -//! CONTENT HASH of the inputs. Staleness is detected cheaply (git HEAD -//! change, manifest hash change, memory corpus change) and the rebuild -//! runs detached so it never blocks SessionStart. +//! The digest is cached in `.kimetsu/digest.md`, keyed by a non-cryptographic +//! CONTENT HASH of current inputs. Warm delivery validates the inputs +//! synchronously so corrected or expired claims cannot survive in cached text. //! //! ## Cheap-model vs rule-based //! @@ -41,8 +40,6 @@ use crate::project::{load_project, load_project_readonly}; const DIGEST_CHAR_BUDGET: usize = 1_600; /// Number of top-useful memories to include in the digest. const TOP_MEMORY_COUNT: usize = 5; -/// Number of recent run titles to include in "current focus". -const RECENT_RUNS_COUNT: usize = 3; /// Max chars per memory text included in digest. const MEMORY_SNIPPET_CHARS: usize = 180; @@ -50,7 +47,7 @@ const MEMORY_SNIPPET_CHARS: usize = 180; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DigestMeta { - /// SHA-256-like content hash of the inputs (via DefaultHasher for speed). + /// Non-cryptographic content hash of the inputs (DefaultHasher). pub input_hash: u64, /// ISO-8601 timestamp when this digest was built. pub built_at: String, @@ -118,12 +115,9 @@ fn build_or_load_digest_inner( /// Read `.kimetsu/digest.md` verbatim, without checking whether it is /// still current. /// -/// This is the warm-path counterpart to [`build_or_load_digest`]: the -/// caller serves the cached text immediately and rebuilds off the hot -/// path (see [`is_stale`]), instead of paying a synchronous rebuild the -/// moment the corpus moves. Returns `None` when the brain is not -/// initialized here or nothing has been cached yet — a cold start still -/// has to build. +/// This diagnostic raw read may return stale text. Model-facing warm delivery +/// uses [`build_or_load_digest`] to validate current inputs before cache reuse. +/// Returns `None` when the brain is not initialized or no cache exists. pub fn load_cached_digest(workspace: &Path) -> Option { let (paths, _config, _conn) = load_project_readonly(workspace).ok()?; let text = std::fs::read_to_string(paths.kimetsu_dir.join("digest.md")).ok()?; @@ -138,11 +132,9 @@ pub fn load_cached_digest(workspace: &Path) -> Option { /// Returns `true` when the cached digest is stale and should be rebuilt. /// -/// Cheap: only checks the content hash (no I/O heavier than reading the -/// meta sidecar and querying two SQLite count rows). +/// Reads the metadata and current bounded digest inputs to compare their hash. /// -/// Used by the SessionStart hook to decide whether to spawn a detached -/// rebuild before injecting the (potentially stale) cached digest. +/// Diagnostic helper; warm delivery validates through build_or_load_digest. pub fn is_stale(workspace: &Path) -> bool { is_stale_inner(workspace).unwrap_or(false) } @@ -178,9 +170,8 @@ fn is_stale_inner(workspace: &Path) -> KimetsuResult { /// Returns `None` when `[broker] warm_start` is off, or when there is no /// digest, no preferences and no live episode to report. /// -/// The cached digest is served even when the corpus has moved under it, and the -/// rebuild is spawned detached — a synchronous rebuild would sit in front of the -/// agent's first turn. Only a cold brain (nothing cached yet) builds inline. +/// Current inputs are checked before cached text is used. Rule-based rebuilds +/// run synchronously when claims change or temporal validity crosses a boundary. /// /// Records ROI attribution as a side effect, so call it only when the block is /// actually going to be emitted. @@ -188,6 +179,22 @@ pub fn warm_start_block(workspace: &Path) -> Option { warm_start_block_scoped(workspace, "") } pub fn warm_start_block_scoped(workspace: &Path, identity: &str) -> Option { + let block = prepare_warm_start_block_scoped(workspace, identity)?; + record_warmstart_served(workspace, block.digest_chars, block.resume_chars); + Some(block.context) +} + +/// Prepared text carries no delivery attribution until the caller emits it. +pub struct PreparedWarmStart { + pub context: String, + pub digest_chars: usize, + pub resume_chars: usize, +} + +pub fn prepare_warm_start_block_scoped( + workspace: &Path, + identity: &str, +) -> Option { // Gate: load warm_start from config (best-effort; default ON). let warm_start_enabled = kimetsu_core::paths::ProjectPaths::discover(workspace) .ok() @@ -198,15 +205,10 @@ pub fn warm_start_block_scoped(workspace: &Path, identity: &str) -> Option { - if is_stale(workspace) { - spawn_detached_refresh(workspace); - } - Some(cached) - } - None => build_or_load_digest(workspace, false), - }; + // Validate current claim text, retirement and temporal applicability before + // using a cached overview. A stale-while-revalidate policy reintroduces + // facts the retrieval path deliberately rejected. + let digest = build_or_load_digest(workspace, false); let resume = crate::episode::render_resume_context_scoped(workspace, identity); // v2.6: the user's standing preferences, delivered rather than retrieved. @@ -244,13 +246,11 @@ pub fn warm_start_block_scoped(workspace: &Path, identity: &str) -> Option Option { /// Best-effort: an unreadable brain means no preferences block, never a failed /// warm start. fn user_profile_block(workspace: &Path) -> Option { - let (_paths, _config, conn) = load_project_readonly(workspace).ok()?; + let (_paths, config, conn) = load_project_readonly(workspace).ok()?; // The cross-project user brain is opened separately; when it is disabled or // unreachable the project's own preferences stand on their own. - let user_conn = kimetsu_core::paths::user_brain_db_path() - .filter(|path| path.exists()) - .and_then(|path| Connection::open(&path).ok()); + let user_conn = + crate::user_brain::open_user_brain_readonly_for_config(config.kimetsu.use_user_brain) + .ok() + .flatten(); let profile = crate::user_profile::build_profile(&conn, user_conn.as_ref()).ok()?; crate::user_profile::render_profile(&profile) } -/// Fire-and-forget ` brain digest --refresh --workspace `. -/// -/// Assumes the running executable is the kimetsu CLI, which holds for every -/// caller of [`warm_start_block`] (the hooks and the MCP server are both the -/// `kimetsu` binary). Embedders of this crate that are not the CLI simply get a -/// spawn that fails and is swallowed — a stale digest, never a broken host. -/// -/// Fully detached with null stdio, mirroring the embed daemon's spawn: an -/// inherited stdout pipe would hold the host's hook open until its timeout. -fn spawn_detached_refresh(workspace: &Path) { - let Ok(exe) = std::env::current_exe() else { - return; - }; - let mut cmd = std::process::Command::new(exe); - cmd.args(["brain", "digest", "--refresh", "--workspace"]) - .arg(workspace) - .stdin(std::process::Stdio::null()) - .stdout(std::process::Stdio::null()) - .stderr(std::process::Stdio::null()); - #[cfg(windows)] - { - use std::os::windows::process::CommandExt; - // DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP - cmd.creation_flags(0x0000_0008 | 0x0000_0200); - } - let _ = cmd.spawn(); -} - // ── ROI attribution ─────────────────────────────────────────────────────────── /// Record ROI attribution events for the warm-start injection. @@ -368,13 +341,11 @@ struct DigestInputs { top_memories: Vec<(String, String)>, /// Manifest summaries: `(manifest_kind, path)` e.g. ("cargo", "Cargo.toml"). manifests: Vec<(String, String)>, - /// Recent run task titles. - recent_runs: Vec, } impl DigestInputs { fn is_empty(&self) -> bool { - self.top_memories.is_empty() && self.manifests.is_empty() && self.recent_runs.is_empty() + self.top_memories.is_empty() && self.manifests.is_empty() } } @@ -398,6 +369,8 @@ fn gather_inputs(conn: &Connection, repo_root: &str) -> KimetsuResult julianday('now')) AND kind != 'preference' ORDER BY CASE WHEN use_count > 0 @@ -433,25 +406,7 @@ fn gather_inputs(conn: &Connection, repo_root: &str) -> KimetsuResult(0) - })?; - for task in rows.flatten() { - if !task.trim().is_empty() { - inputs.recent_runs.push(task); - } - } - } + // Task focus belongs exclusively to the identity-scoped resume block. Ok(inputs) } @@ -468,9 +423,6 @@ fn content_hash(inputs: &DigestInputs) -> u64 { mk.hash(&mut h); mp.hash(&mut h); } - for task in &inputs.recent_runs { - task.hash(&mut h); - } h.finish() } @@ -492,19 +444,6 @@ fn assemble_rule_based( parts.push(format!("Project manifests: {}", manifest_list.join(", "))); } - // Current focus. - if !inputs.recent_runs.is_empty() { - let focus = inputs - .recent_runs - .iter() - .map(|t| t.trim().to_string()) - .filter(|t| !t.is_empty()) - .collect::>(); - if !focus.is_empty() { - parts.push(format!("Current focus: {}", focus.join(" / "))); - } - } - // Top memories. if !inputs.top_memories.is_empty() { parts.push("Key conventions and facts:".to_string()); @@ -585,6 +524,126 @@ mod tests { use super::*; use crate::{project, user_brain}; + #[test] + fn hardening_warm_profile_honors_user_brain_opt_out() { + user_brain::with_user_brain_disabled(|| { + let dir = tmp_workspace("hardening-warm-profile-off"); + git_init_boundary(&dir); + project::init_project(&dir, false).unwrap(); + let global_dir = dir.join("isolated-global"); + std::fs::create_dir_all(&global_dir).unwrap(); + // The shared test-env lock is held by with_user_brain_disabled. + unsafe { + std::env::set_var("KIMETSU_USER_BRAIN_DIR", &global_dir); + } + let global = + Connection::open(kimetsu_core::paths::user_brain_db_path().unwrap()).unwrap(); + crate::schema::initialize(&global).unwrap(); + global.execute("INSERT INTO memories(memory_id,scope,kind,text,normalized_text,confidence,provenance_snapshot_json,created_at) VALUES('global','global_user','preference','PRIVATE_GLOBAL','private_global',1.0,'{}','2026-01-01T00:00:00Z')", []).unwrap(); + let env_disabled = user_profile_block(&dir); + let (paths, mut config, conn) = load_project_readonly(&dir).unwrap(); + config.kimetsu.use_user_brain = false; + std::fs::write(paths.project_toml, config.to_toml().unwrap()).unwrap(); + unsafe { + std::env::remove_var("KIMETSU_USER_BRAIN"); + } + let config_disabled = user_profile_block(&dir); + unsafe { + std::env::set_var("KIMETSU_USER_BRAIN", "0"); + std::env::remove_var("KIMETSU_USER_BRAIN_DIR"); + } + drop(conn); + drop(global); + std::fs::remove_dir_all(dir).unwrap(); + assert!( + env_disabled.is_none(), + "environment opt-out leaked global profile" + ); + assert!( + config_disabled.is_none(), + "project opt-out leaked global profile" + ); + }); + } + + #[test] + fn hardening_warm_digest_revalidates_corrected_and_retired_claims() { + user_brain::with_user_brain_disabled(|| { + let dir = tmp_workspace("hardening-warm-current"); + git_init_boundary(&dir); + project::init_project(&dir, false).unwrap(); + let id = project::add_memory( + &dir, + kimetsu_core::memory::MemoryScope::Project, + kimetsu_core::memory::MemoryKind::Fact, + "ORIGINAL port is 4001", + ) + .unwrap(); + assert!( + build_or_load_digest(&dir, true) + .unwrap() + .contains("ORIGINAL") + ); + project::edit_memory(&dir, &id, Some("CORRECTED port is 4002"), None).unwrap(); + let block = warm_start_block_scoped(&dir, "lane-a").unwrap(); + assert!( + !block.contains("ORIGINAL"), + "stale cache must never reintroduce corrected text" + ); + assert!(block.contains("CORRECTED")); + project::invalidate_memory(&dir, &id, Some("wrong claim")).unwrap(); + assert!( + !warm_start_block_scoped(&dir, "lane-a") + .unwrap_or_default() + .contains("CORRECTED") + ); + std::fs::remove_dir_all(dir).unwrap(); + }); + } + + #[test] + fn hardening_warm_digest_excludes_invalid_time_and_other_task_focus() { + user_brain::with_user_brain_disabled(|| { + let dir = tmp_workspace("hardening-warm-validity"); + git_init_boundary(&dir); + project::init_project(&dir, false).unwrap(); + for (text, from, to) in [ + ("CURRENT endpoint", None, None), + ("FUTURE endpoint", Some("2099-01-01T00:00:00Z"), None), + ("EXPIRED endpoint", None, Some("2020-01-01T00:00:00Z")), + ] { + project::add_memory_with_validity( + &dir, + kimetsu_core::memory::MemoryScope::Project, + kimetsu_core::memory::MemoryKind::Fact, + text, + from, + to, + ) + .unwrap(); + } + for lane in ["ALPHA", "BETA"] { + crate::episode::capture_episode( + &dir, + crate::episode::EpisodePayload { + identity: lane.into(), + task: format!("{lane} task title"), + summary: format!("{lane} task state"), + ..Default::default() + }, + ) + .unwrap(); + } + let block = warm_start_block_scoped(&dir, "ALPHA").unwrap(); + assert!(block.contains("CURRENT") && block.contains("ALPHA")); + assert!( + !block.contains("FUTURE") && !block.contains("EXPIRED") && !block.contains("BETA"), + "{block}" + ); + std::fs::remove_dir_all(dir).unwrap(); + }); + } + fn tmp_workspace(name: &str) -> std::path::PathBuf { let ts = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) @@ -814,7 +873,6 @@ mod tests { manifests: (0..5) .map(|i| ("cargo".to_string(), format!("Cargo{i}.toml"))) .collect(), - recent_runs: (0..5).map(|i| format!("task {i}")).collect(), }; let config = kimetsu_core::config::ProjectConfig::default_for_project("test"); let digest = assemble_rule_based(&inputs, &config).expect("assemble"); diff --git a/crates/kimetsu-brain/src/user_profile.rs b/crates/kimetsu-brain/src/user_profile.rs index 5d02133..2b2507c 100644 --- a/crates/kimetsu-brain/src/user_profile.rs +++ b/crates/kimetsu-brain/src/user_profile.rs @@ -71,7 +71,8 @@ pub fn preferences( WHERE kind = 'preference' AND invalidated_at IS NULL AND superseded_by IS NULL - AND (valid_to IS NULL OR valid_to > datetime('now')) + AND (valid_from IS NULL OR julianday(valid_from) <= julianday('now')) + AND (valid_to IS NULL OR julianday(valid_to) > julianday('now')) ORDER BY usefulness_score DESC, created_at DESC LIMIT ?1", )?; @@ -218,6 +219,33 @@ mod tests { assert_eq!(profile[0].memory_id, "live"); } + #[test] + fn hardening_profile_checks_numeric_start_and_expiry() { + let c = conn(); + for id in ["current", "future", "expired", "invalid"] { + insert(&c, id, "preference", id, 0.0); + } + c.execute( + "UPDATE memories SET valid_from='2099-01-01T00:00:00Z' WHERE memory_id='future'", + [], + ) + .unwrap(); + c.execute("UPDATE memories SET valid_to=strftime('%Y-%m-%dT%H:%M:%SZ','now','-1 minute') WHERE memory_id='expired'", []).unwrap(); + c.execute( + "UPDATE memories SET valid_from='not-a-date' WHERE memory_id='invalid'", + [], + ) + .unwrap(); + let profile = preferences(&c, false, 10).unwrap(); + assert_eq!( + profile + .iter() + .map(|p| p.memory_id.as_str()) + .collect::>(), + vec!["current"] + ); + } + /// Specificity wins when the budget runs out: a preference stated for this /// repo beats one carried across every project. #[test] diff --git a/crates/kimetsu-chat/src/mcp_server.rs b/crates/kimetsu-chat/src/mcp_server.rs index ae20bbd..ccdf3db 100644 --- a/crates/kimetsu-chat/src/mcp_server.rs +++ b/crates/kimetsu-chat/src/mcp_server.rs @@ -12,7 +12,7 @@ use crate::bridge::{ }; use crate::skills::{SkillConfig, SkillRegistry, skill_origin_label}; -const KIMETSU_MCP_INSTRUCTIONS: &str = "Kimetsu is a persistent brain sidecar: it accumulates generalizable knowledge across sessions and retrieves it on demand. Retrieve with kimetsu_brain_context when you start a task. A `skipped: true` reply means the brain held nothing relevant; the reply still has a small input-token cost — retrieving is cheaper than rediscovering. Record with kimetsu_brain_record once you know something a later session would otherwise have to work out again: a constraint that was not obvious, an approach that turned out to be wrong, a convention this project follows. Concrete and actionable, with 2-5 domain tags. Cite with kimetsu_brain_cite when a retrieved memory changed what you did. Citations are the brain's only evidence about which memories earn their place; an uncited memory reads as unused. For Terminal-Bench tasks use kimetsu_benchmark_context instead — it prioritizes semantic_operator and anti_pattern memories over episodic summaries. kimetsu_bridge_status and kimetsu_skills_search surface portable skills."; +const KIMETSU_MCP_INSTRUCTIONS: &str = "Kimetsu is a persistent brain sidecar: it accumulates generalizable knowledge across sessions and retrieves it on demand. Retrieve with kimetsu_brain_context when you start a task. A `skipped: true` reply means the brain held nothing relevant; the reply still has a small input-token cost — retrieving is cheaper than rediscovering. Record with kimetsu_brain_record once you know something a later session would otherwise have to work out again: a constraint that was not obvious, an approach that turned out to be wrong, a convention this project follows. Concrete and actionable, with 2-5 domain tags. Cite with kimetsu_brain_cite when a retrieved memory changed what you did. Citations record reliance, not verification. Missing citations leave usefulness unknown; observed outcomes are associations with the delivered evidence. For Terminal-Bench tasks use kimetsu_benchmark_context instead — it prioritizes semantic_operator and anti_pattern memories over episodic summaries. kimetsu_bridge_status and kimetsu_skills_search surface portable skills."; const BRAIN_STATUS_DESCRIPTION: &str = "Inspect the Kimetsu brain for this workspace. Use this to see whether brain.db is initialized, how many memories/runs/proposals exist, and which memories have positive outcome usefulness. Call before relying on memory if you need to know whether the brain has signal."; @@ -32,7 +32,7 @@ const BENCHMARK_RECORD_OUTCOME_DESCRIPTION: &str = "Record a benchmark attempt i const BRAIN_MEMORY_LIST_DESCRIPTION: &str = "List recent accepted Kimetsu memories with confidence, use count, and usefulness score. Use when you need to understand the durable memory pool or pick a memory id for invalidation."; -const BRAIN_MEMORY_TOP_DESCRIPTION: &str = "List outcome-ranked Kimetsu memories by usefulness_score/use_count. Use this to see which memories have actually helped previous runs and should be trusted more than fresh or low-signal memories."; +const BRAIN_MEMORY_TOP_DESCRIPTION: &str = "List outcome-ranked Kimetsu memories by usefulness_score/use_count. Use this to inspect outcome associations; these scores do not independently verify claims or override their origin."; const BRAIN_MEMORY_ADD_DESCRIPTION: &str = "Add a durable Kimetsu memory manually. Use only when the user states a reusable preference, convention, command, failure pattern, or fact that should influence future runs. This writes a memory.accepted event."; @@ -655,29 +655,45 @@ fn parse_shared_retrieval_args( } } -/// Latch for the once-per-session warm start on the stdio MCP path. -/// -/// One `kimetsu mcp serve` process is one host session, which makes a process -/// latch a session latch. Deliberately not consulted by the remote server: one -/// `kimetsu-remote` process fans out across many sessions and repos. -static WARM_START_SERVED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false); +/// The stdio loop is sequential. Keep a bounded recent-lane delivery cache; +/// remote requests use their own session policy and do not consult this cache. +type WarmStartKey = (PathBuf, String); +static WARM_START_SERVED: std::sync::Mutex> = + std::sync::Mutex::new(std::collections::VecDeque::new()); +const MAX_WARM_START_LANES: usize = 256; -/// Take the session's warm-start block, or `None` if it has already been served -/// (or there is nothing to serve). -/// -/// The latch is only set once a block actually exists, so a call made against a -/// cold brain does not burn the session's one chance at a warm start. -fn take_session_warm_start(workspace: &Path, arguments: &Value) -> Option { - use std::sync::atomic::Ordering; - if WARM_START_SERVED.load(Ordering::SeqCst) { +fn take_session_warm_start( + workspace: &Path, + arguments: &Value, +) -> Option<(WarmStartKey, kimetsu_brain::digest::PreparedWarmStart)> { + let identity = kimetsu_brain::episode::requested_identity(arguments).unwrap_or(""); + let root = kimetsu_core::paths::ProjectPaths::discover(workspace) + .ok()? + .repo_root; + let key = (root, identity.to_owned()); + if WARM_START_SERVED + .lock() + .unwrap_or_else(|p| p.into_inner()) + .contains(&key) + { return None; } - let identity = kimetsu_brain::episode::requested_identity(&arguments).unwrap_or(""); - let block = kimetsu_brain::digest::warm_start_block_scoped(workspace, identity)?; - if WARM_START_SERVED.swap(true, Ordering::SeqCst) { - return None; // lost the race — another call is already emitting it + let block = kimetsu_brain::digest::prepare_warm_start_block_scoped(workspace, identity)?; + Some((key, block)) +} + +fn mark_session_warm_start_served(key: WarmStartKey) { + // Extremely large client identities remain usable but are not retained. + if key.0.as_os_str().len() + key.1.len() > 4096 { + return; + } + let mut served = WARM_START_SERVED.lock().unwrap_or_else(|p| p.into_inner()); + if !served.contains(&key) { + if served.len() >= MAX_WARM_START_LANES { + served.pop_front(); + } + served.push_back(key); } - Some(block) } fn kimetsu_brain_context(workspace: &Path, arguments: &Value) -> Value { @@ -705,12 +721,24 @@ fn stdio_brain_context_with_loader( } else { load(&config.embedder.reranker)? }; - brain_context_tool_with_warm( + let warm = take_session_warm_start(workspace, arguments); + let output = brain_context_tool_with_warm( workspace, arguments, reranker.as_deref(), - take_session_warm_start(workspace, arguments), - ) + warm.as_ref().map(|(_, block)| block.context.clone()), + )?; + if let Some((key, block)) = warm { + if output["warm_start"]["context"].as_str() == Some(block.context.as_str()) { + mark_session_warm_start_served(key); + kimetsu_brain::digest::record_warmstart_served( + workspace, + block.digest_chars, + block.resume_chars, + ); + } + } + Ok(output) })(); result.unwrap_or_else(|e| { bounded_context_error(arguments, 6000, brain_unavailable_json(workspace, &e)) @@ -2681,6 +2709,72 @@ mod tests { }); } + #[test] + fn hardening_mcp_warm_identity_and_retry_after_budget_omission() { + kimetsu_brain::user_brain::with_user_brain_disabled(|| { + let root = temp_root("hardening-mcp-warm-lanes"); + project::init_project(&root, false).unwrap(); + for lane in ["ALPHA", "BETA", "GAMMA"] { + kimetsu_brain::episode::capture_episode( + &root, + kimetsu_brain::episode::EpisodePayload { + identity: lane.into(), + task: format!("{lane} task"), + summary: format!("{lane} progress {}", "state details ".repeat(30)), + ..Default::default() + }, + ) + .unwrap(); + } + let ask = |lane: &str, budget: u32| { + stdio_brain_context_with_loader( + &root, + &json!({"query":"unrelated query","task_id":lane,"include_ambient":false,"budget_tokens":budget}), + |_| Ok(None), + ) + }; + let alpha = ask("ALPHA", 6000); + assert!( + alpha["warm_start"]["context"] + .as_str() + .unwrap_or("") + .contains("ALPHA"), + "{alpha}" + ); + let beta = ask("BETA", 6000); + assert!( + beta["warm_start"]["context"] + .as_str() + .unwrap_or("") + .contains("BETA"), + "one lane must not consume another's warm start: {beta}" + ); + assert!(ask("ALPHA", 6000).get("warm_start").is_none()); + let (_, _, conn) = project::load_project_readonly(&root).unwrap(); + let count = || { + conn.query_row( + "SELECT COUNT(*) FROM events WHERE kind IN ('digest_served','resume_served')", + [], + |r| r.get::<_, i64>(0), + ) + .unwrap() + }; + let before = count(); + assert!(ask("GAMMA", 512).get("warm_start").is_none()); + assert_eq!(count(), before, "omitted warm text is not a served event"); + let gamma = ask("GAMMA", 6000); + assert!( + gamma["warm_start"]["context"] + .as_str() + .unwrap_or("") + .contains("GAMMA"), + "budget omission must leave a retry: {gamma}" + ); + drop(conn); + std::fs::remove_dir_all(root).unwrap(); + }); + } + #[test] fn hardening_served_ids_and_revisions_match_final_mcp_payload() { kimetsu_brain::user_brain::with_user_brain_disabled(|| { diff --git a/crates/kimetsu-cli/src/commands/bench.rs b/crates/kimetsu-cli/src/commands/bench.rs index b710603..36ba732 100644 --- a/crates/kimetsu-cli/src/commands/bench.rs +++ b/crates/kimetsu-cli/src/commands/bench.rs @@ -333,8 +333,8 @@ pub(crate) fn brain_eval_inner(args: EvalArgs) -> KimetsuResult<()> { struct RankerBenchRow { label: String, load_ms: u128, - rerank_mean_ms: f64, - rerank_max_ms: u128, + delivery_mean_ms: f64, + delivery_max_ms: u128, r2: f64, r4: f64, mrr: f64, @@ -366,7 +366,7 @@ pub(crate) fn brain_eval_inner(args: EvalArgs) -> KimetsuResult<()> { let rr = reranker_ref.unwrap(); let mut per_case_ranked: Vec> = Vec::new(); - let mut rerank_times_ms: Vec = Vec::new(); + let mut delivery_times_ms: Vec = Vec::new(); for case in fixture.cases.iter() { let policy = kimetsu_brain::serving::ServingPolicy { @@ -388,7 +388,7 @@ pub(crate) fn brain_eval_inner(args: EvalArgs) -> KimetsuResult<()> { Some(rr), kimetsu_brain::serving::EVAL_EXPOSURE_ID, )?; - rerank_times_ms.push(rr_start.elapsed().as_millis()); + delivery_times_ms.push(rr_start.elapsed().as_millis()); let ranked_keys: Vec = bundle .capsules @@ -405,12 +405,12 @@ pub(crate) fn brain_eval_inner(args: EvalArgs) -> KimetsuResult<()> { let (r2, r4, mrr_val, noise) = compute_metrics(&per_case_ranked); - let rerank_mean_ms = if rerank_times_ms.is_empty() { + let delivery_mean_ms = if delivery_times_ms.is_empty() { 0.0 } else { - rerank_times_ms.iter().sum::() as f64 / rerank_times_ms.len() as f64 + delivery_times_ms.iter().sum::() as f64 / delivery_times_ms.len() as f64 }; - let rerank_max_ms = rerank_times_ms.into_iter().max().unwrap_or(0); + let delivery_max_ms = delivery_times_ms.into_iter().max().unwrap_or(0); // Try to find the ONNX file size on disk (best-effort, no panic on miss). let onnx_kb: Option = { @@ -462,8 +462,8 @@ pub(crate) fn brain_eval_inner(args: EvalArgs) -> KimetsuResult<()> { Ok(RankerBenchRow { label: rr_id.to_string(), load_ms, - rerank_mean_ms, - rerank_max_ms, + delivery_mean_ms, + delivery_max_ms, r2, r4, mrr: mrr_val, @@ -473,7 +473,7 @@ pub(crate) fn brain_eval_inner(args: EvalArgs) -> KimetsuResult<()> { }; println!(); - println!("=== Reranker benchmark (semantic base + per-reranker) ==="); + println!("=== Reranker comparison (full query-to-delivery latency) ==="); println!(); // Print the semantic-only baseline row for comparison. @@ -482,8 +482,8 @@ pub(crate) fn brain_eval_inner(args: EvalArgs) -> KimetsuResult<()> { "{:9} {:>14} {:>13} {:>10} {:>10} {:>10} {:>8} {:>10}", "reranker", "load_ms", - "rerank_mean_ms", - "rerank_max_ms", + "delivery_mean_ms", + "delivery_max_ms", "recall@2", "recall@4", "MRR", @@ -513,8 +513,8 @@ pub(crate) fn brain_eval_inner(args: EvalArgs) -> KimetsuResult<()> { "{:9} {:>14.1} {:>13} {:>10.3} {:>10.3} {:>10.3} {:>8.1} {:>10}", row.label, row.load_ms, - row.rerank_mean_ms, - row.rerank_max_ms, + row.delivery_mean_ms, + row.delivery_max_ms, row.r2, row.r4, row.mrr, diff --git a/crates/kimetsu-cli/src/commands/hooks.rs b/crates/kimetsu-cli/src/commands/hooks.rs index 323470e..7ab476e 100644 --- a/crates/kimetsu-cli/src/commands/hooks.rs +++ b/crates/kimetsu-cli/src/commands/hooks.rs @@ -288,7 +288,7 @@ pub(crate) fn brain_context_hook(args: ContextHookArgs) -> KimetsuResult<()> { // guard. Only the first rendered capsule (idx == 0) can be answer-grade // (it's the top-ranked capsule); subsequent capsules are never marked. if idx == 0 && answer_grade_handle.is_some() { - additional_context.push_str("Verified answer from project memory: "); + additional_context.push_str("Relevant project memory (not independently verified): "); } additional_context.push_str(&text); } diff --git a/docs/canonical-evaluation.md b/docs/canonical-evaluation.md index 547561e..df7cd98 100644 --- a/docs/canonical-evaluation.md +++ b/docs/canonical-evaluation.md @@ -15,3 +15,5 @@ The `--cost-weight` API retains its per-unit meaning; its default is now `0.05 / Train/holdout splitting joins connected aliases sharing a normalized query, task family, or any relevant/stale memory ID. Components are assigned deterministically from stable query/family identities, independent of input order and freshly seeded memory IDs. A single component has no independent holdout. Small personal datasets use a separately seeded fixture with mapped IDs, never fixture IDs against unrelated live memory rows. Fixture-only results cannot authorize applying settings to a personal brain. Application also requires explicit personal negative coverage in both partitions; the current reliance channel supplies positives and unknowns, so it cannot by itself authorize all-query automatic tuning. Such runs are diagnostic, not validated deployment recommendations. No real-model optimum or end-to-end causal benefit is established by unit tests. + +The optional `brain eval` reranker comparison labels its timing `delivery_mean_ms` / `delivery_max_ms`: query embedding, retrieval, reranking and final rendering are all inside that clock. These columns are not comparable with the old reranker-only timings. diff --git a/docs/warm-start-contract.md b/docs/warm-start-contract.md new file mode 100644 index 0000000..561bc46 --- /dev/null +++ b/docs/warm-start-contract.md @@ -0,0 +1,11 @@ +# Warm-start delivery + +Warm starts validate current digest inputs before reusing cached text. Corrections, retirement, future starts and expiry apply to both digest facts and standing preferences. This adds a synchronous database read and, when needed, a rule-based rebuild. It prevents a stale cache from reintroducing facts excluded by ordinary retrieval; it makes no constant-time scaling claim. + +The shared repo digest contains facts and manifests. Task titles and progress appear only in the caller-selected task/session/worktree resume lane. Global preferences use the normal read-only user-brain opener, including project and environment opt-outs. + +Stdio MCP retains delivered warm-start keys for the latest 256 canonical workspace/identity pairs. Keys larger than 4096 combined path/identity units are not cached. An evicted or oversized key may receive a later repeat; all output remains subject to final serialization admission. This bounds process cache growth. The stdio request loop is sequential; the remote server uses its own session policy. + +MCP prepares warm text without recording delivery, then marks the lane and records warm attribution only if the final budgeted response contains that block. A block omitted for space can be retried with a larger budget. Warm content is not inferred to be verified, and textual warm material does not receive fabricated capsule-based outcome credit. + +BrainBenchmark's paired retrieval runs explicitly disable warm starts and ambient augmentation. Their query gold labels cover capsules, while warm-start validity, identity separation and omission/retry behavior are covered by integration regressions. Benchmark retrieval costs therefore exclude this separate opening-context service. From ece62f26b5b150ad6df621f6be4fd56ae87d3863 Mon Sep 17 00:00:00 2001 From: RodCor Date: Fri, 4 Sep 2026 23:53:44 -0300 Subject: [PATCH 19/34] bind warm digest delivery to current assembled inputs --- crates/kimetsu-brain/src/digest.rs | 30 ++++++++++++++++++++++-------- docs/warm-start-contract.md | 2 +- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/crates/kimetsu-brain/src/digest.rs b/crates/kimetsu-brain/src/digest.rs index 7a7962d..81ff1f3 100644 --- a/crates/kimetsu-brain/src/digest.rs +++ b/crates/kimetsu-brain/src/digest.rs @@ -88,19 +88,23 @@ fn build_or_load_digest_inner( let cache_path = paths.kimetsu_dir.join("digest.md"); let meta_path = paths.kimetsu_dir.join("digest-meta.json"); - // 4. Check cache validity. - if !force_rebuild { - if let Some(cached) = try_load_cache(&cache_path, &meta_path, hash) { - return Ok(Some(cached)); - } - } - - // 5. Build the digest (cheap-model optional; rule-based otherwise). + // The rule-based assembly is cheap and binds delivery to this exact input + // snapshot. Separate diagnostic cache publishers can mix text/metadata + // generations, so an input-hash match alone cannot authorize cached text. let digest_text = assemble_rule_based(&inputs, &config)?; if digest_text.trim().is_empty() { return Ok(None); } + // 4. Reuse the disk cache only as a reason to skip an unchanged write. + if !force_rebuild { + if let Some(cached) = try_load_cache(&cache_path, &meta_path, hash) { + if cached == digest_text { + return Ok(Some(digest_text)); + } + } + } + // 6. Write cache atomically. let meta = DigestMeta { input_hash: hash, @@ -591,6 +595,16 @@ mod tests { "stale cache must never reintroduce corrected text" ); assert!(block.contains("CORRECTED")); + // Separate cache-file publishers can leave old text with current + // input metadata. Delivery must bind to the gathered inputs anyway. + let (paths, _, conn) = load_project_readonly(&dir).unwrap(); + std::fs::write(paths.kimetsu_dir.join("digest.md"), "ORIGINAL port is 4001").unwrap(); + let mixed = warm_start_block_scoped(&dir, "lane-a").unwrap(); + assert!( + mixed.contains("CORRECTED") && !mixed.contains("ORIGINAL"), + "mixed cache generations leaked: {mixed}" + ); + drop(conn); project::invalidate_memory(&dir, &id, Some("wrong claim")).unwrap(); assert!( !warm_start_block_scoped(&dir, "lane-a") diff --git a/docs/warm-start-contract.md b/docs/warm-start-contract.md index 561bc46..f97e1d4 100644 --- a/docs/warm-start-contract.md +++ b/docs/warm-start-contract.md @@ -1,6 +1,6 @@ # Warm-start delivery -Warm starts validate current digest inputs before reusing cached text. Corrections, retirement, future starts and expiry apply to both digest facts and standing preferences. This adds a synchronous database read and, when needed, a rule-based rebuild. It prevents a stale cache from reintroducing facts excluded by ordinary retrieval; it makes no constant-time scaling claim. +Warm starts gather current inputs and assemble the bounded rule-based digest for delivery. Corrections, retirement, future starts and expiry apply to both digest facts and standing preferences. Matching cached text only avoids an unchanged disk write; it never replaces the freshly assembled result. This also prevents separate text/metadata cache publishers from mixing generations in model output. The synchronous database read makes no constant-time scaling claim. The shared repo digest contains facts and manifests. Task titles and progress appear only in the caller-selected task/session/worktree resume lane. Global preferences use the normal read-only user-brain opener, including project and environment opt-outs. From f99e1b636a5cb33214417a45542adf65b6388102 Mon Sep 17 00:00:00 2001 From: RodCor Date: Sat, 5 Sep 2026 00:06:32 -0300 Subject: [PATCH 20/34] reject failed requested embedders at measured serving boundaries --- crates/kimetsu-brain/src/embeddings.rs | 35 +++++++++ crates/kimetsu-chat/src/mcp_server.rs | 76 ++++++++++++++++++- crates/kimetsu-cli/src/commands/brain.rs | 4 +- crates/kimetsu-remote/tests/http_roundtrip.rs | 5 +- docs/local-inference.md | 2 +- 5 files changed, 116 insertions(+), 6 deletions(-) diff --git a/crates/kimetsu-brain/src/embeddings.rs b/crates/kimetsu-brain/src/embeddings.rs index 32f15fd..1941cab 100644 --- a/crates/kimetsu-brain/src/embeddings.rs +++ b/crates/kimetsu-brain/src/embeddings.rs @@ -507,6 +507,41 @@ pub fn open_embedder_for(config_enabled: bool) -> &'static dyn Embedder { } } +/// Serving/evaluation must distinguish an explicitly lexical configuration from +/// a requested model whose cached initialization fell back to Noop. +pub fn open_embedder_for_checked(config_enabled: bool) -> Result<&'static dyn Embedder, String> { + let embedder = open_embedder_for(config_enabled); + validate_requested_embedder( + embedder, + embedder_enabled_for_config(config_enabled), + cfg!(feature = "embeddings"), + )?; + Ok(embedder) +} + +fn validate_requested_embedder( + embedder: &dyn Embedder, + enabled: bool, + available: bool, +) -> Result<(), String> { + if available && enabled && embedder.is_noop() { + return Err("requested embedder unavailable after initialization; no semantic measurement (explicitly disable embeddings for lexical-only serving)".into()); + } + Ok(()) +} + +#[cfg(test)] +mod checked_serving_loader_tests { + use super::*; + #[test] + fn failed_requested_model_is_not_an_intentional_lexical_measurement() { + assert!(validate_requested_embedder(&NoopEmbedder, true, true).is_err()); + assert!(validate_requested_embedder(&NoopEmbedder, false, true).is_ok()); + assert!(validate_requested_embedder(&NoopEmbedder, true, false).is_ok()); + assert!(validate_requested_embedder(&StubEmbedder::default(), true, true).is_ok()); + } +} + /// v0.8: open a FRESH (uncached) embedder for an explicit built-in /// model id. Unlike [`open_default_embedder`], this bypasses the /// process-static cache AND the env/override resolution — the caller diff --git a/crates/kimetsu-chat/src/mcp_server.rs b/crates/kimetsu-chat/src/mcp_server.rs index ccdf3db..e2bf13a 100644 --- a/crates/kimetsu-chat/src/mcp_server.rs +++ b/crates/kimetsu-chat/src/mcp_server.rs @@ -711,6 +711,23 @@ fn stdio_brain_context_with_loader( &str, ) -> Result>, String>, +) -> Value { + stdio_brain_context_with_model_loaders( + workspace, + arguments, + load, + kimetsu_brain::embeddings::open_embedder_for_checked, + ) +} + +fn stdio_brain_context_with_model_loaders( + workspace: &Path, + arguments: &Value, + load: impl FnOnce( + &str, + ) + -> Result>, String>, + load_embedder: impl FnOnce(bool) -> Result<&'static dyn kimetsu_brain::embeddings::Embedder, String>, ) -> Value { let result = (|| -> Result { let paths = @@ -722,11 +739,12 @@ fn stdio_brain_context_with_loader( load(&config.embedder.reranker)? }; let warm = take_session_warm_start(workspace, arguments); - let output = brain_context_tool_with_warm( + let output = brain_context_tool_with_embedder_loader( workspace, arguments, reranker.as_deref(), warm.as_ref().map(|(_, block)| block.context.clone()), + load_embedder, )?; if let Some((key, block)) = warm { if output["warm_start"]["context"].as_str() == Some(block.context.as_str()) { @@ -813,6 +831,22 @@ fn brain_context_tool_with_warm( arguments: &Value, reranker: Option<&dyn kimetsu_brain::embeddings::Reranker>, warm_start: Option, +) -> Result { + brain_context_tool_with_embedder_loader( + workspace, + arguments, + reranker, + warm_start, + kimetsu_brain::embeddings::open_embedder_for_checked, + ) +} + +fn brain_context_tool_with_embedder_loader( + workspace: &Path, + arguments: &Value, + reranker: Option<&dyn kimetsu_brain::embeddings::Reranker>, + warm_start: Option, + load_embedder: impl FnOnce(bool) -> Result<&'static dyn kimetsu_brain::embeddings::Embedder, String>, ) -> Result { use kimetsu_brain::context::ContextRequest; @@ -915,7 +949,7 @@ fn brain_context_tool_with_warm( .retrieve( &session, request, - kimetsu_brain::embeddings::open_embedder_for(session.config().embedder.enabled), + load_embedder(session.config().embedder.enabled)?, reranker, &exposure.event_id.to_string(), ) @@ -2775,6 +2809,44 @@ mod tests { }); } + #[test] + fn hardening_failed_embedder_load_is_bounded_and_has_no_success_exposure() { + kimetsu_brain::user_brain::with_user_brain_disabled(|| { + let root = temp_root("hardening-mcp-embedder-failure"); + project::init_project(&root, false).unwrap(); + let result = stdio_brain_context_with_model_loaders( + &root, + &json!({"query":"unrelated","include_ambient":false,"budget_tokens":1200}), + |_| Ok(None), + |_| Err("requested embedder unavailable".into()), + ); + assert!( + result["error"] + .as_str() + .unwrap_or("") + .contains("requested embedder unavailable"), + "{result}" + ); + assert_ne!(result["ok"], true); + assert_eq!( + result["used_tokens"].as_u64(), + Some(kimetsu_brain::context::delivery::serialized_output_tokens(&result) as u64) + ); + assert!(result["used_tokens"].as_u64().unwrap() <= 1200); + let (_, _, conn) = project::load_project_readonly(&root).unwrap(); + let count: i64 = conn + .query_row( + "SELECT COUNT(*) FROM events WHERE kind='context.injected'", + [], + |r| r.get(0), + ) + .unwrap(); + assert_eq!(count, 0); + drop(conn); + std::fs::remove_dir_all(root).unwrap(); + }); + } + #[test] fn hardening_served_ids_and_revisions_match_final_mcp_payload() { kimetsu_brain::user_brain::with_user_brain_disabled(|| { diff --git a/crates/kimetsu-cli/src/commands/brain.rs b/crates/kimetsu-cli/src/commands/brain.rs index 01758b9..2e4bcf0 100644 --- a/crates/kimetsu-cli/src/commands/brain.rs +++ b/crates/kimetsu-cli/src/commands/brain.rs @@ -2693,7 +2693,7 @@ pub(crate) fn brain_tune_sweep( ) -> KimetsuResult<()> { use kimetsu_brain::{ context::ContextRequest, - embeddings::{open_embedder_for, open_reranker_checked}, + embeddings::{open_embedder_for_checked, open_reranker_checked}, eval::{EvaluationMetrics, summarize_deliveries}, project::BrainSession, serving::{EVAL_EXPOSURE_ID, ServingPolicy}, @@ -2708,7 +2708,7 @@ pub(crate) fn brain_tune_sweep( return Err("cost_weight must be finite and nonnegative".into()); } let config = project::load_config(paths)?; - let embedder = open_embedder_for(config.embedder.enabled); + let embedder = open_embedder_for_checked(config.embedder.enabled)?; let policy = ServingPolicy::default(); let current_combo = TuneCombo { min_lexical_coverage: config.broker.min_lexical_coverage, diff --git a/crates/kimetsu-remote/tests/http_roundtrip.rs b/crates/kimetsu-remote/tests/http_roundtrip.rs index 317c082..9d624b4 100644 --- a/crates/kimetsu-remote/tests/http_roundtrip.rs +++ b/crates/kimetsu-remote/tests/http_roundtrip.rs @@ -193,7 +193,10 @@ async fn hardening_remote_reranker_empty_reply_obeys_final_budget() { request["params"]["arguments"]["include_ambient"] = json!(false); let response = send_with_reranker(tmp.path(), "empty-budget", request).await; let payload = inner(&response); - assert_eq!(payload["ok"], true); + // The canonical exposure envelope no longer fits in 250 bytes. The compact + // error must still be truthfully accounted, rather than claiming success. + assert_eq!(payload["ok"], false); + assert_eq!(payload["error"], "budget_too_small"); assert_eq!(payload["capsule_count"], 0); let actual = response["result"].to_string().len() as u64; assert_eq!( diff --git a/docs/local-inference.md b/docs/local-inference.md index a5172e9..0f4ab26 100644 --- a/docs/local-inference.md +++ b/docs/local-inference.md @@ -2,7 +2,7 @@ `KIMETSU_INTRA_THREADS=4` opts the process into a shared ONNX Runtime pool with four intra-operation threads, one inter-operation thread and idle spinning disabled. Set it before the first embedding or reranking model loads. Valid values are integers from 1 to 1024. Unset preserves the existing backend default; changing the environment after first load requires a new process. This controls local embedding/reranking inference, not host model generation. -The configured pool is shared by both models, including user-defined ONNX rerankers. Startup reports the applied setting to stderr. If another embedding application already configured ONNX Runtime, initialization reports that this requested setting cannot take effect rather than claiming success. Existing model-loader fallback behavior still applies, so inspect stderr when diagnosing a disabled semantic path. +The configured pool is shared by both models, including user-defined ONNX rerankers. Startup reports the applied setting to stderr. If another embedding application already configured ONNX Runtime, initialization reports that this requested setting cannot take effect rather than claiming success. Low-level loaders retain lexical fallback, but MCP serving and tuning reject a failed requested embedding model; explicit off and lean builds remain lexical. Inspect stderr for the original cached initialization error. FastEmbed 5.13.4 sets per-session threads to available logical CPUs. The pinned ort 2.0.0-rc.12 session builder disables per-session pools when the environment provides a global pool, making this control effective without vendoring FastEmbed. The direct ort dependency intentionally matches FastEmbed's runtime instance. From b9658995609e945d18ee87163d543494c1c44713 Mon Sep 17 00:00:00 2001 From: RodCor Date: Sat, 5 Sep 2026 00:23:34 -0300 Subject: [PATCH 21/34] fix: index remote repository files under owning brain --- crates/kimetsu-brain/src/ingest.rs | 36 ++++++++++++++------ crates/kimetsu-brain/src/project.rs | 10 +++--- crates/kimetsu-remote/tests/server_ingest.rs | 6 +++- 3 files changed, 35 insertions(+), 17 deletions(-) diff --git a/crates/kimetsu-brain/src/ingest.rs b/crates/kimetsu-brain/src/ingest.rs index 2bface7..076377a 100644 --- a/crates/kimetsu-brain/src/ingest.rs +++ b/crates/kimetsu-brain/src/ingest.rs @@ -46,7 +46,18 @@ pub fn ingest_repo( paths: &ProjectPaths, config: &ProjectConfig, ) -> KimetsuResult { - let repo_root = paths.repo_root.canonicalize()?; + ingest_repo_from_root(conn, paths, config, &paths.repo_root) +} + +/// File traversal can live in a managed checkout, while indexed rows remain +/// scoped to the owning brain root used by every retrieval consumer. +pub(crate) fn ingest_repo_from_root( + conn: &Connection, + paths: &ProjectPaths, + config: &ProjectConfig, + files_root: &Path, +) -> KimetsuResult { + let repo_root = files_root.canonicalize()?; let skip_dirs = skip_dirs(config); let (max_file_bytes, max_total_files) = effective_ingest_limits(config); let mut builder = WalkBuilder::new(&repo_root); @@ -94,22 +105,27 @@ pub fn ingest_repo( } let tx = conn.unchecked_transaction()?; - let repo_root_text = repo_root.to_string_lossy().to_string(); + let repo_root_text = paths + .repo_root + .canonicalize()? + .to_string_lossy() + .to_string(); + let old_checkout_key = repo_root.to_string_lossy().to_string(); tx.execute( - "DELETE FROM repo_files WHERE repo_root = ?1", - params![repo_root_text], + "DELETE FROM repo_files WHERE repo_root = ?1 OR repo_root = ?2", + params![repo_root_text, old_checkout_key], )?; tx.execute( - "DELETE FROM repo_files_fts WHERE repo_root = ?1", - params![repo_root_text], + "DELETE FROM repo_files_fts WHERE repo_root = ?1 OR repo_root = ?2", + params![repo_root_text, old_checkout_key], )?; tx.execute( - "DELETE FROM repo_manifests WHERE repo_root = ?1", - params![repo_root_text], + "DELETE FROM repo_manifests WHERE repo_root = ?1 OR repo_root = ?2", + params![repo_root_text, old_checkout_key], )?; tx.execute( - "DELETE FROM repo_manifests_fts WHERE repo_root = ?1", - params![repo_root_text], + "DELETE FROM repo_manifests_fts WHERE repo_root = ?1 OR repo_root = ?2", + params![repo_root_text, old_checkout_key], )?; let mut manifests = 0usize; diff --git a/crates/kimetsu-brain/src/project.rs b/crates/kimetsu-brain/src/project.rs index 6609f25..f04217b 100644 --- a/crates/kimetsu-brain/src/project.rs +++ b/crates/kimetsu-brain/src/project.rs @@ -1499,16 +1499,14 @@ pub fn ingest_repo_at_root( brain_root: &Path, files_root: &Path, ) -> KimetsuResult { - let (mut paths, config, conn) = load_project_at_root(brain_root)?; - // Walk the checkout, but keep the brain/lock under brain_root. - paths.repo_root = files_root - .canonicalize() - .unwrap_or_else(|_| files_root.to_path_buf()); + let (paths, config, conn) = load_project_at_root(brain_root)?; + // Keep the storage identity/lock at the brain root; traverse the checkout + // separately so retrieval uses the same key as file and manifest indexing. let run_id = RunId::new(); let _lock = ProjectLock::acquire(&paths, "brain ingest-repo (remote)", Some(run_id))?; let started = admin_started_event(&paths, &config, run_id, "repo ingest")?; - let summary = ingest::ingest_repo(&conn, &paths, &config)?; + let summary = ingest::ingest_repo_from_root(&conn, &paths, &config, files_root)?; let ingested = Event::new( run_id, "repo.ingested", diff --git a/crates/kimetsu-remote/tests/server_ingest.rs b/crates/kimetsu-remote/tests/server_ingest.rs index 3cbc7e8..dad005b 100644 --- a/crates/kimetsu-remote/tests/server_ingest.rs +++ b/crates/kimetsu-remote/tests/server_ingest.rs @@ -142,7 +142,11 @@ async fn registered_repo_ingests_and_files_are_retrievable() { .await, ); assert!( - ctx.to_string().contains("snorblax"), + ctx["capsules"].as_array().is_some_and(|capsules| capsules + .iter() + .any(|c| c["kind"] == "repo_file" + && c["expansion_handle"] == "file:README.md" + && c["summary"].as_str().unwrap_or("").contains("snorblax"))), "context did not surface the ingested file: {ctx}" ); From 7ac83ae31fb1736e7f473b1525de76c92d22dba2 Mon Sep 17 00:00:00 2001 From: RodCor Date: Sat, 5 Sep 2026 01:55:57 -0300 Subject: [PATCH 22/34] docs: record hardening results and paired memory benchmarks --- docs/audits/2026-09-04-agent-memory-audit.md | 184 ++ docs/audits/2026-09-04-hardening-results.md | 138 ++ .../2026-09-04-models-parameters-memory.md | 227 +++ .../2026-09-05-brainbench/.gitattributes | 3 + docs/audits/2026-09-05-brainbench/README.md | 39 + .../agent-memory-contract.json | 262 +++ .../benchmark_idf_sql.py | 36 + .../2026-09-05-brainbench/contract-2048.json | 228 +++ .../2026-09-05-brainbench/contract-512.json | 228 +++ .../development-100.json | 1678 +++++++++++++++++ .../development-6000.json | 180 ++ .../idf-sql-comparison.json | 137 ++ .../2026-09-05-brainbench/run-comparisons.ps1 | 48 + .../threads-default-vs-4.json | 180 ++ .../tinybert-vs-minilm-4threads.json | 180 ++ 15 files changed, 3748 insertions(+) create mode 100644 docs/audits/2026-09-04-agent-memory-audit.md create mode 100644 docs/audits/2026-09-04-hardening-results.md create mode 100644 docs/audits/2026-09-04-models-parameters-memory.md create mode 100644 docs/audits/2026-09-05-brainbench/.gitattributes create mode 100644 docs/audits/2026-09-05-brainbench/README.md create mode 100644 docs/audits/2026-09-05-brainbench/agent-memory-contract.json create mode 100644 docs/audits/2026-09-05-brainbench/benchmark_idf_sql.py create mode 100644 docs/audits/2026-09-05-brainbench/contract-2048.json create mode 100644 docs/audits/2026-09-05-brainbench/contract-512.json create mode 100644 docs/audits/2026-09-05-brainbench/development-100.json create mode 100644 docs/audits/2026-09-05-brainbench/development-6000.json create mode 100644 docs/audits/2026-09-05-brainbench/idf-sql-comparison.json create mode 100644 docs/audits/2026-09-05-brainbench/run-comparisons.ps1 create mode 100644 docs/audits/2026-09-05-brainbench/threads-default-vs-4.json create mode 100644 docs/audits/2026-09-05-brainbench/tinybert-vs-minilm-4threads.json diff --git a/docs/audits/2026-09-04-agent-memory-audit.md b/docs/audits/2026-09-04-agent-memory-audit.md new file mode 100644 index 0000000..a921ff7 --- /dev/null +++ b/docs/audits/2026-09-04-agent-memory-audit.md @@ -0,0 +1,184 @@ +# Kimetsu agent-memory audit — 2026-09-04 + +Audited `E:/Kimetsu`, commit `3ec56a8`, version 2.7.0. Scope: local brain, retrieval, MCP/CLI/hook delivery, feedback, temporal correctness, persistence, benchmark methodology, and selected remote/ANN paths. Sibling repositories were not audited. This is an audit and proposed roadmap; production code was not changed. + +**Verdict:** Kimetsu has a strong foundation, but it is not yet a reliably bounded, self-correcting memory for an agent. The biggest gaps are at the interfaces between existing features: rejected memories still reach the model, edits do not survive replay, time validity is incomplete, and citations are treated as successful outcomes. Fix those before adding more retrieval models or graph machinery. + +## What “0 tokens” can actually mean + +| Target | Feasibility | +|---|---| +| Zero paid model calls for storage, indexing, retrieval, and maintenance | Achievable with local deterministic processing and local embeddings/reranking. | +| Zero generative LLM tokens anywhere in memory processing | Achievable only when distillation, local `ask`, HyDE, and host-agent harvesting are disabled or replaced with deterministic capture. Local generation still uses tokens, even without an API bill. | +| Zero memory tokens added on irrelevant turns | Achievable through silent host-side abstention, before invoking the model. | +| Arbitrary new memories influence a hosted model while consuming no input tokens | Not achievable through ordinary MCP or text injection. The model must receive the information. | +| Lower total agent tokens with memory enabled | Achievable in principle; requires paired measurements of task success and total consumption. | + +Prompt caching reuses prompt computation; it is not token-free semantic memory. OpenAI documents cached-input accounting and cost measurement in its [prompt caching guide](https://developers.openai.com/api/docs/guides/prompt-caching). + +The useful product promise is: **“No paid inference for memory operations; silent when unnecessary; bounded context when useful; measured reduction in total agent cost.”** Do not call embedding and cross-encoder inference “model-free”; “no generative LLM calls” is accurate. + +## Existing strengths worth preserving + +- Rust, SQLite/FTS5, optional local embeddings, HNSW, and a warm daemon are a sensible local architecture. +- The system already has provenance weights, remote-pack quarantine, redaction, supersession, decay, negative feedback, retrieval abstention, chronological rendering, episodes, and injection-policy training. These are not missing features. +- Expandable capsules, per-session suppression, and compression provide the basis for compact context delivery. +- Replay, migrations, host smoke tests, and synthetic memory evaluations give substantial regression coverage. +- The README discloses several benchmark comparability limits. Keep that transparency and make consumption equally visible. + +## Prioritized findings + +### 1. P0 — MCP bypasses its own context budget + +**Confirmed in current source and reproduced against the freshly built lean binary.** + +`kimetsu_brain_context` serializes both selected `capsules` and complete `excluded` capsules. Excluded entries include their summaries. The agent therefore receives memories rejected by the budget/ranking pipeline. Diagnostics are mixed into the model-facing response, along with verbose guidance, metadata, and repeated query information. + +An isolated 26-memory fixture requested 100 tokens, which the endpoint clamped to 500. It reported **186 used tokens, three selected capsules, and 23 excluded capsules**, but emitted **37,868 characters on the JSON-RPC wire**, including **11,860 characters of excluded summaries alone**. Wire characters are not tokenizer counts, and some bytes are transport encoding; the excluded text is nevertheless demonstrably in the tool payload. The preexisting release binary reproduced the same result. + +**Change:** Default to a compact response containing selected evidence, short stable handles, necessary validity/provenance, and expansion access. Return excluded counts only. Move scoring details and excluded text into a separate opt-in inspection surface. Enforce the budget on the final model-visible representation, including framing and warm-start content. + +**Acceptance:** Growing the rejected candidate pool must not grow the normal tool response. A tokenizer-based contract test must cover the entire returned content. + +Evidence: [mcp_server.rs](../../crates/kimetsu-chat/src/mcp_server.rs#L858), especially line 896; the benchmark-context response also serializes excluded entries near line 1168. + +### 2. P0 — Corrected memories revert after rebuilding + +**Confirmed and reproduced.** `edit_memory` directly updates SQLite text, kind, FTS, and embeddings without writing a corresponding durable correction event. Replaying the original event log restores the original memory. + +Probe: add “The audit service uses sqlite checkpoint before deployment migration.” → edit to “Corrected audit lesson: use verified recovery procedure.” → `brain rebuild` → original text returns. + +This breaks the central promise that the brain learns corrections. The edit also preserves confidence/usefulness even when the proposition changes, and the sequence of database updates is not one atomic correction transaction. + +**Change:** Make corrections immutable, revisioned events; project text, FTS, validity, and embedding invalidation atomically. Preserve lineage while distinguishing evidence for the old claim from evidence for the new claim. Ensure sync exports/imports correction events. + +**Acceptance:** Edit → restart → rebuild → sync round trip preserves the correction. Interruption cannot leave text and FTS disagreeing. Historical queries return the appropriate revision. + +Evidence: [project.rs](../../crates/kimetsu-brain/src/project.rs#L2026); existing edit tests near line 5572 check immediate results, not replay. + +### 3. P1 — Hidden full scans undermine retrieval scaling + +**Confirmed SQL shape; measured isolated SQL workload.** Both IDF functions issue `COUNT(*) ... lower(text) LIKE '%term%'` once per query term. The query plan is `SCAN memories`. A normal retrieval can invoke both lexical-floor and evidence-coverage computations. HNSW does not remove these costs. + +Eight query terms, synthetic roughly 200-character rows, Python SQLite in memory, five samples per scale: + +| Memories | Median time for one eight-term IDF pass | +|---:|---:| +| 1,000 | 4.03 ms | +| 10,000 | 41.26 ms | +| 100,000 | 437.72 ms | + +These are not full Rust broker timings, semantic-model timings, or million-memory results. They demonstrate linear work in a helper on the retrieval path. Another growth risk: query routing loads every qualifying route and computes similarity in Rust; it is not a bounded nearest-neighbor lookup despite the “one indexed SQL read” description. + +**Change:** Maintain token document frequencies incrementally, or use an appropriate FTS vocabulary with consistent token semantics; share/cache them by corpus revision. Bound and index route retrieval. Avoid replacing substring semantics with FTS semantics without evaluation. + +**Acceptance:** Profile the complete request, including SQL, candidate hydration, routing, reranking, serialization, and hooks. Report cold/warm p50/p95/p99, resident memory, and contention at 1K/10K/100K/1M memories. + +Evidence: [context.rs](../../crates/kimetsu-brain/src/context.rs#L444), line 2414; [reinforce.rs](../../crates/kimetsu-brain/src/reinforce.rs#L315). + +### 4. P1 — Current-time retrieval can return facts that are not currently valid + +**Future-date failure reproduced; timestamp comparison reproduced independently.** Candidate SQL filters `valid_to` but omits `valid_from`. A memory with `valid_from=2099-01-01T00:00:00Z` was returned among selected capsules today. + +Expiration also compares RFC3339 strings against SQLite `datetime('now')`, whose separator is a space. Lexicographically, `2026-09-04T01:00:00Z > 2026-09-04 12:00:00` is true although the fact expired eleven hours earlier. Offset variations add further problems unless timestamps are normalized. + +The “bitemporal” view also uses a single timestamp for both axes and does not select historical text revisions. Direct edits are consequently incompatible with faithful historical beliefs. + +**Change:** Use normalized numeric times and a shared predicate: `valid_from <= now < valid_to`, with explicit null semantics. Separate “valid at” from “known at” and retain revision history. Apply the same rules to FTS, ANN hydration, graph expansion, and historical views. + +**Acceptance:** Cover future facts, same-day expiration, timezone offsets, late-arriving corrections, supersession, and replay with a controllable clock. + +Evidence: [context.rs](../../crates/kimetsu-brain/src/context.rs#L1242), lines 1416 and 1502; [bitemporal.rs](../../crates/kimetsu-brain/src/bitemporal.rs#L59). + +### 5. P1 — Free-tier gating does not prevent host-agent token spending + +**Confirmed control-flow gap; no paid run was triggered during this audit.** `resolve_pipeline_distiller` returns `None` on Free. The stop hook interprets absence of a distiller as a reason to issue a blocking harvest cue when `auto_harvest` is enabled; that setting defaults to true. The cue requests a memory-harvester agent, re-entering host inference. Proactive hooks can also issue harvest cues. + +**Change:** Make the zero-generative-token policy apply across host callbacks, harvesting, HyDE, reflection, and background jobs. In strict mode, collect structured tool outcomes deterministically and enqueue evidence locally. Distinguish strict/no-generation, local-generation, and paid-generation capabilities explicitly. + +**Acceptance:** Replay a substantial session under strict mode and assert no generation provider, harvest agent, or stop continuation was invoked. No relevant memory means no injected text. + +Evidence: [distiller.rs](../../crates/kimetsu-cli/src/distiller.rs#L622); [hooks.rs](../../crates/kimetsu-cli/src/commands/hooks.rs#L503), lines 533, 642, 931; [config.rs](../../crates/kimetsu-core/src/config.rs#L476). + +### 6. P1 — Citations conflate attention, usefulness, and verification + +**Confirmed control flow.** An MCP citation becomes a standalone citation with a fresh group ID and no query. The projector immediately applies a positive outcome and stamps useful activity. Trust treats `last_useful_at` as corroboration, removing origin penalties. This requires no independent successful test or final task outcome. + +The benchmark helper further credits memories retrieved *after* a task passes, rather than requiring the exact IDs actually supplied to the agent. That can reward an unexposed memory. Repeated singleton MCP citations also fail to provide the grouped/query-linked signal needed by co-citation reinforcement. + +**Change:** Record exposure, cited reliance, verified outcome, contradiction, and causal benefit separately. Bind feedback to session, task, query, memory revision, and evidence. Credit actual delivered IDs. One citation should not erase an origin penalty. Preserve negative evidence without treating every unused memory as harmful. + +**Acceptance:** Citations without verification do not become “verified here”; an unexposed memory cannot receive task credit; failed tasks cannot receive successful-outcome reinforcement. + +Evidence: [feedback.rs](../../crates/kimetsu-brain/src/feedback.rs#L175); [projector.rs](../../crates/kimetsu-brain/src/projector.rs#L365); [trust.rs](../../crates/kimetsu-brain/src/trust.rs#L148); [reinforce.rs](../../crates/kimetsu-brain/src/reinforce.rs#L73). + +### 7. P1 — Token estimates and savings estimates are not enforceable measurements + +`estimate_tokens` is `ceil(whitespace_words * 1.33)`. A long identifier, minified JSON, or unspaced CJK passage can count as roughly two tokens. Budgeting happens before some rendering changes, and MCP output contains additional material not counted by `used_tokens`. + +The ROI ledger assigns fixed savings per citation/digest/resume and estimates output tokens using a 0.25 ratio. Those are assumptions about avoided work, not measured causal lower bounds. A cited memory can save nothing or cause extra work. Calling a constant conservative does not prove that it underestimates savings. + +**Change:** Use the host tokenizer where available and a conservative byte-based bound otherwise. Measure final output. Track real input/output/cached usage when exposed; label missing usage unknown. Keep counterfactual savings estimates separate from observed consumption. + +Evidence: [context.rs](../../crates/kimetsu-brain/src/context.rs#L2853); [roi.rs](../../crates/kimetsu-brain/src/roi.rs#L31), constants starting at line 58. + +### 8. P1/P2 — Cross-process freshness needs a revision protocol + +**Source-backed risk, not an embeddings-runtime reproduction.** ANN freshness checks compare maximum row ID. Re-embedding an existing memory in another process does not increase that watermark. The local process updates a cached ANN handle if it has one; that does not notify an already warm daemon elsewhere. Reconciliation adds rows above the watermark and removes retired rows, but does not refresh changed vectors at existing row IDs. + +**Change:** Publish a monotonic corpus/change sequence for insert, edit, re-embed, invalidate, supersede, and rebuild. Let all indexes consume deltas, and bind cached results to the revision. Include embedding-model identity. + +**Acceptance:** Keep reader/daemon A warm while writer B edits or re-embeds a memory. A must retrieve the new meaning without restart; old vectors must not occupy the useful top-K pool indefinitely. + +Evidence: [ann.rs](../../crates/kimetsu-brain/src/ann.rs#L327), line 609; [embeddings.rs](../../crates/kimetsu-brain/src/embeddings.rs#L1004). + +### 9. P2 — Agent continuity is scoped too coarsely + +Episodes hold useful task/open-thread/dead-end fields, but there is one live episode per repo root. Independent tasks in the same checkout can replace one another's resume state. The payload does not include a task/session/branch/commit identity for that state. + +For an agent like this one, reliable memory should distinguish: user preference, durable project rule, verified code fact, and current task state. Each needs different expiration and authority. Code claims need paths/symbols plus a revision or content fingerprint; task state needs branch/worktree/session keys and source evidence. Existing provenance and ambient context are a starting point, not substitutes for applicability checks. + +Evidence: [episode.rs](../../crates/kimetsu-brain/src/episode.rs#L39), projection near line 117. + +## Codex integration observations + +This session started at `E:/`, outside the Kimetsu project directory, and exposed no Kimetsu MCP tools in the available tool catalog. Therefore Kimetsu was not automatically accessible to me through MCP in this session. That does not establish that the installed hooks are broken; their execution was not observed. + +The checked-in hooks use `--workspace .`, so correct workspace resolution and actual host activation need an end-to-end probe. The checked-in prompt hook also lacks `--warm-on-first-prompt`, which the current installer generates. Presence of configuration files is insufficient evidence that the running host loaded them. + +The `Bash` matcher itself is **not a bug**: current [official Codex hook documentation](https://learn.chatgpt.com/docs/hooks) explicitly maps `exec_command` to `Bash`. Add a doctor check that proves a hook ran and that a seeded nonce reached model-visible context, with the resolved repository and selected retrieval path recorded. Avoid automatic setup changes during a read-only audit. + +## What would justify “best memory” + +Existing QA scores do not establish the best agent memory under a small token budget. LongMemEval's harness configures a 48,000-token retrieval budget, yielding roughly 24,000 capsule-budget tokens, and uses a generative reader/judge. That is a different operating point from a 100–500-token coding-agent hint. The stored BrainBench report's 91.3% aggregate includes 122 calibration scenarios but only five retrieval scenarios, which score 56.6%; it is a historical artifact, not a rerun of this commit. + +Build one held-out evaluation matrix: + +- Same agent/model/tools/task budget, paired no-memory and memory runs; multiple seeds and confidence intervals. +- Cold, repeated, and related-but-new tasks; split by repository/task family/time to prevent training leakage. +- Wrong memories, changed branches, same-day expiry, corrections, malicious imported instructions, empty memory, and multilingual/code-heavy text. +- Retrieval at final-context budgets 0/128/256/512/1024 tokens, plus a larger diagnostic ceiling. +- Primary metrics: verified task success, total billed tokens/cost, time to completion, repeated-error rate, harmful/irrelevant injection rate, and recovery after interruption. Citations remain a secondary signal. + +Evidence: [longmemeval.rs](../../bench/src/drivers/longmemeval.rs#L737); [stored BrainBench report](../../bench/brainbench-full.md). + +## Recommended implementation order + +1. **Restore trust in the contract:** compact MCP delivery, final-output budgeting, event-sourced corrections, correct temporal predicates, strict generation gating. +2. **Make runtime work bounded:** indexed/cached IDF, bounded route search, revision-driven ANN/cache refresh, stage latency metrics and deadlines. +3. **Improve what gets remembered:** passive structured capture of command failures/fixes and verified outcomes; distinct task/user/project memory; revision-aware applicability checks; explicit evidence grades. +4. **Prove benefit:** paired task evaluations and real usage accounting, with a held-out small-context track. Tune retrieval only against those results. + +Suggested initial engineering targets—not measured achievements—are warm local retrieval p95 below 100 ms at 10K memories and below 250 ms at 100K; zero extra model context on irrelevant turns; 128–512 tokens for routine useful hints; and no task-success regression against the paired baseline. Measure larger corpora separately before promising million-memory interactive performance. Specify hardware and resident-memory budgets for each supported mode. + +The existing architecture can support this direction. Replacing SQLite or introducing another generative layer is not currently justified by the evidence. + +## Verification and limits + +- `cargo test -p kimetsu-brain -p kimetsu-e2e --locked --offline`: 646 passed, three ignored, zero failures including doctests. +- `cargo test -p kimetsu-chat -p kimetsu-cli --locked --offline`: 414 passed, one ignored, zero failures. +- Total: **1,060 passed; four ignored; zero failures**. These are lean/default-feature runs, not the embeddings/remote/full-workspace matrix. +- [Reproduction script](../../tmp-tests/audit_probe_20260904.py) uses isolated temporary projects with user brain disabled. Final run uses the current-source debug binary produced by the host test build. +- [Probe output](../../tmp-tests/audit-probes-20260904.json) records MCP payload growth, edit/rebuild regression, future-validity leakage, timestamp comparison, and isolated SQL scaling. It includes synthetic payload text only. +- Test logs: [brain/e2e](../../tmp-tests/audit-tests-20260904.log), [chat/CLI](../../tmp-tests/audit-host-tests-20260904.log). +- No paid model calls, full public QA benchmark reruns, million-memory end-to-end measurements, live Codex-hook activation test, or cross-process embeddings test were performed. No global host configuration or existing brain was deliberately modified. diff --git a/docs/audits/2026-09-04-hardening-results.md b/docs/audits/2026-09-04-hardening-results.md new file mode 100644 index 0000000..961d3a1 --- /dev/null +++ b/docs/audits/2026-09-04-hardening-results.md @@ -0,0 +1,138 @@ +# Kimetsu hardening and BrainBenchmark comparison + +Implementation branches: `codex/brain-hardening` (Kimetsu) and `codex/brain-benchmark-hardening` (the separate benchmark repository). The original `E:/Kimetsu` source and live project/user brains were not changed. The preserved pre-change binary is from `3ec56a8`; all comparisons use the same updated harness on both binaries. + +This work addresses the concrete defects in the [memory audit](2026-09-04-agent-memory-audit.md) and [model/parameter audit](2026-09-04-models-parameters-memory.md). Local retrieval and rule-based memory processing require no paid generation calls. Returned memory still consumes the receiving agent's context; zero-token information transfer is not an achievable delivery contract. + +## What changed + +| Area | Result | +|---|---| +| Corrections and history | Durable correction revisions, atomic projection, embedding compare-and-swap, causal exposure bindings, and rebuild-safe corrected text. Historical import/sync stages events before replaying merged history; failed replay rolls back. | +| Freshness and applicability | Monotonic corpus revisions invalidate warm ANN state after existing-row changes. Numeric validity predicates exclude future, expired and malformed dates through retrieval, graph, digest and preference paths. | +| Ranking and scaling | Usefulness/trust adjustments are bounded; freshness uses the configured half-life. Indexed FTS document-frequency lookups replace per-term text scans. Route lookup and duplicate comparison work are bounded. | +| Lifecycle | Similarity proposes conflicts rather than automatically retiring claims. Consolidation preserves scope, kind and unique text. Temporary useful facts receive validity bounds. Archival is reversible; manual rejection and supersession cannot be bypassed by restore. Restore events replicate. | +| Agent context | Final compact MCP output contains admitted capsules and expansion handles, without rejected summaries. The complete serialized MCP content is accounted for, including escaping. Strict Free hooks do not cue generation. | +| Evidence and continuity | Reliance, outcome association and verification are distinct. Feedback targets the exact delivered claim revision. Task/session/worktree resume lanes are separate; warm state is marked only after actual admission, with bounded process bookkeeping. | +| Warm caches | Delivery assembles current rule-based inputs; disk text is never substituted solely because metadata matches. Global profile opt-outs are honored. Shared repo digests do not mix task titles across lanes. | +| Repository files | Remote ingestion traverses the managed checkout but indexes files and manifests under the owning brain, matching retrieval. Replacement is transactional; the regression checks a retrieved file capsule rather than query text echoed in a response. | +| Models and evaluation | Shared serving policy, configured stdio reranker caching, explicit threshold overrides, canonical model aliases, temporal fixture seeding, final delivered-cost measurement and honest failure/latency labels. | +| Tuning | Real fractional recall differs from hit rate; known negatives differ from unknown outcomes. Stable connected fact/task-family splits reduce leakage. Fixture fallback uses isolated seeded IDs. Insufficient labels cannot authorize automatic application. | +| BrainBenchmark | Persistent production MCP, cross-process write tests, stale-injection checks, separate positive/negative denominators, equal dimension weighting, payload bytes, latency and Windows process working set. Paired runs alternate order, fingerprint inputs, validate isolation, retain failures and clean timed-out descendants. | + +Details: [evaluation and objective](../canonical-evaluation.md), [warm-start contract](../warm-start-contract.md), [evidence/continuity](2026-09-04-evidence-continuity-hardening.md), [maintenance](../memory-maintenance.md), [local inference controls](../local-inference.md). + +## Measurements + +Measurements ran on September 5, 2026, on the Ryzen 7 3800X (8 cores/16 logical processors, 32 GiB RAM), with local cached models, one benchmark job and no concurrent compilation or agent inference. This is a desktop workload measurement, not an isolated laboratory host. Each pair uses three alternating-order repeats and fresh temporary brains; warm starts, ambient augmentation, user brain and conflict mutation are disabled. BGE-small-en-v1.5 is pinned throughout. Before/after runs retain new-project defaults: the baseline does not apply configured stdio reranking, whereas the candidate honors TinyBERT. Runtime/model contrasts explicitly select the custom retrieval level and the indicated reranker on both sides. + +### Before and after + +The 210-query development set contains 197 positives and 13 negatives. Rates below average repeats within each query; there are 630 observations per side, not 630 independent questions. + +| Metric, requested budget 6,000 | Baseline | Candidate | +|---|---:|---:| +| Positive hit@4 | 75.63% | 70.56% | +| Positive fractional recall@4 | 73.69% | 69.63% | +| Positive MRR | 0.7386 | 0.6980 | +| Negative injection | 11/13 (84.62%) | 7/13 (53.85%) | +| Subsequent query p50 / p95 | 866 / 982 ms | 1,016 / 1,154 ms | +| Mean serialized MCP result | 153,629 bytes | 1,262 bytes | +| Largest observed MCP peak working set | 210.1 MiB | 280.9 MiB | + +The response shrank **99.18%**, but the combined default behavior loses ten positive hits and gains four correct abstentions. This is a delivery and safety improvement with a retrieval/latency tradeoff, not an across-the-board quality win. The query-weighted scenario score falls from 0.7008 to 0.6817; no scenario-bootstrap interval is available for this single corpus. There are no labeled stale queries in this dataset. + +The contract set contains 11 positives, 11 negatives and two queries with explicit stale targets (the stale count overlaps the positive/negative classes). It includes six scenarios and a persistent-MCP workflow with writes from another process. + +| Requested budget | Build | Positive hit and recall | Negative injection | Stale injection | Mean MCP bytes | +|---|---|---:|---:|---:|---:| +| 512 | Baseline | 10/11 | 7/11 | 2/2 | 3,296 | +| 512 | Candidate | 1/11 | 1/11 | 0/2 | 308 | +| 2,048 | Baseline | 10/11 | 7/11 | 2/2 | 3,303 | +| 2,048 | Candidate | 8/11 | 2/11 | 0/2 | 417 | + +**Do not adopt 512 as a general default:** strict admission loses most positives at that size. At 2,048, all three missed candidate positives are the Spanish queries, each returning no capsule; the baseline retrieves two of those three. This tiny track establishes a multilingual coverage regression, not a general estimate of Spanish capability. The candidate's workflow score improves from 0.8 to 1.0 at 2,048, and the two labeled temporal cases stop injecting stale evidence. Neither result certifies behavior beyond these examples. + +The equal-dimension headline changes from 0.63 to 0.54 at 512 and from 0.63 to 0.85 at 2,048. These aggregates can hide lost positives; retain the metric vector above. The exploratory retrieval-scenario bootstrap at 2,048 spans −0.16 to +0.50, crossing zero with only five retrieval scenarios. + +### Runtime and model contrasts + +With the candidate binary and TinyBERT fixed, explicit four-thread ONNX execution preserved every ranking across all three paired runs. It did **not** improve latency here: + +| Runtime setting | Subsequent p50 / p95 | Mean complete run | Peak MCP working set | +|---|---:|---:|---:| +| Backend default | 940 / 1,052 ms | 203.4 s | 279.8 MiB | +| Four threads | 951 / 1,104 ms | 229.0 s | 276.9 MiB | + +Keep the backend default on this evidence. The small working-set difference does not justify the observed slowdown; no statistically significant effect or universal optimum is claimed. The explicit thread control remains useful for host contention and future workload-specific tests. It changes the ONNX global pool, including inter-op/spinning policy, so this is a runtime-configuration contrast rather than a pure physical-core-count experiment. + +With BGE-small, the candidate binary, four threads and a 6,000-unit requested budget fixed: + +| Metric | TinyBERT L2 | MiniLM L4 | +|---|---:|---:| +| Positive hit@4 | 139/197 (70.56%) | 142/197 (72.08%) | +| Positive fractional recall@4 | 69.63% | 70.90% | +| Positive MRR | 0.6980 | 0.7132 | +| Negative injection | 7/13 | 6/13 | +| Subsequent p50 / p95 | 973 / 1,049 ms | 1,139 / 1,346 ms | +| Mean MCP result | 1,262 bytes | 1,206 bytes | +| Peak MCP working set | 276.1 MiB | 710.9 MiB | + +MiniLM adds three positive hits and one correct abstention, at about **28% higher p95** and **2.58× peak working set**. Keep **BGE-small + TinyBERT with backend-default threads** as the economical measured starting point; MiniLM is an opt-in quality/resource tradeoff, not a demonstrated universal upgrade. This model contrast used four threads, so MiniLM on backend-default threads is unmeasured. The multilingual contract was not part of this model pair; these numbers do not establish a fix for the Spanish regression. No model/threshold setting was promoted into the user's live configuration. + +### Indexed document-frequency scaling + +An isolated in-memory SQLite experiment evaluates eight rare query terms over synthetic corpora. It asserts equal document counts between the old `LIKE` scans and current FTS-prefix joins, then alternates their order for five repeats. The current path includes its once-per-query population count. + +| Memories | Old median | Indexed median | Old / indexed | +|---|---:|---:|---:| +| 1,000 | 4.51 ms | 0.59 ms | 7.68× | +| 10,000 | 46.94 ms | 5.87 ms | 8.00× | +| 100,000 | 558.99 ms | 71.81 ms | 7.78× | +| 1,000,000 | 5,989.83 ms | 809.66 ms | 7.40× | + +This verifies reduced work for the helper under aligned token semantics. It does not benchmark production SQLite version/storage, common-term postings, end-to-end semantic retrieval or agent success. The indexed helper still has linear work, including its population count: the complete eight-term measurement took a median 810 ms at one million memories. A revision-aware population-count cache and incremental ANN maintenance are future scale work, not delivered or measured improvements in this patch. + +### Reproduction and artifacts + +All **five pairs completed with zero errors and zero unpaired scenarios**, totaling **4,044 query observations**. The [measurement artifacts](2026-09-05-brainbench/README.md) preserve comparison JSON, exact fixtures, a campaign runner and the SQL helper/results. Comparison JSON fingerprints both binaries, the harness, runner and dataset, and records run order/configuration. Raw per-query reports and command logs remain under `tmp-tests/paired-*` in the implementation worktree. These artifacts enable reanalysis; they are not additional held-out evidence. + +## The math and parameter decisions + +The previous tuning objective subtracted `0.005 × estimated_tokens`: 200 units erased an entire point of MRR. It also subtracted the same historical regret constant from every candidate, which cannot change the winner. + +The default now uses: + +`J = Q − 0.05 × mean_final_delivery_bound / 6000` + +With both classes present, `Q = (positive MRR + negative abstention accuracy) / 2`. This explicitly gives positive retrieval and avoiding unsupported injection equal class weight. It is a policy preference, not a fitted optimum. For MRR 0.5, negative accuracy 1, and delivery bound 512, `Q=0.75` and `J=0.745733`. Spending an entire 6000-unit budget subtracts 0.05 quality units. Missing class coverage is reported, and reliance-only personal data cannot authorize all-query automatic tuning. + +The measured development comparison illustrates the weighting decision. Ignoring delivery cost, equal-class `Q` rises from `(0.7386 + 2/13)/2 ≈ 0.4462` to `(0.6980 + 6/13)/2 ≈ 0.5798`. The benchmark's query-weighted fractional-recall/abstention score instead falls by `4/210 ≈ 0.0190`, because the lost positive recall mass exceeds the four newly correct abstentions. Neither weighting is mathematically mandatory: it encodes how costly unwanted memory is relative to missing useful memory. Do not optimize a headline without choosing that tradeoff. + +The delivery unit is a conservative UTF-8 byte bound over the serialized MCP content envelope, not provider tokenization or billing. A tiny budget smaller than the error envelope produces a truthful `budget_too_small` response, not a fabricated compliant token count. The JSON-RPC transport framing is measured separately by the harness. + +Usefulness and freshness remain heuristics. Correct half-life decay is `2^(−age / half_life)`, so a 30-day half-life leaves 0.5 after 30 days and 0.125 after 90 days. A sigmoid-transformed cross-encoder score is not a calibrated probability of useful or true evidence. No calibrator, Bayesian verification channel or empirically optimal threshold is claimed. + +## Verification + +- Full workspace, including integration and documentation tests: **1,371 passed, zero failed, five ignored**, using `cargo test --workspace --no-fail-fast --locked --offline -j 1`. +- Embeddings-enabled CLI and remote server: compile check passed. Both focused embeddings-enabled serving tests passed, including production/evaluation arbitration and final-budget parity. +- BrainBenchmark: **130 Rust tests and 16 Python tests passed**. +- Optimized embeddings-enabled candidate built from `b9658995609e945d18ee87163d543494c1c44713`. Benchmark source: `b3e0eda1641d340781eda843a477425f629aa119`. +- Scoped independent reviews covered corrections, lifecycle, delivery, evidence, evaluation, synchronization, warm caches, repository ingestion and benchmark methodology. Regression tests exposed the identified behavior before fixes; final tests were rerun after the last source change. + +Logs are preserved in `tmp-tests/final-workspace-complete.log`, `final-embeddings-check.log`, `final-embeddings-serving.log`, `final-candidate-build.log`, `benchmark-final-complete.log`, and `benchmark-final-python-complete.log`. Builds used one compiler job after an earlier parallel build exhausted available memory; that failed build is not counted as a passing verification run. + +## Interpretation and remaining evidence needed + +The six-scenario contract fixture and the existing 100-memory development fixture are development evidence, not a held-out certification. Repeats do not create independent examples. The paired harness averages repeats within each scenario/query; latency percentiles are descriptive. The larger fixture is one corpus/scenario, so its scenario-bootstrap uncertainty is unavailable. Thirteen no-answer examples provide little statistical power for a general abstention claim. + +The comparisons exercise production MCP retrieval with warm starts and ambient augmentation explicitly disabled, because their gold labels cover capsules. Separate regressions cover warm-start identity, freshness and budget admission. These timings exclude warm-start construction and are not complete agent-task latency or success measurements. No reader model or paid generation was run. + +Requested budget units do not represent equal realized response sizes across the two versions: the baseline uses the old accounting and can expose a much larger response. The before/after comparison also includes the candidate newly honoring configured stdio reranking. It measures the combined behavior change, not the causal effect of each fix or a model-controlled algorithm comparison. The same-candidate thread and reranker experiments isolate those configuration contrasts. All harness requests explicitly allow four capsules; the serving default remains three. + +The performance changes do not make every operation constant-time. Population counting and ANN reload after corpus revision remain proportional to corpus size; maintaining durable history consumes storage. The SQL scale experiment isolates document-frequency work and excludes models, ANN, serialization and full-agent behavior. Whole-corpus capacity, incremental ANN maintenance and representative concurrent workloads still need their own measurements. + +For capacity planning, raw dense-vector storage is `N × dimensions × bytes_per_component`. At 100,000 memories, 384-dimensional float32 vectors occupy 153.6 MB; a separate float16 ANN representation adds 76.8 MB before graph/index overhead. At one million memories those quantities become 1.536 GB and 0.768 GB. They are raw representation sizes, not measured resident RAM, and exclude text, SQLite indexes, revision history, models and caches. A 768-dimensional representation doubles the vector terms. Conservative archival protects correctness; it is not yet a measured whole-corpus capacity budget. + +The next evidence needed for choosing a universal default is a held-out set of repository/task families, enough explicit no-answer and stale/conflicting cases, representative Spanish/multilingual tasks, and actual agent task success with context cost. The implemented safeguards do not turn association scores into verification or make the existing development set establish “best memory.” diff --git a/docs/audits/2026-09-04-models-parameters-memory.md b/docs/audits/2026-09-04-models-parameters-memory.md new file mode 100644 index 0000000..7d06317 --- /dev/null +++ b/docs/audits/2026-09-04-models-parameters-memory.md @@ -0,0 +1,227 @@ +# Models, parameters, and memory management — 2026-09-04 + +Companion to the [first audit](2026-09-04-agent-memory-audit.md). Source baseline: `3ec56a8`, Kimetsu 2.7.0. The analysis concerns the memory models, not a comparison of frontier coding agents. No project configuration was applied and no paid generation benchmark was run. + +## Actual configuration and evidence available + +The project config selects BGE-small-en-v1.5, embeddings enabled, 8 capsules, a 6,000-token nominal budget, explicit semantic floor 0, 30-day usefulness half-life, and automatic harvesting. Missing fields inherit TinyBERT-L2-v2 reranking, lexical coverage 0.5, linear fusion, per-kind normalization, and abstention 0. New projects differ: their generated configuration enables automatic abstention. Therefore upgrading the binary does not make this project's thresholds match new-project defaults. + +The `[model]` Claude Opus configuration is for Kimetsu's coding/answering model. Its temperature 0.2 and output limit 8,192 do not tune BGE embeddings or TinyBERT. The configured distiller is disabled. Memory-generation tier and semantic retrieval level are separate concepts even though both use “deep” terminology. + +A read-only aggregate of the project brain found **49 active memories, all with BGE-small embeddings; 40 had never been used, none had ten uses, and none had a negative usefulness score**. These counts exclude the user brain. This is not enough evidence to fit a trustworthy personalized utility/forgetting policy or justify switching a production model automatically. + +## Measured model comparison + +Fresh release build, 100 memories, 197 positive queries and 13 no-answer queries; candidate pool 12, output cap 4. These are direct retrieval measurements on this machine, with default CPU allocation. Load and corpus seeding are outside query latency; the first query is included. RSS is the benchmark process's reported peak, not incremental model weight size. + +| Embedder | Reranker | Positive MRR | Positive hit@4 | Mean / p95 latency | Peak RSS | +|---|---|---:|---:|---:|---:| +| BGE-small | TinyBERT-L2 | 0.9095 | 94.92% | 588 / 672 ms | 522 MiB | +| BGE-small | MiniLM-L4 | 0.9255 | 95.94% | 833 / 998 ms | 1,324 MiB | +| BGE-small | Off | 0.8105 | 86.29% | 561 / 637 ms | 361 MiB | +| Jina-code | TinyBERT-L2 | 0.8697 | 90.36% | 151 / 365 ms | 1,551 MiB | +| Jina-code | MiniLM-L4 | 0.8896 | 91.88% | 369 / 604 ms | 2,351 MiB | +| Jina-code | Off | 0.7923 | 84.26% | 126 / 333 ms | 1,468 MiB | + +**Keep BGE-small + TinyBERT as the baseline.** Removing reranking loses about 0.099 MRR for only 27 ms saved in this run. MiniLM's improvement over TinyBERT is 0.0161 MRR; the exploratory paired cluster-bootstrap 95% interval is [-0.0012, 0.0327], so this dataset does not clearly establish a reliable improvement. The interval groups by 114 expected-key sets, not independent repositories. Jina is faster under these runtime defaults but costs roughly three times the process RAM and retrieves less accurately here. None of these findings establishes the best model across all agent tasks. + +### Abstention matters more than a small ranking gain + +All 13 no-answer cases receive four memories with BGE under this benchmark path. A post-hoc TinyBERT score floor of 0.30 reduces positive hit@4 from 94.92% to 85.79%, while still injecting on 7/13 no-answer cases. At 0.99, hit@4 falls to 69.04% and 1/13 no-answer cases still receives memory. A high sigmoid score alone does not solve this problem. + +These are diagnostic filters over already returned top-four results, not a full rerun of production arbitration; they cannot recover earlier discarded candidates. Do not copy either threshold into production as an optimum. Add explicit negative examples and calibrate the complete decision to inject. + +### CPU allocation experiment + +Same BGE-small + TinyBERT workload, sequential runs, verified affinity masks on isolated children: + +| Logical CPUs allowed | Mean latency | p95 latency | Peak RSS | MRR | +|---|---:|---:|---:|---:| +| 16, default baseline | 588 ms | 672 ms | 522 MiB | 0.9095 | +| 1 | 703 ms | 821 ms | 502 MiB | 0.9095 | +| 4 | 568 ms | 639 ms | 507 MiB | 0.9095 | +| 8 | 552 ms | 621 ms | 511 MiB | 0.9095 | + +Eight logical CPUs improved mean latency by 6.0% and p95 by 7.6% in this run. That is a promising follow-up setting, not a demonstrated optimum: there is one run per allocation, fixed run order, and no simultaneous coding-agent workload. Process affinity changes scheduling as well as available CPU capacity; it is not an isolated ONNX thread-count experiment. One CPU was slower. Expose explicit inference thread/spinning controls and repeat randomized trials before making a global default change. + +### Spanish stress check + +On 12 manually authored Spanish queries against the same English memories, BGE-small + TinyBERT found the expected memory in the top four for **8/12** queries (MRR 0.6042); Jina-code + TinyBERT did so for **6/12** (MRR 0.5000). The fixture retains 13 existing English no-answer queries separately. This is a small exploratory cross-language test, not a matched English/Spanish experiment or a language-wide estimate. + +The misses justify adding a representative multilingual evaluation set. If Spanish is a regular usage language, test a multilingual embedder **and** reranker together before choosing a replacement. Moving to the tested Jina-code model alone did not improve this stress check. + +### Recommended decisions + +| Item | Decision justified now | +|---|---| +| BGE-small + TinyBERT | Keep as the local-memory baseline; no paid generation required for these models. | +| MiniLM-L4 reranker | Optional quality experiment; its small measured gain does not yet justify the RAM/latency increase as default. | +| CPU allocation | Repeat the eight-logical-CPU candidate with explicit ONNX controls, randomized run order, and concurrent host load. | +| Semantic/rerank/abstention thresholds | Repair evaluator parity and collect explicit negatives before declaring an optimum. The old project's missing abstention field currently disables that gate. | +| Tuner | Correct objective units and per-candidate outcome measurement; keep automatic application off until validated. | +| Conflict resolution / consolidation | Keep detection available; disable automatic resolution pending claim-level checks, and constrain merging by scope and identity. | +| Forgetting | Keep automatic forgetting disabled until usefulness labels and recovery/archival semantics are trustworthy. | +| ANN parameters | Leave the current baseline at this corpus size; evaluate recall/latency at actual target scale later. | +| Claude temperature/output limit | No claim of optimality: these benchmarks exercise memory retrieval, not the configured generative agent. Evaluate those separately on coding-task success and cost. | + +This follow-up completed **11 benchmark combinations/runs and 1,940 query evaluations**: six model combinations, three CPU-affinity runs, and two Spanish stress runs. Repeated queries are not independent new examples. All model inference in these experiments was local; no live project configuration or memory records were changed. + +## The mathematical problems + +### Relevance scores are not probabilities + +The linear path blends lexical evidence `L` and cosine `c` as: + +`R = (1 - 0.5)L + 0.5(c + 1)/2 = 0.5L + 0.25c + 0.25`. + +Even `L=0, c=0` produces 0.25. The next normalization divides each score by the strongest score in its capsule kind, making the best memory's relevance 1 even in an irrelevant corpus. This is useful for relative ordering; it cannot establish answerability. + +The stage score then combines `wR*R_normalized + wC*confidence + wF*freshness + wS*scope`. A final cross-encoder replaces the numerical score, while additional policy ordering still applies. Thus the configured weights are not a simple final linear ranking model. + +Cross-encoder logits pass through `sigmoid(z)=1/(1+exp(-z))`. This preserves rank, but it does **not** make the output a calibrated probability of a helpful memory. BGE's authors likewise warn against transferring similarity thresholds without measuring the local score distribution. They suggest an optional query instruction for short-query retrieval and recommend evaluating it on the task. See the [BGE model card](https://huggingface.co/BAAI/bge-small-en-v1.5). + +**Recommendation:** Separate candidate relevance from the decision to inject. Fit an answerability/usefulness calibrator on labeled positive and no-answer queries, preferably using raw relevance, score margin, coverage, applicability, and evidence grade. Assess calibration on held-out data. Keep exact identifier/BM25 retrieval alongside dense retrieval. Compare RRF experimentally; it removes score-scale dependence but does not itself solve abstention. + +### The tuning objective has the wrong scale for a conservative quality tradeoff + +Current objective: `J = MRR - 0.005*T - 0.5*regret_rate`. + +MRR lies in [0,1]. The token term subtracts an entire MRR unit for 200 tokens: + +| Illustrative configuration | MRR | Tokens | Objective before regret | +|---|---:|---:|---:| +| Better retrieval | 0.95 | 300 | -0.55 | +| Worse retrieval | 0.80 | 100 | 0.30 | + +That tradeoff may be intentional for an extremely expensive context, but it is not a small efficiency regularizer. If 200 additional tokens are worth sacrificing 0.01 MRR, the corresponding coefficient is `0.01/200 = 0.00005`, **100 times smaller**. This is an illustrative preference, not an empirically optimal coefficient. + +More importantly, the CLI supplies the **same historical regret rate for every combination**. Subtracting a common constant cannot affect the winner. The advertised regret-aware optimization therefore does not select less regrettable configurations. + +Use a constrained objective first: maximize measured task success or retrieval utility subject to a final token budget, latency limit, and no-answer false-injection limit. Once actual task costs are known, use comparable units instead of mixing a rank metric and uncalibrated token estimates. + +Evidence: [tune.rs](../../crates/kimetsu-brain/src/tune.rs#L347); [tuner CLI](../../crates/kimetsu-cli/src/commands/brain.rs#L2838). + +### The self-tuner's experimental design needs repair before automatic application + +- Its evaluator uses **pool 8 / cap 4 / reranker floor 0.30**. The model-grid benchmark uses **pool 12 / cap 4 / floor 0**. Production adds evidence-band arbitration and host-specific delivery. These are different experiments. +- A semantic value of `0` in an injected request means “inherit configuration”; `-1` is passed through and fails the positive-floor condition instead of resolving the documented automatic threshold. The sweep labels do not consistently mean what users expect. +- The train/holdout split is every fifth array position. Personal examples originate from a `HashMap`; it is not a stable split by query identity, session, repository, or fact family. Paraphrases can leak across the split. +- Personal examples without citations are counted but excluded from the returned evaluation cases. Absence of a citation is not necessarily a negative label, but removing all such cases also means the dataset cannot measure abstention. +- Citation matching can fall back to a time window across sessions. That introduces mislabeled examples. + +**Recommendation:** One canonical evaluator shared by tuning, benchmarking, and serving. Use explicit `Option`/enum semantics for inherit/off/automatic thresholds. Split by stable fact/task family and time; preserve explicit no-answer labels and retain unknown outcomes separately. Include a final untouched test set and report uncertainty. Do not use `brain tune --apply` as an automatic authority yet. + +Evidence: [request resolution](../../crates/kimetsu-brain/src/project.rs#L604), [tuner evaluation](../../crates/kimetsu-cli/src/commands/brain.rs#L2778), [split](../../crates/kimetsu-brain/src/tune.rs#L418), [personal labels](../../crates/kimetsu-brain/src/tuneset.rs#L75). + +### Decide to inject using expected utility + +A simple model is: + +`U = p*B - (1-p)*H - C`. + +Here `p` is calibrated helpfulness probability, `B` is expected benefit when helpful, `H` is expected harm when misleading, and `C` is delivery/attention cost, all in the same units. Inject only if: + +`p > (H+C)/(B+H)`. + +For illustrative equivalent-work costs `B=500, H=1500, C=200`, the threshold is **0.85**. Changing the risk changes the right threshold. A remembered formatter command and a migration instruction should not share identical error costs. A raw cosine of 0.85 is not this probability. + +For multiple memories, select their **marginal** benefit under a shared budget: maximize total utility minus redundant evidence costs, subject to total serialized tokens <= budget. Do not count two paraphrases from one source as two independent confirmations. MMR is a useful heuristic, but evidence needed for multi-hop answers can look redundant and must remain available. + +### Confidence and decay currently mean several different things + +The confidence EMA is `c_next = 0.95*c + 0.05*y`. Starting at 0.5, three successes produce **0.5713**, while the usefulness multiplier already reaches its full envelope after three uses. These two confidence mechanisms react at very different speeds. An EMA with alpha 0.05 has an asymptotic effective sample size of roughly `(2-alpha)/alpha = 39` under independent stationary observations; it is not automatically Bayesian calibration. + +A more interpretable starting point for verified binary outcomes is a Beta prior: + +`p ~ Beta(a+s, b+f)`, with posterior mean `(a+s)/(a+b+s+f)`. + +With a uniform prior and three successes, the mean is 0.8, but the equal-tail 95% interval's lower endpoint is only **0.398**. Three successes are weak evidence. Real outcomes are correlated, so even this model requires session/source grouping and careful definitions of success and failure. + +There are also three different clocks: + +- Configured usefulness decay: `2^(-age/30)`; 30-day half-life. +- Freshness score: `exp(-age/30)`; **20.79-day half-life**, not 30. +- Conflict resolution: confidence times a hardcoded 30-day half-life, independent of the broker's configured one. + +The verification-stage freshness weight is 0.4. After 90 days, freshness is only 0.0498, so a new memory can gain about 0.38 score from recency alone. That is a substantial bias against stable verified procedures. + +**Recommendation:** Use explicit clocks for factual validity, recent usefulness, and retrieval activity. Stable preferences/conventions should persist until corrected; code facts should depend on source revision; task episodes should depend on task lifetime. Decay uncertainty or priority, not truth indiscriminately. + +Evidence: [scoring constants](../../crates/kimetsu-brain/src/scoring.rs), [usefulness](../../crates/kimetsu-brain/src/context.rs#L1763), [freshness](../../crates/kimetsu-brain/src/context.rs#L2333), [conflict score](../../crates/kimetsu-brain/src/conflict.rs#L155). + +## Memory management beyond the earlier audit + +### Separate duplicate, related, contradictory, and superseding claims + +High cosine means related meaning, not equivalence or contradiction. Current conflict detection can treat similar-but-different text as a conflict, and resolution picks a confidence-times-recency winner. A confidence-0.95 fact aged 90 days scores 0.11875; a confidence-0.55 new statement scores 0.55 and clears the 0.15 winner gap. Recency can therefore retire stronger old evidence without proving a contradiction. + +The distiller novelty gate can also reject a correction because its cosine to the old claim exceeds 0.9. Phrases like “temporarily” are dropped even when they describe a useful version-scoped workaround. + +Use similarity to propose relationships. Use an explicit claim key—entity, attribute, scope, condition, validity—and incompatible values or correction evidence to resolve them. Route temporary facts to expiring episodic memory. Until that exists, automatic conflict resolution should be disabled for reliable-memory operation, while conflict detection remains available for review. + +Evidence: [conflict.rs](../../crates/kimetsu-brain/src/conflict.rs#L104), [quality gate](../../crates/kimetsu-cli/src/distiller.rs#L171). + +### Consolidation needs semantic and scope constraints + +The merge loop checks embedding model identity but **not scope or memory kind**. It joins all connected pairs above cosine 0.92, then keeps the survivor's text and combines evidence counters. + +Connected similarity is not transitive equivalence. Let three unit vectors lie at 0°, 20°, and 40°. Adjacent similarities are **0.9397**, above 0.92, but the endpoints have similarity **0.7660**. Union-find still merges all three. A unique detail can disappear, and cross-scope material can inherit the survivor's scope. + +Require matching scope and claim identity; constrain each member against the representative, not just any neighbor. Preserve unique clauses and source lineage. Do not add support counts as if dependent copies were independent observations. Use ANN to propose bounded candidate pairs instead of exhaustive O(N²) comparisons; at 100K entries the latter is roughly five billion pairs. + +Evidence: [consolidate.rs](../../crates/kimetsu-brain/src/consolidate.rs#L332). + +### Popularity must not make memories immortal + +Forgetting is disabled by default, which is appropriate with the current evidence. Its optional policy protects any memory with ten uses regardless of later usefulness. Retrieval activity also refreshes the age reference. A repeatedly surfaced bad memory can therefore remain protected precisely because it keeps being surfaced. `COALESCE(last_used_at,last_useful_at,created_at)` is additionally first-non-null selection, not the “most recent” timestamp described in the comment. + +Keep separate hot, archived, invalid, and superseded states. Archive low expected future utility under a capacity budget; reserve invalidation for false/inapplicable claims. Retain explicit durable preferences and rare critical recovery procedures. A verified contradiction must override popularity. Provide a recovery route for archived evidence. + +Evidence: [forget policy](../../crates/kimetsu-brain/src/lifecycle.rs#L243), [defaults](../../crates/kimetsu-core/src/config.rs#L1382). + +### Bounded usefulness should remain bounded after reranking + +Before reranking, usefulness gains are capped at 0.10. After reranking, a positive usefulness tier sorts before all neutral tiers. For example, a cited capsule at cross-encoder score 0.31 can outrank a neutral capsule at 0.99 when both clear a 0.30 floor. This reintroduces unbounded priority in a different form. + +Usefulness should reorder similarly relevant, currently applicable candidates, not override relevance globally. Preserve evidence thresholds and cap the final effect. Evaluate repeated-success incumbents against new correct facts explicitly. + +Evidence: [policy ordering](../../crates/kimetsu-brain/src/context.rs#L3278). + +## Model/runtime parameters worth experimenting with + +- **CPU threads:** FastEmbed 5.13.4 initializes ONNX intra-op threads from all available logical processors for both embedding and reranking. This machine has a Ryzen 7 3800X, 8 cores/16 logical processors, and about 32 GiB RAM. More threads are not automatically faster for short, sequential inference. Measure 1/2/4/8 threads with spinning on/off and realistic host contention; expose explicit thread budgets rather than consuming all available CPU. +- **Input length:** Kimetsu does not override FastEmbed's 512-token maximum. Buying an 8K-capable model does not automatically provide an 8K embedding window in this integration. Chunk complete atomic facts/procedures, retain source handles, and test evidence near the truncation boundary before increasing the maximum. +- **Query encoding:** Compare a query-specific BGE instruction against no instruction while leaving document embeddings unchanged. The current adapter uses the same `embed(text)` operation for both. +- **Candidate pool:** Compare 8/12/24/32 under the same retrieval and final-token budgets. Increasing pool alone may not help when pre-rerank budgeting already removed evidence. Measure candidate recall before blaming the reranker. +- **ANN:** Current M=16, construction expansion=128, search expansion=64, f16 are reasonable starting points, not measured optima. Tune ANN recall against exact cosine at target scale separately from answer quality. At only 49 memories, ANN tuning is unlikely to be the primary lever. +- **Memory footprint:** Dense vector storage is `N*d*bytes`. At 100K entries, BGE's 384-dimensional float32 blobs require 153.6 MB of raw storage; 768-dimensional Jina doubles that. The f16 ANN vectors add a separate representation, plus graph and model overhead. Disk blob size is not the same as resident memory. + +The daemon already shares loaded models and uses a bounded connection queue, which is a useful foundation. Each embedding/reranking engine is protected by a mutex, so adding connection workers does not create independent inference capacity. Benchmark concurrent clients and report queue wait separately from model inference; avoid multiplying model copies just to improve a single-request latency chart. Evidence: [model engines](../../crates/kimetsu-brain/src/embeddings.rs#L705), [daemon queue](../../crates/kimetsu-cli/src/embed_daemon/server.rs#L155). + +For the nominal 6,000-token context budget, run a separate end-to-end sweep at 256/512/1,024 tokens after the previously identified final-payload budget issue is fixed. Select the smallest budget preserving task success and critical constraints. Those values are experiment points, not a claim that every task fits 1,024 tokens. Measure all memory-related host tokens, including tool calls, metadata, harvesting, and injected content. + +“Zero tokens” is achievable for **paid LLM calls used by the memory subsystem** when extraction and retrieval remain local/deterministic. A hosted agent still consumes context tokens when it reads a memory. Optimize net task tokens saved and useful decisions per injected token; zero additional context tokens cannot convey new textual knowledge to the model. + +For Spanish or mixed natural languages, BGE-M3 and a multilingual reranker are candidates, not proven replacements. Kimetsu already exposes these model families. Their resources and thresholds require a separate local benchmark. [BGE-M3 model card](https://huggingface.co/BAAI/bge-m3). Jina-v2-code targets English and programming languages, so its label alone does not establish Spanish quality. [Jina model card](https://huggingface.co/jinaai/jina-embeddings-v2-base-code). TinyBERT's published speed figures are GPU measurements, not a promise about this CPU. [TinyBERT model card](https://huggingface.co/cross-encoder/ms-marco-TinyBERT-L2-v2). + +## Reproduction and scope of measurements + +Current-source embeddings release build: `cargo build -p kimetsu-cli --release --features embeddings --locked --offline`. + +Model grid: `kimetsu brain bench --dataset bench/local/dataset-100.json --embedders bge-small-en-v1.5,jina-v2-base-code --rerankers off,ms-marco-tinybert-l-2-v2,ms-marco-minilm-l-4-v2 --pool 12 --cap 4 --out tmp-tests/model-audit-grid`. + +Child environment disables the user brain and conflict detection/resolution, keeping the corpus contents fixed. The benchmark initializes a fresh project, so its inherited thresholds differ from the existing project configuration. It calls the broker/reranker directly, not the full host/MCP delivery path. Timestamps are generated during seeding; exact replay with fixed timestamps is still needed before interpreting small model differences as causal. + +The dataset has 100 memories, 197 positive queries and 13 no-answer queries. It is an existing development dataset, not a newly held-out test. Some positives have multiple relevant keys. The CLI's displayed “recall@4” is actually an any-relevant **hit rate**; the analysis script additionally calculates true fraction-of-relevant recall. + +The [analysis script](../../tmp-tests/model_audit_analysis.py) produces [metrics and mathematical examples](../../tmp-tests/model-audit-analysis.json), including exploratory score-floor sweeps and a paired bootstrap grouped by expected-key set. These groups do not establish repository-level independence. The 13 no-answer cases are too few to certify a low error rate: even zero failures would leave a **20.6% one-sided 95% upper bound** under an independent-binomial model. + +The [Spanish fixture builder](../../tmp-tests/model_audit_stress_fixture.py) adds 12 manually authored positive queries and reuses the 13 existing no-answer queries. It is an exploratory stress check, not a comprehensive language benchmark. The [affinity runner](../../tmp-tests/model_audit_affinity.py) changes affinity only for its own process and benchmark children. + +### What the existing tests establish + +The earlier audit ran the lean test suites: 1,060 passed, four ignored, none failed. This follow-up rebuilt the embeddings release and exercised real local models. It did not rerun the entire suite because implementation code was unchanged. + +Review of the inline tests found coverage for merge identity/model separation, event replay, forgetting, abstention, decay curves, and tuner arithmetic. Those checks are valuable but do not establish useful policy: the reranking test explicitly expects a historically useful item to outrank a higher relevance score, and objective tests verify the chosen formula rather than the desirability of its tradeoff. + +Missing evaluation priorities are cross-scope/non-transitive merge safety, conflicting near-duplicates, corrections after repeated prior success, rare critical memory retention, calibrated no-answer behavior, and stable task-family holdouts. Add end-to-end agent tasks measuring success, wrong-memory harm, and final context tokens; this retrieval fixture contains no labeled stale-memory correctness cases, so its zero stale-hit rate is not evidence of temporal correctness. diff --git a/docs/audits/2026-09-05-brainbench/.gitattributes b/docs/audits/2026-09-05-brainbench/.gitattributes new file mode 100644 index 0000000..3840a6b --- /dev/null +++ b/docs/audits/2026-09-05-brainbench/.gitattributes @@ -0,0 +1,3 @@ +# Preserve measured artifact bytes and fixture SHA-256 hashes across checkouts. +*.json -text whitespace=cr-at-eol +benchmark_idf_sql.py -text whitespace=cr-at-eol diff --git a/docs/audits/2026-09-05-brainbench/README.md b/docs/audits/2026-09-05-brainbench/README.md new file mode 100644 index 0000000..60629fe --- /dev/null +++ b/docs/audits/2026-09-05-brainbench/README.md @@ -0,0 +1,39 @@ +# BrainBenchmark measurement artifacts — September 5, 2026 + +Read the [results and limitations](../2026-09-04-hardening-results.md) before interpreting these comparisons. + +| Artifact | Comparison | +|---|---| +| [contract-512.json](contract-512.json) | Original versus hardened binary; contract fixture; requested budget 512 | +| [contract-2048.json](contract-2048.json) | Same, requested budget 2,048 | +| [development-6000.json](development-6000.json) | Original versus hardened binary; 197 positive and 13 negative development queries; requested budget 6,000 | +| [threads-default-vs-4.json](threads-default-vs-4.json) | Hardened binary + TinyBERT on both sides; default versus four-thread runtime | +| [tinybert-vs-minilm-4threads.json](tinybert-vs-minilm-4threads.json) | Hardened binary + four-thread runtime on both sides; TinyBERT L2 versus MiniLM L4 | +| [idf-sql-comparison.json](idf-sql-comparison.json) | Isolated synthetic SQLite helper comparison, five repeats at each corpus size | + +Each paired JSON records binary, harness, Python runner and fixture SHA-256 fingerprints, configuration, alternating run order, scenario pairing, summaries and raw-report filenames. All five pairs completed without errors or unpaired scenarios. There are 4,044 query observations, including repeated measurements; the fixtures are development evidence, not held-out evaluation. Raw reports/logs remain in `tmp-tests/paired-*` in the implementation worktree; those files are not embedded in these summary artifacts. + +Source revisions: + +- Original Kimetsu: `3ec56a8`; frozen executable SHA-256 `aba19d66742fe4c6b7d8902a52f0b6d5a0ad5c9d72ecf705a2e5d69d546a75c0`. +- Hardened Kimetsu: `b9658995609e945d18ee87163d543494c1c44713`; executable SHA-256 `5c87e542907a47917f23fad50ddff1e46789eaae14328ccc662ca748b66b5477`. +- Separate benchmark repository: `b3e0eda1641d340781eda843a477425f629aa119`. + +The [contract fixture](agent-memory-contract.json) has 22 queries in six scenarios. The [development fixture](development-100.json) is a converted copy of the existing 100-memory dataset; the original dataset's SHA-256 was `7a9da27cc7c3b88c13b0fb97eee5fdb2c02c5c308c6719a992cc6dbbf98bf58f`. Conversion preserves its 197 positive and 13 negative queries. Copies here have the same bytes as the campaign inputs. + +To reproduce, build the CLI at each source revision with `cargo build -p kimetsu-cli --release --features embeddings --locked --offline -j 1`, preserving each executable separately. Build the benchmark at its revision with `cargo build --release --bin kbench --locked --offline -j 1`. Rebuilding on a different toolchain/host can change binary hashes and timings. The separate benchmark repository must be available at the implementation root's `bench` directory. + +Run [run-comparisons.ps1](run-comparisons.ps1) from a fresh PowerShell process, supplying the preserved executables and a cache containing BGE-small, TinyBERT L2 and MiniLM L4: + +```powershell +pwsh -NoProfile -File ./docs/audits/2026-09-05-brainbench/run-comparisons.ps1 ` + -Baseline ./tmp-tests/kimetsu-baseline.exe ` + -Candidate ./tmp-tests/kimetsu-candidate.exe ` + -Harness E:/Kimetsu/bench/target/release/kbench.exe ` + -ModelCache E:/Kimetsu/.fastembed_cache ` + -OutputRoot ./tmp-tests/brainbench-rerun +``` + +Choose a new output directory. The runner uses only temporary benchmark brains, sets offline/local model controls, runs the five pairs sequentially with three repeats each, rejects errors/unpaired scenarios, then runs [the SQL helper](benchmark_idf_sql.py). It intentionally leaves host/global configuration unchanged. Do not run compilation, tests or other local inference concurrently. The measured campaign's original launcher remains at `tmp-tests/run-hardening-comparisons.ps1`; this parameterized wrapper reproduces its settings without hard-coding binary locations. + +The SQL experiment uses Python's SQLite library and an in-memory synthetic database. It compares aligned rare-token document counts, includes the new population count, and excludes ANN, models, serialization and production-storage effects. Its results must not be described as an end-to-end retrieval speedup. diff --git a/docs/audits/2026-09-05-brainbench/agent-memory-contract.json b/docs/audits/2026-09-05-brainbench/agent-memory-contract.json new file mode 100644 index 0000000..be4c897 --- /dev/null +++ b/docs/audits/2026-09-05-brainbench/agent-memory-contract.json @@ -0,0 +1,262 @@ +{ + "scenarios": [ + { + "id": "exact-code-evidence", + "dimension": "retrieval", + "tier": "easy", + "description": "Exact identifiers, multi-fact recall, and unrelated no-answer queries.", + "memories": [ + { + "key": "stderr", + "text": "MCP diagnostic logs must go to stderr. Stdout is reserved for JSON-RPC messages." + }, + { + "key": "foreign-keys", + "text": "Enable SQLite foreign key constraints explicitly on every new connection using PRAGMA foreign_keys = ON." + }, + { + "key": "blocking", + "text": "Move long synchronous operations into tokio::task::spawn_blocking instead of blocking asynchronous executor threads." + }, + { + "key": "test-isolation", + "text": "Initialize a Git boundary in each temporary test workspace so repository discovery cannot write to the real project." + } + ], + "queries": [ + { + "query": "Where must MCP diagnostic logs be written?", + "relevant": [ + "stderr" + ] + }, + { + "query": "How do I enable SQLite foreign key constraints on a new connection?", + "relevant": [ + "foreign-keys" + ] + }, + { + "query": "How should I handle a long synchronous operation in the Tokio executor?", + "relevant": [ + "blocking" + ] + }, + { + "query": "What is the weather forecast for tomorrow?", + "relevant": [] + }, + { + "query": "What is the current production database password?", + "relevant": [] + } + ] + }, + { + "id": "related-but-unanswerable", + "dimension": "retrieval", + "tier": "hard", + "description": "Related vocabulary must not imply that a stored fact answers a different question.", + "memories": [ + { + "key": "http-port", + "text": "The local development HTTP server listens on port 4317." + }, + { + "key": "storage", + "text": "The project stores local agent memories in a SQLite database." + }, + { + "key": "logs", + "text": "Development request logs include the route and elapsed milliseconds." + } + ], + "queries": [ + { + "query": "Which port does the local development HTTP server use?", + "relevant": [ + "http-port" + ] + }, + { + "query": "What authentication password does the local development HTTP server require?", + "relevant": [] + }, + { + "query": "Which encryption key protects the SQLite database?", + "relevant": [] + }, + { + "query": "What is the production request log retention duration?", + "relevant": [] + } + ] + }, + { + "id": "cross-language-code", + "dimension": "retrieval", + "tier": "hard", + "description": "Small exploratory Spanish query track against English code memories.", + "memories": [ + { + "key": "stderr", + "text": "Write MCP diagnostic messages to stderr because stdout carries the JSON-RPC protocol." + }, + { + "key": "foreign-keys", + "text": "SQLite foreign key enforcement must be enabled separately for every connection with PRAGMA foreign_keys = ON." + }, + { + "key": "blocking", + "text": "Use tokio::task::spawn_blocking for long synchronous work inside an asynchronous application." + } + ], + "queries": [ + { + "query": "¿Dónde deben escribirse los mensajes de diagnóstico MCP para no romper JSON-RPC?", + "relevant": [ + "stderr" + ] + }, + { + "query": "¿Cómo se activan las claves foráneas de SQLite en cada conexión?", + "relevant": [ + "foreign-keys" + ] + }, + { + "query": "¿Cómo ejecuto trabajo síncrono largo sin bloquear Tokio?", + "relevant": [ + "blocking" + ] + }, + { + "query": "¿Cuál es el precio actual de la electricidad?", + "relevant": [] + } + ] + }, + { + "id": "multi-fact-retrieval", + "dimension": "retrieval", + "tier": "medium", + "description": "Fraction recall requires both expected facts; finding either is only a hit.", + "memories": [ + { + "key": "git-boundary", + "text": "For safe temporary memory tests, run git init in the temporary workspace to establish a repository boundary." + }, + { + "key": "user-brain", + "text": "For isolated memory tests, set KIMETSU_USER_BRAIN=0 to prevent cross-project memory leakage." + }, + { + "key": "release", + "text": "Release packaging produces a compressed archive containing the executable." + } + ], + "queries": [ + { + "query": "How do temporary memory tests establish a Git boundary and prevent user brain leakage?", + "relevant": [ + "git-boundary", + "user-brain" + ] + }, + { + "query": "Which executable signing certificate is used for releases?", + "relevant": [] + } + ] + }, + { + "id": "live-temporal-applicability", + "dimension": "retrieval", + "tier": "hard", + "description": "Future starts and expired claims must not appear in current context; these are storage validity fields, not text hints.", + "memories": [ + { + "key": "expired", + "text": "The Atlas staging listener uses port 4001.", + "valid_to": "2020-01-01T00:00:00Z" + }, + { + "key": "future", + "text": "The Atlas staging listener uses port 4003.", + "valid_from": "2099-01-01T00:00:00Z" + }, + { + "key": "current", + "text": "The Atlas staging listener uses port 4002." + } + ], + "queries": [ + { + "query": "Which port does the Atlas staging listener currently use?", + "relevant": [ + "current" + ], + "stale": [ + "expired", + "future" + ] + }, + { + "query": "What authentication password does the Atlas staging listener require?", + "relevant": [], + "stale": [ + "expired", + "future" + ] + } + ] + }, + { + "id": "persistent-mcp-observes-new-writes", + "dimension": "workflow", + "tier": "medium", + "description": "A persistent MCP process must see new claims written by another process, while related unknowns remain unanswered.", + "workflow": { + "seed": [], + "episodes": [ + { + "task": "Which port does the Zephyr local development HTTP server use?", + "relevant": [], + "record": [ + { + "key": "port", + "text": "The Zephyr local development HTTP server listens on port 5243." + } + ] + }, + { + "task": "Which port does the Zephyr local development HTTP server use?", + "relevant": [ + "port" + ] + }, + { + "task": "What authentication password does the Zephyr HTTP server require?", + "relevant": [] + }, + { + "task": "Where should MCP diagnostic logs be written?", + "relevant": [], + "record": [ + { + "key": "logs", + "text": "MCP diagnostic logs go to stderr; stdout is reserved for JSON-RPC messages." + } + ] + }, + { + "task": "Where should MCP diagnostic logs be written?", + "relevant": [ + "logs" + ] + } + ] + } + } + ] +} diff --git a/docs/audits/2026-09-05-brainbench/benchmark_idf_sql.py b/docs/audits/2026-09-05-brainbench/benchmark_idf_sql.py new file mode 100644 index 0000000..413d6b1 --- /dev/null +++ b/docs/audits/2026-09-05-brainbench/benchmark_idf_sql.py @@ -0,0 +1,36 @@ +"""Isolated SQLite helper comparison, not end-to-end/semantic retrieval latency. +Uses the old per-term LIKE scan and current FTS-prefix join + population count. +The synthetic corpus makes their document counts equal so work is comparable. +""" +from pathlib import Path +import argparse,json,platform,sqlite3,statistics,time +TERMS=['narwhal','quokka','walrus','puffin','ibex','ocelot','tapir','wombat'] +OLD_N='SELECT COUNT(*) FROM memories WHERE invalidated_at IS NULL' +OLD_DF='SELECT COUNT(*) FROM memories WHERE invalidated_at IS NULL AND lower(text) LIKE ?' +NEW_N='SELECT COUNT(*) FROM memories_fts JOIN memories m USING(memory_id) WHERE m.invalidated_at IS NULL' +NEW_DF='SELECT COUNT(DISTINCT m.memory_id) FROM memories_fts JOIN memories m USING(memory_id) WHERE memories_fts MATCH ? AND m.invalidated_at IS NULL' +def measure(conn,new): + n=conn.execute(NEW_N if new else OLD_N).fetchone()[0] + counts=[conn.execute(NEW_DF if new else OLD_DF, ('text : "'+term+'"*' if new else '%'+term+'%',)).fetchone()[0] for term in TERMS] + return n,counts + +def main(): + parser=argparse.ArgumentParser(description=__doc__);parser.add_argument('--sizes',default='1000,10000,100000,1000000');parser.add_argument('--repeats',type=int,default=5);parser.add_argument('--out',type=Path,required=True);args=parser.parse_args() + result={'scope':'isolated SQL helper, synthetic aligned token semantics, in-memory SQLite; excludes ANN/model/reranking/serialization','sqlite':sqlite3.sqlite_version,'platform':platform.platform(),'processor':platform.processor(),'repeats':args.repeats,'rows':[]} + for n in map(int,args.sizes.split(',')): + c=sqlite3.connect(':memory:');c.executescript('CREATE TABLE memories(memory_id TEXT PRIMARY KEY,text TEXT,invalidated_at TEXT);CREATE VIRTUAL TABLE memories_fts USING fts5(memory_id UNINDEXED,text);') + def corpus(): + for i in range(n): + term=TERMS[i%808] if i%808/.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races. Re-enable kimetsu_brain_ingest_repo in the tool allowlist only when ingest is configured, and INTERCEPT that tools/call in the remote handler (clone+ingest_repo_at_root) before the normal dispatch (which would walk the wrong dir). Hermetic test: git init a temp repo, register url=local path, ingest, then context retrieves the file capsule via FTS (noop embedder). (context: R3c: server-side ingest for kimetsu-remote — cloning repos so file-capsule retrieval works without a local checkout.)" + }, + { + "key": "remote-mcp-host-wiring", + "text": "[tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal. Derive a stable repo id from the git remote: strip `.git`, scheme (`://`), and `user@`, then map non-alphanumerics to '-' and collapse — so both https://github.com/org/repo.git and git@github.com:org/repo.git -> `github-com-org-repo`. Remote install writes ONLY the MCP entry + instructions (no local hooks — the brain is on the server). Codex/Pi don't get --remote (no remote-MCP / no MCP). (context: R2: implementing `kimetsu plugin install --remote` to wire a host at a kimetsu-remote HTTP MCP server.)" + }, + { + "key": "cargo-feature-unification-embeddings", + "text": "[tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli). Diagnostic tell: a test that passes alone but fails only under `cargo test --workspace` AND a brand-new crate was just added = suspect feature unification flipping a sibling crate's behavior. (context: Building the kimetsu-remote crate (HTTP MCP server); its default embeddings feature broke 3 kimetsu-chat retrieval tests only under the full workspace test.)" + }, + { + "key": "bedrock-kimetsu-provider", + "text": "[tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env. Wire \"bedrock\" into BOTH pipeline.rs provider matches AND the distiller (normalize_distiller_provider + instantiation); the distiller is configured independently so agent-on-Bedrock + harvester-on-direct-Claude works for free. Sign and send the SAME payload bytes; test signing determinism with a fixed SystemTime. (context: Workstream A: adding AWS Bedrock as a provider for the agent + auto-harvester in v1.0.0.)" + }, + { + "key": "bridge-target-enum-seams", + "text": "[tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors. (context: Adding BridgeTarget::OpenClaw host to Kimetsu bridge.rs and main.rs in Workstream C)" + }, + { + "key": "pi-openclaw-extension-api", + "text": "[tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`. External commands use `pi.exec()` but `node:child_process` spawn also works. Pi has NO MCP so Kimetsu integrates via TS extension + SKILL.md only. (context: Implementing Pi host target for Kimetsu plugin install/status/uninstall system.)\n\nAlso: [tags: kimetsu host-integration pi openclaw bridge] When integrating Kimetsu with an external host agent (Pi, OpenClaw, etc.), VERIFY the host's real plugin/extension API against its actual repo before writing embedded assets — docs-from-memory are frequently wrong. Concretely corrected during v1.0: Pi uses a default-export factory `export default function(pi)` (not `defineExtension`) with lifecycle events `session_start`/`agent_end`/`session_shutdown`; OpenClaw plugin entry is `index.ts` via `definePluginEntry` from `openclaw/plugin-sdk/plugin-entry` + an `openclaw.plugin.json` manifest, with snake_case hook events `agent_turn_prepare`/`agent_end`/`session_end` (NOT colon-delimited). Always make the embedded hook shell-out a silent no-op if the `kimetsu` binary isn't on PATH so a wrong guess never breaks the host. (context: Adding Pi + OpenClaw as BridgeTarget hosts in v1.0.0; the inferred extension/plugin APIs from docs were wrong and had to be corrected against the real repos.)" + }, + { + "key": "aws-sigv4-bedrock-blocking", + "text": "[tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed. aws-smithy-runtime-api required as a companion to supply Identity. (context: Implementing BedrockProvider for Kimetsu with blocking reqwest + SigV4 signing, no tokio/aws-sdk)" + }, + { + "key": "gc-trace-env-guard-placement", + "text": "[tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site. (context: QQ4 — runs auto-GC on run creation. Env guard placement decision when wiring opportunistic GC into TraceWriter::create.)" + }, + { + "key": "init-project-git-boundary", + "text": "[tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain. (context: QQ3 — kimetsu setup integration test failed because init_project climbed git tree to real ~/.kimetsu instead of temp workspace)" + }, + { + "key": "clap-version-build-flavor", + "text": "[tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds. (context: QQ2: --version build flavor + plugin install self-check)" + }, + { + "key": "harbor-terminal-bench-subprocess-isolation", + "text": "[tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd). Worker re-derives auth internally from .env so the OAuth token never lands in argv; it writes {run,grade} JSON the parent reads back. One Harbor invocation per process always works (baseline-alone passed). (context: kbench multi-trial sweeps crashed on every trial after the 1st; diagnosed as Harbor/pyiceberg os.getcwd staleness on WSL2.)" + }, + { + "key": "sqlite-vacuum-wal-checkpoint", + "text": "[tags: rust sqlite vacuum rusqlite windows] When implementing SQLite VACUUM in rusqlite: VACUUM cannot run inside a transaction. rusqlite's Connection does not hold an implicit transaction, so `conn.execute_batch(\"VACUUM;\")` works directly. After VACUUM, run `PRAGMA wal_checkpoint(TRUNCATE);` before measuring file size — on Windows the WAL file can hold significant space that isn't reflected in the main db file until the checkpoint runs. (context: Implementing kimetsu brain compact (Q8) — SQLite VACUUM + WAL checkpoint for accurate post-compact file size.)" + }, + { + "key": "import-dedup-seen-ids", + "text": "[tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount — both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise. (context: Implementing brain export/import (Q5). First naive approach used a single `seen_ids` set local to the function; the dedup test caught it on the second-import assertion.)" + }, + { + "key": "toml-value-parse", + "text": "[tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table. (context: Implementing config get/set with toml::Value navigation; str.parse() failed with 'unexpected content' error on document strings.)" + }, + { + "key": "process-start-time-cross-platform", + "text": "[tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path). Keep the skew decision logic in a pure function `assess_mcp_skew(servers, binary_mtime, binary_path) -> Outcome` so it can be unit-tested without any live OS state. (context: Q3 — kimetsu doctor version-skew check for stale MCP server processes)" + }, + { + "key": "windows-update-process-locking", + "text": "[tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics — mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code. (context: Q2 — kimetsu update preflight for locked binary on Windows)" + }, + { + "key": "cfg-cross-platform-dead-code", + "text": "[tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform. (context: Adding parse_unix_ps to kimetsu-cli/src/process.rs — used only on Unix at runtime but needed on Windows for cross-platform unit tests.)" + }, + { + "key": "sqlite-busy-timeout-wal", + "text": "[tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch. Set the timeout before any transaction, not inside one — it is a connection-level property. (context: Kimetsu brain writer and reader processes sharing the same SQLite brain database.)" + }, + { + "key": "sqlite-wal-network-drive", + "text": "[tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db. Fallback: `PRAGMA journal_mode=DELETE;` is safe over SMB at the cost of lower concurrency. Detect network drives at startup with `GetFileAttributes` checking FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS or using `PathIsNetworkPath`. (context: Users running kimetsu with the brain database on a mapped network drive.)" + }, + { + "key": "sqlite-fts5-tokenizer", + "text": "[tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon. If you switch tokenizers on an existing FTS5 table, you MUST rebuild the shadow tables: `INSERT INTO tbl(tbl) VALUES('rebuild');` — a schema-only change leaves the inverted index unusable. The `porter` stemmer is available as `tokenize='porter unicode61'` but aggressively strips suffixes and hurts precision on technical terms. (context: Kimetsu brain FTS5 index tuning for Rust identifier retrieval.)" + }, + { + "key": "sqlite-page-size", + "text": "[tags: sqlite page_size performance rusqlite] SQLite's default page_size is 4096 bytes. For a write-heavy brain database with large BLOB payloads (embedding vectors), raising page_size to 16384 reduces fragmentation and improves sequential scan throughput. `PRAGMA page_size = 16384;` must be set BEFORE the first table is created — changing it on an existing database requires a VACUUM afterward to rebuild all pages. Verify it took effect with `PRAGMA page_size;` after VACUUM. rusqlite's `Connection::open` runs no implicit PRAGMA, so set this in the connection init path. (context: Tuning the kimetsu brain SQLite schema for embedding vector storage.)" + }, + { + "key": "sqlite-foreign-keys-default-off", + "text": "[tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting — every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing. Check your schema with `PRAGMA foreign_key_list(table_name);` and your current setting with `PRAGMA foreign_keys;`. rusqlite does not enable foreign keys automatically. (context: Kimetsu brain schema — memory_tags table has FK to memories table, discovered ON DELETE CASCADE wasn't firing.)" + }, + { + "key": "sqlite-json1-extract", + "text": "[tags: sqlite json1 json_extract rusqlite] SQLite's json1 extension (built in since 3.38.0) lets you index and query JSONB columns with `json_extract(col, '$.field')`. To create a partial index over a JSON field: `CREATE INDEX idx ON memories (json_extract(metadata, '$.scope')) WHERE json_extract(metadata, '$.scope') IS NOT NULL;`. Use `json_each` for array fields. On older SQLite builds (rusqlite links whatever the system provides), check for json1 with `SELECT json('{}');` — an error means it's absent. Always prefer column storage over JSON blobs for frequently queried fields. (context: Kimetsu brain querying metadata scopes without migrating a separate column.)" + }, + { + "key": "sqlite-prepared-stmt-cache", + "text": "[tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8). The cache key is the SQL string verbatim, so template strings with interpolated values defeat caching — use `?1, ?2` placeholders instead. Calling `prepare_cached` in a tight loop is effectively free after warmup. (context: Kimetsu brain high-throughput ingest path — replacing prepare() with prepare_cached() cut ingest time by ~30%.)" + }, + { + "key": "sqlite-partial-index", + "text": "[tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query — the planner uses the partial index only when the WHERE clause matches. Verify index usage with `EXPLAIN QUERY PLAN SELECT ...`. Partial indexes are not supported before SQLite 3.8.0; rusqlite's bundled SQLite is always current, but system SQLite on old Debian/Ubuntu may not be. (context: Optimizing kimetsu brain retrieval query over the active-memories subset.)" + }, + { + "key": "cargo-lockfile-drift", + "text": "[tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this — it errors on any lockfile diff. For library crates, `Cargo.lock` is normally gitignored, but for workspace roots with binary crates it should be committed. Use `cargo update --precise ` to pin a specific dep version without touching unrelated entries. (context: Kimetsu workspace lockfile drift after adding kimetsu-remote crate.)" + }, + { + "key": "cargo-build-script-rerun", + "text": "[tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory. If the build script generates code from a schema file, emit `rerun-if-changed=schema.json`. If there are NO inputs (e.g. the script only inspects env vars), emit `cargo:rerun-if-changed=` with an empty string to suppress re-runs entirely. Missing this directive is the most common cause of unexpectedly slow incremental builds. (context: kimetsu-cli build.rs for embedding version stamps.)" + }, + { + "key": "cargo-dev-dep-leak", + "text": "[tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates. Run `cargo tree --features ` to trace which crate activated an unexpected feature. (context: Kimetsu testing infra — a dev-dep was activating the embeddings feature in non-test builds.)" + }, + { + "key": "cargo-target-dir-sharing", + "text": "[tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps — use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination. (context: Kimetsu development on Windows with Windows Defender causing intermittent link failures.)" + }, + { + "key": "cargo-incremental-cache-corruption", + "text": "[tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase. Fix: `cargo clean` then rebuild. Adding `CARGO_INCREMENTAL=0` to CI matrices prevents this class of false failures. (context: Kimetsu development — spurious type mismatch errors after branch switches.)" + }, + { + "key": "cargo-profile-override", + "text": "[tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug. The downside: rebuild time increases for that crate. For overflow-checks, `overflow-checks = false` per package speeds up hot loops. Never disable overflow-checks in release for business-critical data-mutating code. `[profile.release] strip = \"debuginfo\"` reduces binary size with minimal impact on stack traces. (context: Kimetsu dev experience — embedding inference was 10x slower in debug builds.)" + }, + { + "key": "cargo-patch-section", + "text": "[tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace — including transitive deps — that depend on `my-crate`. Remove the patch before publishing. Using `[replace]` is deprecated since Cargo 0.47; always use `[patch]`. When patching a crate pinned via an exact version specifier, the patch must satisfy that exact version. Use `cargo tree` to confirm the patch is applied. (context: Kimetsu patching upstream rusqlite for a Windows-specific WAL fix.)" + }, + { + "key": "cargo-msrv", + "text": "[tags: cargo rust msrv edition compatibility] Set `rust-version` in each `Cargo.toml` to declare the minimum supported Rust version (MSRV). Cargo enforces this with `--check`: `cargo check` fails if the toolchain is older than `rust-version`. Keep MSRV as old as your oldest supported deployment target. When bumping MSRV, update the CI matrix and the workspace root. Common trap: a transitive dep bumps its MSRV, pulling yours up silently — check with `cargo msrv` (cargo-msrv crate) or `cargo tree -e features | grep msrv`. Edition 2021 requires Rust >= 1.56.0. (context: Kimetsu workspace MSRV policy — ensuring it runs on the LTS toolchain.)" + }, + { + "key": "windows-long-paths", + "text": "[tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe. On Windows 10 1607+ the LongPathsEnabled key is sufficient for most tools. `cargo build` itself works after the registry change; MSI installers may still fail on paths > 260 in the installer runtime. (context: Kimetsu CI on Windows Server 2019 — build failed with OS error 3 on deeply nested proc-macro paths.)" + }, + { + "key": "windows-file-locking-av", + "text": "[tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine. For CI, use GitHub-hosted Windows runners which don't have real-time AV. Alternatively, build to a different directory with `CARGO_TARGET_DIR=C:\\tmp\\target`. The error is non-deterministic — it only appears when AV scanning races with the link step. (context: Kimetsu development on Windows — intermittent linker errors.)" + }, + { + "key": "windows-unc-paths", + "text": "[tags: windows unc-paths rust std::fs] Windows UNC paths (`\\\\server\\share\\...`) are not supported by most Rust `std::fs` operations unless passed through the extended-length prefix `\\\\?\\UNC\\server\\share\\...`. `std::path::Path::new(\"\\\\\\\\server\\\\share\")` works for basic operations but breaks with `canonicalize()` which returns the verbatim prefix form. When walking directory trees that may start on UNC paths, use the `dunce` crate to strip the verbatim prefix before comparing or displaying paths. Never `cd` into a UNC path in a subprocess started with `std::process::Command` — the subprocess may not inherit it correctly on older Windows. (context: Kimetsu ingest walking paths on network-mounted project directories.)" + }, + { + "key": "windows-console-encoding", + "text": "[tags: windows console encoding utf8 rust] Windows console code page defaults to the system ANSI code page (usually CP1252 or CP932), not UTF-8. Rust's `println!` writes UTF-8 bytes which display as mojibake in a non-UTF-8 console. Fix at process startup: call `SetConsoleOutputCP(65001)` via `winapi` or `windows-sys`, or set `PYTHONUTF8=1`/`RUST_LOG` before launch. In PowerShell, `[Console]::OutputEncoding = [System.Text.Encoding]::UTF8` fixes the session. For binary piped output (MCP stdio protocol), write raw bytes — don't use the console code page. (context: Kimetsu MCP server — Unicode memory text was garbled on non-UTF8 Windows terminals.)" + }, + { + "key": "windows-junctions-vs-symlinks", + "text": "[tags: windows junctions symlinks rust std::fs] On Windows, directory junctions (NTFS reparse points) behave like symlinks for directory traversal but `std::fs::symlink_metadata` returns `FileType::is_symlink() = false` for junctions (only true for regular symlinks). Use `std::fs::read_link` — it succeeds for both junction and symlink. `walkdir` crate's `follow_links` follows both, but its `is_symlink()` method correctly reports only actual symlinks. Creating symlinks requires SeCreateSymbolicLinkPrivilege (admin or Developer Mode). Creating junctions requires no special privilege. Use junctions for internal tooling that doesn't need to cross volumes. (context: Kimetsu path handling for brain symlink detection on Windows.)" + }, + { + "key": "windows-exit-codes", + "text": "[tags: windows exit-codes rust process child] On Windows, process exit codes are 32-bit unsigned integers (DWORD). Rust's `ExitStatus::code()` returns `Option` — it's `None` if the process was killed by a signal (which Windows doesn't use; instead, TerminateProcess with a code). Conventional codes: 0=success, 1=generic error, 0xC0000005=access violation. Programs that call `std::process::exit(-1)` on Windows produce exit code 0xFFFFFFFF (4294967295), not -1. When checking for success in a subprocess chain, always check `status.success()` rather than `status.code() == Some(0)` to handle this portably. (context: Kimetsu update binary replacement — exit code handling.)" + }, + { + "key": "windows-registry-rust", + "text": "[tags: windows registry rust winreg read write] Reading and writing the Windows registry from Rust requires the `winreg` crate. Open a key with `RegKey::predef(HKEY_LOCAL_MACHINE).open_subkey_with_flags(path, KEY_READ)` — use `KEY_READ` for reads and `KEY_READ | KEY_WRITE` for writes (NOT `KEY_ALL_ACCESS`, which requires admin). To set a DWORD value: `key.set_value(\"LongPathsEnabled\", &1u32)`. Registry paths use backslash separators and are case-insensitive. Prefer reading env vars over registry for runtime config — registry reads are expensive (kernel transition) and inappropriate for hot paths. For kimetsu, registry access is limited to the `kimetsu doctor` check for long-path enablement. (context: Kimetsu doctor — checking LongPathsEnabled registry value on Windows.)" + }, + { + "key": "onnx-tokenizer-mismatch", + "text": "[tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly — specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings — cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo. Validate by checking a reference embedding against the HuggingFace Python output. (context: Kimetsu custom ONNX reranker loading — wrong tokenizer produced degraded retrieval.)" + }, + { + "key": "onnx-quantization-drift", + "text": "[tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals — cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case. (context: Kimetsu embedding model selection — evaluating jina-v2 int8 vs fp32.)" + }, + { + "key": "onnx-batch-padding", + "text": "[tags: onnx batch padding attention-mask embeddings] When running batch inference with an ONNX model, all inputs in the batch must be padded to the same sequence length. The `attention_mask` tensor marks which tokens are real (1) and which are padding (0). Failing to pass `attention_mask` causes the model to average-pool over padding tokens, producing systematically lower-norm embeddings. With ORT (ort crate), construct the mask as a 2-D i64 tensor `[batch, seq_len]` with 1s for real tokens and 0s for padding. For variable-length batches, pad to `max(lengths)` in the batch, not to `model.max_length`. (context: Kimetsu embedding batch inference with ORT — missing attention mask caused MRR degradation.)" + }, + { + "key": "onnx-model-cache-paths", + "text": "[tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use. In kimetsu, `KIMETSU_EMBEDDER_CACHE` overrides the path and is forwarded when spawning child bench processes — without forwarding it, each child re-downloads the model. (context: Kimetsu brain bench on CI — model cache path handling in child processes.)" + }, + { + "key": "onnx-cosine-vs-dot", + "text": "[tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing — double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g. E5, GTE with separate query/passage prefixes), the query and document encoders must use different prefix strings. Check the model card's `Similarity function` field. usearch/qdrant: prefer `MetricKind::Cos` over `Dot` for passage vectors that may not be perfectly normalized. (context: Kimetsu embedding storage — similarity metric selection.)" + }, + { + "key": "onnx-dim-mismatch", + "text": "[tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results — the ANN index shape mismatch isn't always caught at runtime. kimetsu detects this by storing `embedder_id` in the brain schema and refusing to query if the configured embedder differs from what was used at ingest time. Mitigation: re-ingest all memories with the new model, or keep per-memory vector dim metadata. (context: Kimetsu embedder migration — detecting dimension mismatch at startup.)" + }, + { + "key": "onnx-prefix-instructions", + "text": "[tags: onnx embeddings prefix instruction e5 query passage] E5 and Instructor family models require a text prefix on BOTH query and passage sides to produce meaningful similarities: query prefix `\"query: \"`, passage prefix `\"passage: \"`. Omitting the prefix can drop MRR by 10-15 percentage points on out-of-domain datasets. Check the model's README for the exact prefix string — it varies by model family. In kimetsu, the embedder abstraction has `query_prefix` and `passage_prefix` fields; FallbackEmbedder uses `\"\"` for both. jina-v2-base-code and bge-small use `\"\"` prefixes. (context: Kimetsu embedder trait design — prefix handling for E5/Instructor models.)" + }, + { + "key": "onnx-ort-threading", + "text": "[tags: onnx ort thread-pool parallelism cpu] ORT (ONNX Runtime) creates its own inter-op and intra-op thread pools. In a multi-process bench setup, each child inherits these pools and they compete for CPU cores. Set `SessionOptionsBuilder::with_intra_threads(1).with_inter_threads(1)` if you're running many parallel bench processes — this sacrifices per-inference throughput for lower contention. In a single-threaded embedding pipeline, 2-4 intra-op threads are better. For benchmarking, set `ORT_NUM_THREADS=1` via env var to get deterministic single-threaded latency numbers. (context: Kimetsu brain bench multi-process parallelism — ORT thread contention causing inconsistent latency.)" + }, + { + "key": "git-worktree-brain-isolation", + "text": "[tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root — if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain. Always set `KIMETSU_BRAIN_DIR` or use `git_init_boundary` in tests to prevent this. (context: Kimetsu development with git worktrees — test isolation.)" + }, + { + "key": "git-hooks-bypass", + "text": "[tags: git hooks bypass pre-commit skip] `git commit --no-verify` skips ALL hooks (pre-commit and commit-msg). Never use this in shared team repos where hooks enforce quality gates (lint, tests, memory harvest). Instead, fix the failing hook. If the hook itself is broken, fix the hook script. For emergency commits where hooks aren't relevant (e.g. updating a gitignore to untrack already-committed files), document the `--no-verify` use in the commit message. In CI, hooks run only if explicitly invoked — `git commit` in a CI pipeline with no hooks configured does nothing for quality enforcement. (context: Kimetsu pre-commit hook enforcing memory harvest.)" + }, + { + "key": "git-sparse-checkout", + "text": "[tags: git sparse-checkout partial-clone bandwidth] `git sparse-checkout init --cone` combined with `git clone --filter=blob:none` (partial clone) fetches only the commit graph and tree objects, not blobs. Individual blobs are fetched on demand when accessed. This cuts clone time for large repos from minutes to seconds. For kimetsu server-side ingest, use `git clone --depth 1 --filter=blob:none` for the initial checkout, then `git sparse-checkout set ` to limit the working tree to indexed directories. On `git fetch --depth 1 origin main` for refresh, blobs in the sparse set are updated lazily. (context: Kimetsu remote ingest — reducing bandwidth and disk usage for large repo checkouts.)" + }, + { + "key": "git-line-endings-windows", + "text": "[tags: git line-endings windows crlf autocrlf] On Windows, `core.autocrlf=true` (git's default for Windows installs) converts LF to CRLF on checkout and CRLF to LF on commit. This causes spurious diffs when files are edited on Windows then committed — the content is identical but the line endings differ in the index vs the working tree. Fix: set `core.autocrlf=false` and `.gitattributes` with `* text=auto eol=lf` for the repo. For Rust projects, all source files should be LF; only Windows batch scripts need CRLF. Warn: AV scanners that modify newly written files can re-introduce CRLF in files Rust writes. (context: Kimetsu CI — spurious diffs from Windows CRLF conversion.)" + }, + { + "key": "git-submodule-pinning", + "text": "[tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip — this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version. If a submodule is the kimetsu-bench repo inside the main repo, pin the bench SHA after validating the dataset change. Use `git diff HEAD -- bench` to see the pinned SHA change before committing. (context: Kimetsu bench as a git submodule of the main repo.)" + }, + { + "key": "git-reflog-rescue", + "text": "[tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone — they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only — remote reflog is not accessible via normal git commands. If you need the remote version, use `git fetch origin +refs/heads/main:refs/heads/main-backup` before a force push. In kimetsu bench development, always create a branch before destructive rebases. (context: Kimetsu bench dataset recovery after accidental hard reset.)" + }, + { + "key": "tokio-blocking-in-async", + "text": "[tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking — never call rusqlite directly from an async fn without spawn_blocking. fastembed inference is also blocking (ONNX Runtime is synchronous). The threshold: any operation taking more than 100 microseconds that can't be made async belongs in spawn_blocking. Ignoring this causes tail-latency spikes and request timeouts under load in kimetsu-remote. (context: Kimetsu remote server — SQLite and embedding calls from async handlers.)" + }, + { + "key": "tokio-runtime-in-tests", + "text": "[tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests. For sync test code that calls async, use `tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async { ... })`. Never call `block_on` from inside an async function. (context: Kimetsu remote integration tests — nested runtime panic.)" + }, + { + "key": "tokio-select-cancellation", + "text": "[tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded. For correctness, cancelled futures must be cancellation-safe: holding no partially committed state. `tokio::sync::watch::Receiver::changed()` is cancellation-safe; `tokio::sync::mpsc::Sender::send()` is NOT (the item is lost). In kimetsu shutdown, use a `CancellationToken` and `select!` branches that are all cancellation-safe. (context: Kimetsu remote graceful shutdown — race between incoming requests and shutdown signal.)" + }, + { + "key": "tokio-channel-backpressure", + "text": "[tags: tokio mpsc channel backpressure async rust] `tokio::sync::mpsc::channel(N)` with a bounded buffer provides backpressure: senders block when the buffer is full. This prevents unbounded memory growth but can cause sender tasks to stall. Choosing N: too small causes frequent backpressure (throughput drops); too large defeats the purpose. For kimetsu's harvest pipeline, N=16 was a good balance — the harvester is I/O bound (LLM call), producers are fast (hook callbacks). Prefer bounded channels over unbounded in production code. `tokio::sync::mpsc::unbounded_channel()` is a footgun for bursty producers. (context: Kimetsu auto-harvester pipeline — bounded vs unbounded channel selection.)" + }, + { + "key": "tokio-spawn-blocking", + "text": "[tags: tokio spawn_blocking thread-pool rust blocking] `tokio::task::spawn_blocking` places work on a dedicated blocking thread pool (default up to 512 threads, configurable via `Builder::max_blocking_threads`). Each call creates or reuses a thread — there's no true pooling, threads may be created on demand. For many short-duration blocking calls (e.g. per-query SQLite reads), thread creation overhead may dominate. Prefer batching: collect N queries, then one `spawn_blocking` to run them all. Alternatively, keep a persistent blocking task that reads from an mpsc channel. Profile with `tokio-console` if you suspect spawn_blocking overhead. (context: Kimetsu retrieval server — per-query spawn_blocking was adding ~0.3ms overhead.)" + }, + { + "key": "tokio-shutdown-ordering", + "text": "[tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries — the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks. `axum::Server::with_graceful_shutdown` handles steps 1-2; you must handle 3-5 manually. (context: Kimetsu remote server graceful shutdown implementation.)" + }, + { + "key": "http-connection-pooling", + "text": "[tags: http reqwest connection-pool keep-alive rust] reqwest's `Client` holds a connection pool; always create ONE `Client` instance and clone it for each handler — cloning is cheap (Arc under the hood). Creating a `Client::new()` per request defeats connection pooling and causes TCP connection exhaustion under load. The default pool settings: max_idle_per_host=usize::MAX (unbounded), idle_timeout=90s. For a kimetsu outbound client (LLM provider), set `pool_max_idle_per_host(5)` to limit idle connections. On Windows, the underlying hyper+winapi stack may not reuse connections as aggressively as on Linux — set `connection_verbose(true)` on the builder to confirm reuse. (context: Kimetsu provider HTTP client — connection pooling best practices.)" + }, + { + "key": "http-timeout-layering", + "text": "[tags: http reqwest timeout connect read total rust] reqwest has three distinct timeout knobs: `connect_timeout`, `read_timeout`, and `timeout` (total). They compose: if all three are set, the request fails at whichever fires first. For LLM API calls with streaming responses, `read_timeout` must be larger than the slowest expected token (often 30-60s) while `connect_timeout` can be tight (3-5s). `timeout` should be your SLA ceiling. If you set only `timeout`, a slow connect eats into the overall budget. For kimetsu-remote, set both `connect_timeout(5s)` and `timeout(120s)` — the LLM call is the bottleneck. (context: Kimetsu provider timeouts — request timing out during streaming.)" + }, + { + "key": "http-retry-idempotency", + "text": "[tags: http retry idempotency post put reqwest] Only retry idempotent requests automatically. GET, HEAD, PUT, DELETE are idempotent. POST is NOT — retrying a POST may create duplicate resources. For LLM API calls (POST), implement retry with idempotency keys: include a stable `X-Idempotency-Key: ` header; the provider deduplicates. For transient 429 (rate limit) responses, back off with jitter: `min(base * 2^attempt, cap) + rand(0, base)`. For 5xx, retry at most 3 times. Never retry on 4xx (except 429). In kimetsu, retry logic lives in the provider layer, not the distiller. (context: Kimetsu LLM provider retry strategy.)" + }, + { + "key": "http-tls-roots", + "text": "[tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle — the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle. Alternatively, add the custom root with `add_root_certificate`. On Linux, the system CA bundle is at `/etc/ssl/certs/ca-certificates.crt`; on Windows it's in the Windows Certificate Store. (context: Kimetsu on a corporate Windows machine with a custom proxy CA.)" + }, + { + "key": "http-streaming-bodies", + "text": "[tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding — a chunk may split across frame boundaries. In kimetsu's proxy path, accumulate bytes until `\n\n` (SSE frame delimiter) before parsing the JSON data field. Never assume one `.chunk()` call = one SSE event. (context: Kimetsu remote proxy — streaming LLM responses to the client.)" + }, + { + "key": "http-proxy-env", + "text": "[tags: http proxy environment reqwest rust corporate] reqwest respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` environment variables by default (with `default-tls` or `rustls-tls`). In a corporate network, these may redirect traffic through an intercepting proxy that breaks mTLS or adds latency. To disable proxy usage entirely: `reqwest::ClientBuilder::no_proxy()`. On Windows, reqwest does NOT use the system proxy settings (IE/WinInet) — you must set env vars explicitly. `NO_PROXY=127.0.0.1,localhost` prevents proxying loopback traffic (important for kimetsu-remote local dev). (context: Kimetsu provider calls failing behind corporate proxy on Windows.)" + }, + { + "key": "testing-snapshot-churn", + "text": "[tags: testing snapshot insta assert churn rust] Snapshot tests (e.g. with the `insta` crate) fail whenever the output changes, even for intended changes. In CI, they fail loudly; locally, `cargo insta review` walks you through accepting or rejecting changes. Snapshot churn becomes a problem when output includes timestamps, process IDs, or randomly-ordered maps. Redact these before snapshotting: use `insta::with_settings!({redactions: [\".timestamp\" => \"[TIMESTAMP]\"]})`. For JSON output, sort maps and arrays before comparing. Keep snapshot files in `src/snapshots/` and always commit them — an untracked snapshot file causes the next CI run to fail with a different error than expected. (context: Kimetsu CLI output snapshot tests — reducing churn.)" + }, + { + "key": "testing-temp-dirs-ci", + "text": "[tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure. On Windows, `env::temp_dir()` returns `C:\\Users\\\\AppData\\Local\\Temp` — ensure the test binary has write permissions there. Avoid using the workspace root as a temp dir — tests should never write to the source tree. (context: Kimetsu test infrastructure — temp directory discipline.)" + }, + { + "key": "testing-time-dependent-flakes", + "text": "[tags: testing time flaky clock mock rust] Tests that depend on wall-clock time are inherently flaky under load (slow CI runners, GC pauses). Abstract time behind a trait (`Clock: Fn() -> SystemTime`) injected at construction, and supply a fake in tests. For tests checking that something happened \"within N seconds\", use a generous multiple of the expected duration (10x is not unreasonable for CI). `std::thread::sleep` in tests is a smell — prefer channel synchronization or a condvar instead of timing-based waits. If you must use sleep, set `KIMETSU_TEST_TIMEOUT_SCALE` to stretch timeouts in slow environments. (context: Kimetsu GC and TTL tests — time-dependent flakes on loaded CI.)" + }, + { + "key": "testing-property-tests", + "text": "[tags: testing property-based proptest quickcheck rust] Property-based tests (proptest, quickcheck) find edge cases that example-based tests miss. For kimetsu's memory text normalization, proptest found that zero-width joiner characters and right-to-left marks caused hash collisions. Run proptest with `PROPTEST_CASES=10000` in CI for thorough coverage. Shrinking: when proptest finds a failure, it automatically shrinks the input to the minimal failing case — read the `Minimized failure` output, not the original random input. Use `prop_assume!` to skip inputs that violate preconditions rather than `if/return`. (context: Kimetsu brain text normalization — property test for dedup hash stability.)" + }, + { + "key": "testing-serial-vs-parallel", + "text": "[tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`). `cargo nextest` runs each test in a separate process by default, avoiding the problem entirely at the cost of longer startup time. For kimetsu, prefer nextest in CI and accept that `test_env_lock` exists only for `cargo test` compatibility. (context: Kimetsu test suite — env-var mutation in parallel tests.)" + }, + { + "key": "testing-fixture-drift", + "text": "[tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code. For kimetsu, `EvalFixture::from_memories(memories)` constructs a dataset from the exported format — use it in tests instead of hardcoded JSON. Tag fixture files with the schema version they were generated against in a comment. (context: Kimetsu eval fixture drift after schema migration.)" + }, + { + "key": "mcp-stdout-protocol", + "text": "[tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr. `println!` in the request handler is forbidden. Use `eprintln!` or `tracing` with a stderr subscriber. In tests of the MCP server, capture stdout as bytes and validate it parses as JSON-Lines. When debugging, set `KIMETSU_LOG=debug` which writes to stderr only. (context: Kimetsu MCP server stdout protocol hygiene.)" + }, + { + "key": "mcp-tool-timeouts", + "text": "[tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking — in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize — keep it in a process-global `OnceLock`). The reranker adds another 200-800ms; jina-tiny is the fastest. If tool calls are still slow, log the per-stage latency with `tracing::info!` at DEBUG level and profile under load. (context: Kimetsu MCP tool latency optimization.)" + }, + { + "key": "mcp-env-propagation", + "text": "[tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment — changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate. For kimetsu hooks (pre-commit, post-commit), the hook script inherits the shell's env at hook invocation time, not the server's. If `KIMETSU_BRAIN_DIR` needs to vary per project, set it in the project's `.env` file and source it in the hook script. (context: Kimetsu env propagation from hooks to MCP server.)" + }, + { + "key": "mcp-schema-validation", + "text": "[tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array — omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error. Use `serde(default)` for optional fields. When adding a new tool, check the schema in the tools/list response manually by piping a JSON-RPC tools/list request to `./kimetsu mcp`. (context: Kimetsu MCP tool schema — required field validation.)" + }, + { + "key": "mcp-tool-naming", + "text": "[tags: mcp tool naming convention kimetsu] MCP tool names must be valid identifiers for all host agents. Claude Code restricts tool names to `[a-zA-Z0-9_-]` and max 64 chars. Use `snake_case` (kimetsu_brain_context, kimetsu_brain_record) — hyphen is technically allowed but some hosts reject it. Avoid dots (not allowed). Namespace with a prefix (`kimetsu_brain_`) to prevent collisions with other MCP servers. When a tool name changes, update ALL host config files (`.mcp.json`, `openclaw.json`, skill markdown) — mismatched names cause silent failures where the host skips the tool. (context: Kimetsu MCP tool naming convention enforcement.)" + }, + { + "key": "mcp-transcript-paths", + "text": "[tags: mcp transcript paths kimetsu hooks runs] kimetsu writes run transcripts to `/.kimetsu/runs//`. The post-session hook reads the latest run's transcript to trigger memory harvest. On Windows, the path uses backslashes internally but the MCP JSON must use forward slashes or the host may reject path-type arguments. `std::path::Path::display()` produces backslashes on Windows — use `.to_string_lossy().replace('\\\\', \"/\")` when serializing paths for MCP protocol. The transcript path is included in the `kimetsu_brain_context` response under the `run_dir` field for the distiller's reference. (context: Kimetsu transcript path handling in MCP responses on Windows.)" + }, + { + "key": "aws-credentials-chain", + "text": "[tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually. On Windows, `~/.aws` is `%USERPROFILE%\\.aws` — `std::env::var(\"USERPROFILE\")` to get the path since `~` expansion is shell-level. (context: Kimetsu Bedrock provider credential resolution.)" + }, + { + "key": "aws-region-resolution", + "text": "[tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time. For cross-region inference (e.g. us-west-2 for Claude Opus), set `AWS_REGION=us-west-2`; do NOT rely on the Bedrock endpoint prefix being region-agnostic. (context: Kimetsu Bedrock provider region configuration.)" + }, + { + "key": "aws-retry-throttling", + "text": "[tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with ±25% jitter. Do NOT retry `ValidationException` or `AccessDeniedException` — these are permanent errors. `ModelStreamErrorException` during streaming may be retryable. Log the `x-amzn-requestid` header from failed responses for AWS support debugging. (context: Kimetsu Bedrock provider retry logic.)" + }, + { + "key": "aws-presigned-urls", + "text": "[tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time — clock skew > 15 minutes causes `RequestTimeTooSkewed`. kimetsu could use presigned URLs to serve brain exports from S3 without exposing credentials to the client. (context: Kimetsu potential S3 export feature — presigned URL generation.)" + }, + { + "key": "aws-instance-metadata", + "text": "[tags: aws imds instance-metadata ec2 token] The AWS Instance Metadata Service v2 (IMDSv2) requires a session token: PUT `http://169.254.169.254/latest/api/token` with `X-aws-ec2-metadata-token-ttl-seconds: 21600` to get a token, then GET metadata with `X-aws-ec2-metadata-token: `. IMDSv1 (no token) is disabled on hardened instances. The metadata endpoint is only reachable from within EC2 — a connection timeout means you're not on EC2. Set a short connect timeout (200ms) when probing for the metadata service to avoid slow startup on non-EC2 hosts. (context: Kimetsu Bedrock provider — EC2 instance role credential fallback.)" + }, + { + "key": "ci-cache-keys", + "text": "[tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key — macOS and Windows have incompatible artifact formats. Separate the registry cache from the build cache: the registry (downloaded crates) changes rarely, the build cache changes every push. Bust the build cache on major dependency changes by adding a manual cache version suffix to the key. (context: Kimetsu CI — cache invalidation strategy.)" + }, + { + "key": "ci-matrix-explosion", + "text": "[tags: ci github-actions matrix jobs resources] A CI matrix combining OS (3) x Rust toolchain (3) x features (2) = 18 jobs. Each spawns a runner; at $0.008/min for Ubuntu and $0.016/min for Windows, a 10-minute build costs $2.40 per push. Reduce: test the full matrix only on PRs to main; on feature branches, test only Linux+stable. Use `fail-fast: false` to see all failures, not just the first. Combine related checks (clippy + test) in one job when they share build artifacts. For Windows-specific tests, run only the OS-specific job to reduce cost. (context: Kimetsu CI matrix cost optimization.)" + }, + { + "key": "ci-secrets-masking", + "text": "[tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output — but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable. Never reconstruct secrets from parts in step output. For kimetsu bench `--remote` CI runs, `KIMETSU_REMOTE_TOKEN` must be in the repository secrets, not in the workflow YAML. Use `${{ secrets.KIMETSU_REMOTE_TOKEN }}` in env — never `echo ${{ secrets.KIMETSU_REMOTE_TOKEN }}` in a run step. (context: Kimetsu CI remote benchmark — token handling.)" + }, + { + "key": "ci-artifact-retention", + "text": "[tags: ci github-actions artifacts retention benchmark] GitHub Actions artifacts are retained for 90 days (default). For benchmark results, use `actions/upload-artifact` with `retention-days: 365` for long-term tracking. The free tier has 500MB storage — per-combo JSON files from kimetsu bench (each ~60KB) add up fast if you upload them on every push. Upload only the summary.md. For regression detection, compare the current run's MRR against the artifact from the last green main build — fetch it with the `actions/download-artifact` action. (context: Kimetsu CI benchmark result tracking.)" + }, + { + "key": "ci-flaky-quarantine", + "text": "[tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal — a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output. NEVER let a flaky test gate the merge queue. For kimetsu timing-based tests (`test_gc_old_runs_deletes_ancient`), apply `#[cfg_attr(ci, ignore)]` and run only in a dedicated slow-CI job. (context: Kimetsu CI flaky test policy.)" + }, + { + "key": "kimetsu-daemon-lifecycle", + "text": "[tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required. The server PID is not stored anywhere; use `kimetsu doctor` to enumerate running MCP server processes via OS APIs. On Windows, the server binary may be locked by AV after first launch — `kimetsu update` must stop all running server processes before replacing the binary. (context: Kimetsu daemon lifecycle — process management for updates.)" + }, + { + "key": "kimetsu-capsule-budgets", + "text": "[tags: kimetsu capsule tokens budget retrieval] kimetsu retrieval enforces a token budget per capsule type: memory capsules are capped at 6000 tokens total (across all retrieved memories), file capsules at 3000 tokens. When a memory is large and would exceed the budget, it is truncated at a sentence boundary. The budget is enforced AFTER reranking — reranking may reorder results so that a truncated high-ranked memory displaces a full lower-ranked one. `noise_caps` in the bench output counts capsules that scored below the noise floor — they consume budget without contributing signal. Lower noise_caps = tighter retrieval. (context: Kimetsu capsule budget enforcement and noise floor interaction.)" + }, + { + "key": "kimetsu-memory-scopes", + "text": "[tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available — if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope. The `kimetsu_brain_record` MCP tool inherits the scope from the server's launch context. When running kimetsu-remote, all memories are project-scoped to the registered repo-id. (context: Kimetsu memory scope system — project vs user isolation.)" + }, + { + "key": "kimetsu-distiller-config", + "text": "[tags: kimetsu distiller harvest config provider] The kimetsu distiller (auto-harvester) uses a SEPARATE provider configuration from the main agent: `distiller.provider`, `distiller.model`, `distiller.api_key`. This allows running the agent on an expensive model (Claude Opus) while harvesting with a cheap model (Claude Haiku). If `distiller.provider` is not set, it inherits `provider`. The distiller runs as a background task triggered by the post-session hook; it reads the session transcript and emits `kimetsu_brain_record` calls. Distiller timeouts are longer (300s) than normal tool calls (60s) because transcript processing can be slow. (context: Kimetsu distiller provider configuration — agent vs harvester model separation.)" + }, + { + "key": "kimetsu-proactive-hooks", + "text": "[tags: kimetsu proactive hooks context injection] kimetsu's proactive context injection runs before each agent turn (pre-turn hook) and injects relevant memories into the system prompt prefix. The hook invocation adds latency to the first token: embedding inference + vector search + reranking + context formatting. On a cold start, this can be 1-3 seconds. The hook is optional — disable with `KIMETSU_PROACTIVE=0`. The semantic floor (min cosine similarity) filters noise capsules before injection; setting the floor too low injects irrelevant memories and wastes context window tokens. The proactive hook does NOT trigger the distiller — that runs post-session only. (context: Kimetsu proactive context injection — latency and floor tuning.)" + }, + { + "key": "kimetsu-write-tools-gate", + "text": "[tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level — disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients. The feature was introduced to prevent malicious prompts from poisoning the brain. (context: Kimetsu write-tools gate — config-driven security for remote deployments.)" + }, + { + "key": "kimetsu-query-stemming", + "text": "[tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression. The lexical floor (`min_lexical_coverage`) requires at least N stemmed query tokens to match in any retrieved document — this prevents high-semantic-score but lexically-unrelated documents from dominating. Stemming is applied only when the query has >= 3 tokens; short queries skip it. (context: Kimetsu retrieval — query-side stemming implementation.)" + }, + { + "key": "kimetsu-rerank-pool", + "text": "[tags: kimetsu reranker pool size ann retrieval] kimetsu's retrieval pipeline: ANN (approximate nearest neighbor) retrieves a pool of candidates, then the reranker reorders them, then the top-K are returned. The pool size (default 6 for production, 12 in bench) controls the recall-latency tradeoff: larger pool = higher recall = more reranker calls = more latency. For the jina-tiny reranker, pool 12 adds ~80ms vs pool 6. The bench uses pool 12 to maximize measurable recall differences between rerankers; production uses pool 6 for latency. Increasing pool size beyond 20 has diminishing recall returns on corpora < 1000 memories. (context: Kimetsu ANN pool size tuning for the retrieval benchmark.)" + }, + { + "key": "kimetsu-bench-remote-embedder-singleton", + "text": "[tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval. Workaround: run ONE `--embedders` value per invocation and kill the remote process between runs. The local bench path is not affected (each combo is process-isolated via `--single` child spawn). (context: Kimetsu brain bench --remote known issue — multi-embedder contamination.)" + }, + { + "key": "kimetsu-eval-fixture-shape", + "text": "[tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` — a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases). Keys must be unique across the dataset. The bench currently does not validate keys at load — it fails later with an `unwrap()` on a missing HashMap entry. (context: Kimetsu bench dataset shape and validation.)" + }, + { + "key": "kimetsu-mrr-metric", + "text": "[tags: kimetsu bench mrr recall metrics evaluation] kimetsu bench reports MRR (Mean Reciprocal Rank) and Recall@K. MRR is 1/rank_of_first_relevant_result, averaged across cases; it penalizes models that rank the correct answer 2nd or 3rd. Recall@K is the fraction of cases where at least one relevant answer appears in the top K. For multi-answer cases, recall@K considers a case satisfied if ANY relevant key appears in top K. MRR is the primary metric for knowledge retrieval because users read the first result first. A 0.01 MRR difference on a 100-case dataset corresponds to about 1 case changing from rank-2 to rank-1. Noise of ~2-3 cases is expected run-to-run. (context: Kimetsu benchmark metric interpretation.)" + } + ], + "queries": [ + { + "query": "test_env_lock inside with_user_brain_disabled deadlock", + "relevant": [ + "mutex-deadlock-user-brain-disabled" + ] + }, + { + "query": "why does my test hang after calling with_user_brain_disabled when I also lock test_env_lock?", + "relevant": [ + "mutex-deadlock-user-brain-disabled" + ] + }, + { + "query": "ingest_repo_at_root brain_root files_root kimetsu remote", + "relevant": [ + "remote-ingest-split-roots" + ] + }, + { + "query": "why does the remote server index the wrong directory when I run kimetsu brain ingest?", + "relevant": [ + "remote-ingest-split-roots" + ] + }, + { + "query": "kimetsu plugin install --remote mcp.json authorization bearer token", + "relevant": [ + "remote-mcp-host-wiring" + ] + }, + { + "query": "how do I wire a remote kimetsu brain into Claude Code without storing the token in the config file?", + "relevant": [ + "remote-mcp-host-wiring" + ] + }, + { + "query": "cargo feature unification kimetsu-brain embeddings fastembed test failure", + "relevant": [ + "cargo-feature-unification-embeddings" + ] + }, + { + "query": "my integration tests pass in isolation but break when I run cargo test --workspace — embedder changed?", + "relevant": [ + "cargo-feature-unification-embeddings" + ] + }, + { + "query": "build_anthropic_body bedrock-2023-05-31 InvokeModel blocking reqwest", + "relevant": [ + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ] + }, + { + "query": "how do I add AWS Bedrock as a model provider in Kimetsu without pulling in the aws-sdk?", + "relevant": [ + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ] + }, + { + "query": "BridgeTarget enum seams plugin_install_inner plugin_status_inner resolve_setup_hosts", + "relevant": [ + "bridge-target-enum-seams" + ] + }, + { + "query": "I added a new host to the bridge enum but cargo gives me compile errors in five different match arms — what did I miss?", + "relevant": [ + "bridge-target-enum-seams" + ] + }, + { + "query": "Pi extension factory defineExtension agent_end session_shutdown kimetsu.ts", + "relevant": [ + "pi-openclaw-extension-api" + ] + }, + { + "query": "how does Pi (earendil-works/pi) load plugins and what lifecycle hooks does it expose?", + "relevant": [ + "pi-openclaw-extension-api" + ] + }, + { + "query": "aws-sigv4 SigningParams apply_to_request_http1x reqwest sign-http", + "relevant": [ + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider" + ] + }, + { + "query": "how do I sign a Bedrock InvokeModel request with aws-sigv4 in blocking Rust?", + "relevant": [ + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider" + ] + }, + { + "query": "KIMETSU_RUNS_GC env opt-out TraceWriter create gc_old_runs caller", + "relevant": [ + "gc-trace-env-guard-placement" + ] + }, + { + "query": "where should I put the KIMETSU_RUNS_GC=0 guard — inside the GC function or at the call site?", + "relevant": [ + "gc-trace-env-guard-placement" + ] + }, + { + "query": "git_init_boundary ProjectPaths::discover temp dir user brain isolation", + "relevant": [ + "init-project-git-boundary" + ] + }, + { + "query": "my test calls init_project but it writes to the real ~/.kimetsu instead of the temp folder — why?", + "relevant": [ + "init-project-git-boundary" + ] + }, + { + "query": "clap command version KIMETSU_VERSION_DISPLAY cfg feature embeddings", + "relevant": [ + "clap-version-build-flavor" + ] + }, + { + "query": "how do I show the build flavor (lean vs embeddings) in the kimetsu --version output?", + "relevant": [ + "clap-version-build-flavor" + ] + }, + { + "query": "Harbor pyiceberg os.getcwd stale WSL2 DrvFs worker-result subprocess re-exec", + "relevant": [ + "harbor-terminal-bench-subprocess-isolation" + ] + }, + { + "query": "why does my kbench sweep crash after the first trial with 'result.json missing' on WSL2?", + "relevant": [ + "harbor-terminal-bench-subprocess-isolation" + ] + }, + { + "query": "rusqlite VACUUM transaction WAL checkpoint wal_checkpoint TRUNCATE", + "relevant": [ + "sqlite-vacuum-wal-checkpoint" + ] + }, + { + "query": "my SQLite VACUUM reports the file shrank but the disk usage stayed the same — Windows WAL?", + "relevant": [ + "sqlite-vacuum-wal-checkpoint" + ] + }, + { + "query": "add_memory import dedup seen_ids snapshot pre-existing active memory IDs", + "relevant": [ + "import-dedup-seen-ids" + ] + }, + { + "query": "brain import re-imports the same JSON file but the deduplication counter is wrong — why?", + "relevant": [ + "import-dedup-seen-ids" + ] + }, + { + "query": "toml::from_str Value parse document unexpected content str.parse", + "relevant": [ + "toml-value-parse" + ] + }, + { + "query": "how do I parse a TOML configuration file into a toml::Value in toml 0.9?", + "relevant": [ + "toml-value-parse" + ] + }, + { + "query": "CIM CreationDate DMTF WMI ps etimes started_at assess_mcp_skew", + "relevant": [ + "process-start-time-cross-platform" + ] + }, + { + "query": "how do I read a process start time on both Windows and Linux in pure Rust?", + "relevant": [ + "process-start-time-cross-platform" + ] + }, + { + "query": "processes_locking_target decide_preflight_action BufRead Write update.rs", + "relevant": [ + "windows-update-process-locking" + ] + }, + { + "query": "how should I reuse the existing process enumerator in the update preflight check to avoid a second PowerShell query?", + "relevant": [ + "windows-update-process-locking" + ] + }, + { + "query": "cfg_attr windows allow dead_code parse_unix_ps cross-platform tests", + "relevant": [ + "cfg-cross-platform-dead-code" + ] + }, + { + "query": "how do I keep a function that is only called on Unix from triggering dead_code warnings on Windows?", + "relevant": [ + "cfg-cross-platform-dead-code" + ] + }, + { + "query": "deadlocking a Rust mutex in integration tests", + "relevant": [ + "mutex-deadlock-user-brain-disabled" + ] + }, + { + "query": "benchmarking retrieval quality across embedders", + "relevant": [] + }, + { + "query": "process memory working set RSS peak measurement Windows", + "relevant": [ + "process-start-time-cross-platform", + "windows-update-process-locking" + ] + }, + { + "query": "cloning a git repository server-side into a managed checkout", + "relevant": [ + "remote-ingest-split-roots" + ] + }, + { + "query": "SigV4 signing HTTP requests in Rust", + "relevant": [ + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider" + ] + }, + { + "query": "cargo test --workspace feature flag changes broke my unit tests", + "relevant": [ + "cargo-feature-unification-embeddings" + ] + }, + { + "query": "how do I make pasta carbonara?", + "relevant": [] + }, + { + "query": "what is the offside rule in football?", + "relevant": [] + }, + { + "query": "best way to train for a half marathon", + "relevant": [] + }, + { + "query": "my test passes when I run it alone but fails under cargo test --workspace", + "relevant": [ + "cargo-feature-unification-embeddings" + ] + }, + { + "query": "all the project tests started hanging forever after I added my new test", + "relevant": [ + "mutex-deadlock-user-brain-disabled" + ] + }, + { + "query": "my integration test silently wrote memories into my real home brain instead of the temp workspace", + "relevant": [ + "init-project-git-boundary" + ] + }, + { + "query": "where should the env-var opt-out check live for a cleanup feature triggered from a hot code path", + "relevant": [ + "gc-trace-env-guard-placement" + ] + }, + { + "query": "the brain database file stays huge on Windows even after deleting most rows", + "relevant": [ + "sqlite-vacuum-wal-checkpoint" + ] + }, + { + "query": "re-importing the same exported memories file counts them as new instead of deduplicated", + "relevant": [ + "import-dedup-seen-ids" + ] + }, + { + "query": "a helper function only called on Unix at runtime fails the dead-code lint on the Windows build", + "relevant": [ + "cfg-cross-platform-dead-code" + ] + }, + { + "query": "the second Terminal-Bench trial always crashes even though the first one passes", + "relevant": [ + "harbor-terminal-bench-subprocess-isolation" + ] + }, + { + "query": "how does doctor tell a running MCP server process is older than the kimetsu binary on disk", + "relevant": [ + "process-start-time-cross-platform" + ] + }, + { + "query": "the self-update preflight needs the list of running kimetsu processes without re-running the OS query", + "relevant": [ + "windows-update-process-locking" + ] + }, + { + "query": "parsing the WMI DMTF CreationDate timestamp into epoch seconds without extra crates", + "relevant": [ + "process-start-time-cross-platform" + ] + }, + { + "query": "calling Bedrock InvokeModel from blocking reqwest without the aws sdk", + "relevant": [ + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider" + ] + }, + { + "query": "how do I rotate the encryption key protecting the kimetsu brain database", + "relevant": [] + }, + { + "query": "which tokio runtime worker-thread settings does the kimetsu MCP server use", + "relevant": [] + }, + { + "query": "how does kimetsu sync memories between two machines over the network", + "relevant": [] + }, + { + "query": "recovering a corrupted usearch ANN index after a power loss", + "relevant": [] + }, + { + "query": "what postgres schema should I use to store kimetsu memories", + "relevant": [] + }, + { + "query": "the whole CI job just froze forever with no failure output after my latest test PR", + "relevant": [ + "mutex-deadlock-user-brain-disabled" + ] + }, + { + "query": "running the test suite left junk state in my home directory", + "relevant": [ + "init-project-git-boundary" + ] + }, + { + "query": "I deleted a bunch of old rows but the file on disk is still the same size", + "relevant": [ + "sqlite-vacuum-wal-checkpoint" + ] + }, + { + "query": "adding one new crate quietly changed how the whole workspace builds", + "relevant": [ + "cargo-feature-unification-embeddings" + ] + }, + { + "query": "we cannot pull an async runtime into the agent just to talk to AWS", + "relevant": [ + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider" + ] + }, + { + "query": "users should be able to tell which build variant they installed from the version output", + "relevant": [ + "clap-version-build-flavor" + ] + }, + { + "query": "what gotchas should I expect writing process-inspection code that works on both Windows and Unix?", + "relevant": [ + "process-start-time-cross-platform", + "cfg-cross-platform-dead-code", + "windows-update-process-locking" + ] + }, + { + "query": "why might tests behave differently on my machine than in the full CI run?", + "relevant": [ + "cargo-feature-unification-embeddings", + "mutex-deadlock-user-brain-disabled", + "init-project-git-boundary" + ] + }, + { + "query": "what do I need to know before wiring kimetsu into a brand new host agent?", + "relevant": [ + "bridge-target-enum-seams", + "pi-openclaw-extension-api", + "remote-mcp-host-wiring" + ] + }, + { + "query": "tell me everything relevant to running kimetsu against AWS", + "relevant": [ + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ] + }, + { + "query": "ingesting a cloned repo when the brain lives under a different root", + "relevant": [ + "remote-ingest-split-roots" + ] + }, + { + "query": "streamable-http transport entry for openclaw.json with a bearer token", + "relevant": [ + "remote-mcp-host-wiring" + ] + }, + { + "query": "serializing ingests with a tokio mutex to avoid checkout races", + "relevant": [ + "remote-ingest-split-roots" + ] + }, + { + "query": "percent-encoding the colon in the bedrock model id for the invoke URL", + "relevant": [ + "bedrock-kimetsu-provider" + ] + }, + { + "query": "deduplicating re-imported memories against pre-existing ids", + "relevant": [ + "import-dedup-seen-ids" + ] + }, + { + "query": "parsing DMTF datetimes", + "relevant": [ + "process-start-time-cross-platform" + ] + }, + { + "query": "how should install derive a stable identifier from the git remote URL?", + "relevant": [ + "remote-mcp-host-wiring" + ] + }, + { + "query": "the secret token must not end up written into the host config file", + "relevant": [ + "remote-mcp-host-wiring" + ] + }, + { + "query": "keep the cleanup logic unit-testable without touching environment variables", + "relevant": [ + "gc-trace-env-guard-placement" + ] + }, + { + "query": "how do we stop the server from cloning arbitrary repos clients request?", + "relevant": [ + "remote-ingest-split-roots" + ] + }, + { + "query": "make sure a wrong guess about a host plugin API never breaks that host", + "relevant": [ + "pi-openclaw-extension-api" + ] + }, + { + "query": "which wire-format trick lets us reuse the existing Anthropic request builder for AWS?", + "relevant": [ + "bedrock-kimetsu-provider" + ] + }, + { + "query": "the self-update froze because something was still holding the executable", + "relevant": [ + "windows-update-process-locking" + ] + }, + { + "query": "our notes about the extension API turned out wrong once we read the actual repo", + "relevant": [ + "pi-openclaw-extension-api" + ] + }, + { + "query": "half the benchmark trials die right after the first one finishes", + "relevant": [ + "harbor-terminal-bench-subprocess-isolation" + ] + }, + { + "query": "I need this parser visible to tests on every OS even though only one OS calls it", + "relevant": [ + "cfg-cross-platform-dead-code" + ] + }, + { + "query": "the config file content refuses to parse even though the TOML looks valid", + "relevant": [ + "toml-value-parse" + ] + }, + { + "query": "the remote server must refresh its checkout before answering file queries", + "relevant": [ + "remote-ingest-split-roots" + ] + }, + { + "query": "tests must not climb to a parent git repository when resolving project paths", + "relevant": [ + "init-project-git-boundary" + ] + }, + { + "query": "how do I test request signing deterministically when timestamps change every run?", + "relevant": [ + "bedrock-kimetsu-provider" + ] + }, + { + "query": "adding a new variant to the host target enum - which places will I forget to update?", + "relevant": [ + "bridge-target-enum-seams" + ] + }, + { + "query": "how do I enable GPU acceleration for kimetsu embedding inference", + "relevant": [] + }, + { + "query": "how do I throttle kimetsu API spend per month", + "relevant": [] + }, + { + "query": "can the kimetsu brain database be stored in S3 instead of on disk", + "relevant": [] + }, + { + "query": "how do I plug a custom tokenizer into the FTS index", + "relevant": [] + }, + { + "query": "what should I check when kimetsu behaves differently on Windows than on Linux?", + "relevant": [ + "process-start-time-cross-platform", + "cfg-cross-platform-dead-code", + "sqlite-vacuum-wal-checkpoint", + "windows-update-process-locking" + ] + }, + { + "query": "what are the moving parts of the kimetsu remote deployment story?", + "relevant": [ + "remote-ingest-split-roots", + "remote-mcp-host-wiring" + ] + }, + { + "query": "which lessons cover guarding behavior behind environment variables?", + "relevant": [ + "gc-trace-env-guard-placement", + "mutex-deadlock-user-brain-disabled" + ] + }, + { + "query": "SQLite BUSY error under concurrent writes", + "relevant": [ + "sqlite-busy-timeout-wal", + "sqlite-vacuum-wal-checkpoint" + ] + }, + { + "query": "SQLite WAL mode breaks when the database is on a network share", + "relevant": [ + "sqlite-wal-network-drive" + ] + }, + { + "query": "my SQLite WAL database causes SQLITE_IOERR_LOCK on a mapped drive", + "relevant": [ + "sqlite-wal-network-drive" + ] + }, + { + "query": "FTS5 tokenizer configuration for Rust identifiers with underscores", + "relevant": [ + "sqlite-fts5-tokenizer" + ] + }, + { + "query": "I switched the FTS5 tokenizer but search stopped returning results", + "relevant": [ + "sqlite-fts5-tokenizer" + ] + }, + { + "query": "optimal SQLite page size for storing embedding vectors", + "relevant": [ + "sqlite-page-size" + ] + }, + { + "query": "ON DELETE CASCADE in SQLite does nothing — foreign keys not enforced", + "relevant": [ + "sqlite-foreign-keys-default-off" + ] + }, + { + "query": "indexing a JSON metadata column in SQLite without a schema migration", + "relevant": [ + "sqlite-json1-extract" + ] + }, + { + "query": "prepare() vs prepare_cached() in rusqlite hot insert loop", + "relevant": [ + "sqlite-prepared-stmt-cache" + ] + }, + { + "query": "speed up bulk memory ingest by caching SQL statements", + "relevant": [ + "sqlite-prepared-stmt-cache" + ] + }, + { + "query": "partial index on deleted_at IS NULL for faster active memory queries", + "relevant": [ + "sqlite-partial-index" + ] + }, + { + "query": "the brain query is slow because it scans all rows including soft-deleted ones", + "relevant": [ + "sqlite-partial-index" + ] + }, + { + "query": "Cargo.lock changed unexpectedly after adding a new workspace crate", + "relevant": [ + "cargo-lockfile-drift" + ] + }, + { + "query": "how do I prevent CI from accepting a modified lockfile silently?", + "relevant": [ + "cargo-lockfile-drift" + ] + }, + { + "query": "build.rs reruns on every incremental build even when nothing changed", + "relevant": [ + "cargo-build-script-rerun" + ] + }, + { + "query": "incremental cargo build is slow because build script runs every time", + "relevant": [ + "cargo-build-script-rerun" + ] + }, + { + "query": "a dev-dependency is activating an embeddings feature in my production build", + "relevant": [ + "cargo-dev-dep-leak" + ] + }, + { + "query": "how do I prevent a test-only feature from bleeding into the non-test compilation?", + "relevant": [ + "cargo-dev-dep-leak" + ] + }, + { + "query": "linker errors in target/ caused by antivirus holding the exe file", + "relevant": [ + "cargo-target-dir-sharing", + "windows-file-locking-av" + ] + }, + { + "query": "Access is denied (os error 5) when linking on Windows — how do I fix this?", + "relevant": [ + "windows-file-locking-av" + ] + }, + { + "query": "incremental build broke with a type mismatch after switching branches", + "relevant": [ + "cargo-incremental-cache-corruption" + ] + }, + { + "query": "cargo reports a type error that references a type not in the codebase", + "relevant": [ + "cargo-incremental-cache-corruption" + ] + }, + { + "query": "compile fastembed at O2 in debug builds to avoid slow embedding inference", + "relevant": [ + "cargo-profile-override" + ] + }, + { + "query": "override compilation profile for a single crate in a Cargo workspace", + "relevant": [ + "cargo-profile-override" + ] + }, + { + "query": "[patch.crates-io] workspace dependency override", + "relevant": [ + "cargo-patch-section" + ] + }, + { + "query": "pin minimum supported Rust version in Cargo.toml", + "relevant": [ + "cargo-msrv" + ] + }, + { + "query": "Windows path over 260 characters causes OS error 3 during Cargo build", + "relevant": [ + "windows-long-paths" + ] + }, + { + "query": "how do I enable long file paths for Cargo on Windows?", + "relevant": [ + "windows-long-paths" + ] + }, + { + "query": "intermittent sharing violation errors when Rust linker writes the exe on Windows", + "relevant": [ + "windows-file-locking-av" + ] + }, + { + "query": "Rust walkdir follows junctions differently from symlinks on Windows", + "relevant": [ + "windows-junctions-vs-symlinks" + ] + }, + { + "query": "UNC path canonicalize returns verbatim prefix — how do I strip it?", + "relevant": [ + "windows-unc-paths" + ] + }, + { + "query": "UTF-8 memory text prints as mojibake in the Windows console", + "relevant": [ + "windows-console-encoding" + ] + }, + { + "query": "process exit code is 4294967295 instead of -1 on Windows", + "relevant": [ + "windows-exit-codes" + ] + }, + { + "query": "tokenizer.json must match the ONNX model — what breaks if it doesn't?", + "relevant": [ + "onnx-tokenizer-mismatch" + ] + }, + { + "query": "embedding quality degraded after I swapped in the INT8 quantized model", + "relevant": [ + "onnx-quantization-drift" + ] + }, + { + "query": "missing attention mask causes low-norm embeddings in batch inference", + "relevant": [ + "onnx-batch-padding" + ] + }, + { + "query": "ONNX model download fails in a Docker container with no home directory", + "relevant": [ + "onnx-model-cache-paths" + ] + }, + { + "query": "fastembed cache path environment variable for CI", + "relevant": [ + "onnx-model-cache-paths" + ] + }, + { + "query": "cosine similarity vs dot product for L2-normalized embedding vectors", + "relevant": [ + "onnx-cosine-vs-dot" + ] + }, + { + "query": "stored vectors have wrong dimension after switching embedding models", + "relevant": [ + "onnx-dim-mismatch" + ] + }, + { + "query": "E5 and Instructor models need a query prefix — what happens without it?", + "relevant": [ + "onnx-prefix-instructions" + ] + }, + { + "query": "ORT thread pool contention when running multiple bench processes in parallel", + "relevant": [ + "onnx-ort-threading" + ] + }, + { + "query": "git worktrees share the .kimetsu brain — how do I isolate test runs?", + "relevant": [ + "git-worktree-brain-isolation" + ] + }, + { + "query": "when is it safe to use --no-verify on git commit?", + "relevant": [ + "git-hooks-bypass" + ] + }, + { + "query": "reduce clone size and bandwidth for server-side repo ingest", + "relevant": [ + "git-sparse-checkout" + ] + }, + { + "query": "spurious diffs from Windows CRLF line ending conversion in git", + "relevant": [ + "git-line-endings-windows" + ] + }, + { + "query": "git submodule always gets the wrong commit in CI", + "relevant": [ + "git-submodule-pinning" + ] + }, + { + "query": "accidentally ran git reset --hard and lost commits — can I recover?", + "relevant": [ + "git-reflog-rescue" + ] + }, + { + "query": "blocking SQLite call from an async tokio handler causes latency spikes", + "relevant": [ + "tokio-blocking-in-async" + ] + }, + { + "query": "Cannot start a runtime from within a runtime in a tokio test", + "relevant": [ + "tokio-runtime-in-tests" + ] + }, + { + "query": "tokio select cancels the other branch and loses the value in the channel", + "relevant": [ + "tokio-select-cancellation" + ] + }, + { + "query": "mpsc channel backpressure causing senders to stall", + "relevant": [ + "tokio-channel-backpressure" + ] + }, + { + "query": "overhead from calling spawn_blocking on every single query request", + "relevant": [ + "tokio-spawn-blocking" + ] + }, + { + "query": "axum server panics during shutdown because the DB pool is already closed", + "relevant": [ + "tokio-shutdown-ordering" + ] + }, + { + "query": "reqwest Client created per-request defeats connection pooling", + "relevant": [ + "http-connection-pooling" + ] + }, + { + "query": "LLM request times out during streaming — which timeout setting applies?", + "relevant": [ + "http-timeout-layering" + ] + }, + { + "query": "how do I safely retry a POST to the LLM API without creating duplicates?", + "relevant": [ + "http-retry-idempotency" + ] + }, + { + "query": "custom enterprise root CA not trusted by rustls on Windows", + "relevant": [ + "http-tls-roots" + ] + }, + { + "query": "parsing server-sent events when a single TCP chunk contains a partial SSE frame", + "relevant": [ + "http-streaming-bodies" + ] + }, + { + "query": "reqwest does not use the system proxy settings on Windows", + "relevant": [ + "http-proxy-env" + ] + }, + { + "query": "insta snapshot tests fail in CI because output includes a timestamp", + "relevant": [ + "testing-snapshot-churn" + ] + }, + { + "query": "two test workers writing to the same temp directory path race each other", + "relevant": [ + "testing-temp-dirs-ci" + ] + }, + { + "query": "test passes locally but fails on a slow CI runner due to a 100ms sleep", + "relevant": [ + "testing-time-dependent-flakes" + ] + }, + { + "query": "proptest found a hash collision in text normalization that example tests missed", + "relevant": [ + "testing-property-tests" + ] + }, + { + "query": "set_var in tests races when cargo test runs them in parallel", + "relevant": [ + "testing-serial-vs-parallel" + ] + }, + { + "query": "hardcoded JSON fixtures broke after a schema migration", + "relevant": [ + "testing-fixture-drift" + ] + }, + { + "query": "debug print in the MCP handler corrupts the JSON-Lines protocol stream", + "relevant": [ + "mcp-stdout-protocol" + ] + }, + { + "query": "kimetsu MCP tool call times out because embedding model is re-initialized every call", + "relevant": [ + "mcp-tool-timeouts" + ] + }, + { + "query": "env var set after host launch is not visible to the MCP server process", + "relevant": [ + "mcp-env-propagation" + ] + }, + { + "query": "MCP tool call fails because a required field is missing from the JSON input", + "relevant": [ + "mcp-schema-validation" + ] + }, + { + "query": "Claude Code rejects the tool name with a hyphen in it", + "relevant": [ + "mcp-tool-naming" + ] + }, + { + "query": "MCP response path uses backslashes and the host rejects it", + "relevant": [ + "mcp-transcript-paths" + ] + }, + { + "query": "AWS credentials not found — which env var does kimetsu read for Bedrock?", + "relevant": [ + "aws-credentials-chain" + ] + }, + { + "query": "Bedrock InvokeModel fails because the region is not configured", + "relevant": [ + "aws-region-resolution" + ] + }, + { + "query": "how do I handle ThrottlingException from Bedrock with exponential backoff?", + "relevant": [ + "aws-retry-throttling" + ] + }, + { + "query": "generating a presigned S3 URL for brain export without exposing credentials", + "relevant": [ + "aws-presigned-urls" + ] + }, + { + "query": "IMDSv2 token required for instance metadata — PUT before GET", + "relevant": [ + "aws-instance-metadata" + ] + }, + { + "query": "Cargo cache key strategy for GitHub Actions to avoid toolchain version collisions", + "relevant": [ + "ci-cache-keys" + ] + }, + { + "query": "CI matrix has 18 jobs and costs too much — how do I reduce it?", + "relevant": [ + "ci-matrix-explosion" + ] + }, + { + "query": "GitHub Actions secret accidentally printed in build logs", + "relevant": [ + "ci-secrets-masking" + ] + }, + { + "query": "how long do GitHub Actions artifacts persist and what's the storage limit?", + "relevant": [ + "ci-artifact-retention" + ] + }, + { + "query": "timing-based test flake in CI — quarantine or fix?", + "relevant": [ + "ci-flaky-quarantine" + ] + }, + { + "query": "kimetsu doctor says the MCP server is running — how do I stop it before an update?", + "relevant": [ + "kimetsu-daemon-lifecycle" + ] + }, + { + "query": "noise capsules consuming token budget without contributing retrieval signal", + "relevant": [ + "kimetsu-capsule-budgets" + ] + }, + { + "query": "kimetsu_brain_record writes to the wrong brain location — user vs project scope", + "relevant": [ + "kimetsu-memory-scopes" + ] + }, + { + "query": "how do I configure kimetsu to use Claude Haiku for harvesting but Opus for the agent?", + "relevant": [ + "kimetsu-distiller-config" + ] + }, + { + "query": "first agent turn is slow because kimetsu proactive hook runs embedding inference", + "relevant": [ + "kimetsu-proactive-hooks" + ] + }, + { + "query": "make the kimetsu brain read-only for certain repos on a shared remote server", + "relevant": [ + "kimetsu-write-tools-gate" + ] + }, + { + "query": "kimetsu FTS search misses 'deadlocking' when memory says 'deadlock'", + "relevant": [ + "kimetsu-query-stemming" + ] + }, + { + "query": "how does pool size affect retrieval recall and latency in the bench?", + "relevant": [ + "kimetsu-rerank-pool" + ] + }, + { + "query": "second embedder in a remote bench run gets worse results than the first", + "relevant": [ + "kimetsu-bench-remote-embedder-singleton" + ] + }, + { + "query": "what is the expected JSON schema for kimetsu brain bench dataset files?", + "relevant": [ + "kimetsu-eval-fixture-shape" + ] + }, + { + "query": "what does MRR mean and how do I interpret a 0.01 difference between combos?", + "relevant": [ + "kimetsu-mrr-metric" + ] + }, + { + "query": "SQLITE_BUSY keeps appearing even with WAL mode enabled", + "relevant": [ + "sqlite-busy-timeout-wal" + ] + }, + { + "query": "my brain file got huge again right after I compacted it", + "relevant": [ + "sqlite-vacuum-wal-checkpoint", + "sqlite-page-size" + ] + }, + { + "query": "all my FTS queries stopped returning results after I changed the tokenizer config", + "relevant": [ + "sqlite-fts5-tokenizer" + ] + }, + { + "query": "something is preventing the kimetsu binary from being replaced during update", + "relevant": [ + "kimetsu-daemon-lifecycle", + "windows-file-locking-av", + "windows-update-process-locking" + ] + }, + { + "query": "tool call results not appearing in the context — is the semantic floor too high?", + "relevant": [ + "kimetsu-proactive-hooks", + "kimetsu-rerank-pool" + ] + }, + { + "query": "CARGO_INCREMENTAL=0 in CI prevents a class of spurious compilation errors", + "relevant": [ + "cargo-incremental-cache-corruption" + ] + }, + { + "query": "how do I check whether my Cargo workspace respects the MSRV constraint?", + "relevant": [ + "cargo-msrv" + ] + }, + { + "query": "rusqlite connection opened but ON DELETE CASCADE cascade never fires", + "relevant": [ + "sqlite-foreign-keys-default-off" + ] + }, + { + "query": "I cannot connect to kimetsu-remote — something about TLS cert validation failed", + "relevant": [ + "http-tls-roots" + ] + }, + { + "query": "graceful shutdown fails because in-flight SQLite queries are still running when pool closes", + "relevant": [ + "tokio-shutdown-ordering", + "tokio-blocking-in-async" + ] + }, + { + "query": "kimetsu-remote response takes 8 seconds — which stage is slow?", + "relevant": [ + "mcp-tool-timeouts", + "kimetsu-proactive-hooks" + ] + }, + { + "query": "git reflog to rescue accidentally deleted branch", + "relevant": [ + "git-reflog-rescue" + ] + }, + { + "query": "git submodule --remote advances the pinned SHA unexpectedly", + "relevant": [ + "git-submodule-pinning" + ] + }, + { + "query": "axum SSE streaming drops the last event when client disconnects", + "relevant": [ + "http-streaming-bodies", + "tokio-select-cancellation" + ] + }, + { + "query": "how do I detect that I am running inside a git worktree vs the main checkout?", + "relevant": [ + "git-worktree-brain-isolation" + ] + }, + { + "query": "ONNX Runtime intra-op threads causing CPU contention during parallel bench", + "relevant": [ + "onnx-ort-threading" + ] + }, + { + "query": "what is the right way to supply AWS session token alongside access key and secret?", + "relevant": [ + "aws-credentials-chain", + "aws-sigv4-bedrock-blocking" + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/docs/audits/2026-09-05-brainbench/development-6000.json b/docs/audits/2026-09-05-brainbench/development-6000.json new file mode 100644 index 0000000..f203d42 --- /dev/null +++ b/docs/audits/2026-09-05-brainbench/development-6000.json @@ -0,0 +1,180 @@ +{ + "schema_version": 1, + "status": "complete", + "harness": { + "path": "E:\\Kimetsu\\bench\\target\\release\\kbench.exe", + "sha256": "62d20550bac2c915613b39f9381fdd6eb5c4d1a10a1782a91796a6d87ff5ea75", + "bytes": 9355776 + }, + "runner": { + "path": "E:\\tmp\\kimetsu-brain-hardening\\bench\\scripts\\compare_brainbench.py", + "sha256": "c9722466009db103d37e7701b3a42a7a7c0785be6ef3db018be80da4f6fc9b11", + "bytes": 22969 + }, + "binaries": { + "baseline": { + "path": "E:\\tmp\\kimetsu-brain-hardening\\tmp-tests\\kimetsu-baseline.exe", + "sha256": "aba19d66742fe4c6b7d8902a52f0b6d5a0ad5c9d72ecf705a2e5d69d546a75c0", + "bytes": 46408704 + }, + "candidate": { + "path": "E:\\tmp\\kimetsu-brain-hardening\\tmp-tests\\kimetsu-candidate.exe", + "sha256": "5c87e542907a47917f23fad50ddff1e46789eaae14328ccc662ca748b66b5477", + "bytes": 47080448 + } + }, + "datasets": [ + { + "path": "E:\\tmp\\kimetsu-brain-hardening\\tmp-tests\\brainbench-development-100.json", + "sha256": "ff3c78f8b5dab7e9b2f10894af93f07965705502a4f9d3e8c45c529b9b6ca33f", + "bytes": 122806 + } + ], + "settings": { + "budget_tokens": 6000, + "dimensions": [ + "poisoning", + "render-contract", + "retrieval", + "workflow" + ], + "jobs": 1, + "warm_start": false, + "include_ambient": false, + "overrides": { + "KIMETSU_BRAIN_EMBEDDER": "bge-small-en-v1.5", + "KIMETSU_DETECT_CONFLICTS": "0", + "KIMETSU_RESOLVE_CONFLICTS": "0", + "FASTEMBED_CACHE_DIR": "E:/Kimetsu/.fastembed_cache" + }, + "baseline_threads": 0, + "candidate_threads": 0, + "baseline_reranker": null, + "candidate_reranker": null + }, + "runs": [ + { + "label": "baseline", + "repeat": 1, + "intra_threads_override": null, + "reranker_override": null, + "wall_seconds": 205.66018299999996, + "report_file": "1-baseline.json" + }, + { + "label": "candidate", + "repeat": 1, + "intra_threads_override": null, + "reranker_override": null, + "wall_seconds": 230.2012442999985, + "report_file": "1-candidate.json" + }, + { + "label": "candidate", + "repeat": 2, + "intra_threads_override": null, + "reranker_override": null, + "wall_seconds": 233.46974230000342, + "report_file": "2-candidate.json" + }, + { + "label": "baseline", + "repeat": 2, + "intra_threads_override": null, + "reranker_override": null, + "wall_seconds": 180.9405689999985, + "report_file": "2-baseline.json" + }, + { + "label": "baseline", + "repeat": 3, + "intra_threads_override": null, + "reranker_override": null, + "wall_seconds": 179.88030720000097, + "report_file": "3-baseline.json" + }, + { + "label": "candidate", + "repeat": 3, + "intra_threads_override": null, + "reranker_override": null, + "wall_seconds": 198.51590569999826, + "report_file": "3-candidate.json" + } + ], + "comparison": { + "measurement_summary": { + "baseline": { + "unique_queries": 210, + "query_observations": 630, + "positive_queries": 197, + "negative_queries": 13, + "stale_queries": 0, + "positive_recall_at_4": 0.7368866328257191, + "positive_hit_at_4": 0.7563451776649747, + "positive_mrr": 0.7385786802030457, + "negative_injection_rate": 0.8461538461538461, + "stale_injection_rate": null, + "first_query_mean_ms": 942.4398333333332, + "subsequent_query_p50_ms": 866.2045, + "subsequent_query_p95_ms": 982.2975, + "subsequent_observations": 627, + "mean_model_text_bytes": 146856.6, + "mean_mcp_result_bytes": 153628.91904761904, + "memory_observations": 630, + "mean_mcp_working_set_bytes": 188760369.57460317, + "max_mcp_peak_working_set_bytes": 220270592, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + }, + "candidate": { + "unique_queries": 210, + "query_observations": 630, + "positive_queries": 197, + "negative_queries": 13, + "stale_queries": 0, + "positive_recall_at_4": 0.6962774957698815, + "positive_hit_at_4": 0.7055837563451777, + "positive_mrr": 0.6979695431472082, + "negative_injection_rate": 0.5384615384615384, + "stale_injection_rate": null, + "first_query_mean_ms": 1206.6408, + "subsequent_query_p50_ms": 1015.6333, + "subsequent_query_p95_ms": 1154.0131000000001, + "subsequent_observations": 627, + "mean_model_text_bytes": 1167.6, + "mean_mcp_result_bytes": 1262.4285714285713, + "memory_observations": 630, + "mean_mcp_working_set_bytes": 287032602.81904763, + "max_mcp_peak_working_set_bytes": 294555648, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + } + }, + "by_dimension": { + "retrieval": { + "n_scenarios": 1, + "baseline": 0.7007936507936509, + "candidate": 0.6817460317460317, + "mean_delta": -0.019047619047619202, + "ci95": null, + "wins": 0, + "ties": 0, + "losses": 1 + } + }, + "scenarios": [ + { + "identity": "retrieval/existing-development-100", + "dimension": "retrieval", + "baseline": 0.7007936507936509, + "candidate": 0.6817460317460317, + "delta": -0.019047619047619202 + } + ], + "unpaired_scenarios": [], + "unpaired_details": [], + "baseline_errors": 0, + "candidate_errors": 0, + "repeats": 3, + "uncertainty_note": "Exploratory paired bootstrap over scenario IDs after averaging repeats; correlated task families require a separate grouped holdout." + } +} \ No newline at end of file diff --git a/docs/audits/2026-09-05-brainbench/idf-sql-comparison.json b/docs/audits/2026-09-05-brainbench/idf-sql-comparison.json new file mode 100644 index 0000000..354403e --- /dev/null +++ b/docs/audits/2026-09-05-brainbench/idf-sql-comparison.json @@ -0,0 +1,137 @@ +{ + "scope": "isolated SQL helper, synthetic aligned token semantics, in-memory SQLite; excludes ANN/model/reranking/serialization", + "sqlite": "3.49.1", + "platform": "Windows-11-10.0.26200-SP0", + "processor": "AMD64 Family 23 Model 113 Stepping 0, AuthenticAMD", + "repeats": 5, + "rows": [ + { + "memories": 1000, + "term_document_counts": [ + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2 + ], + "samples_ms": { + "old_like": [ + 4.511300008744001, + 4.530499994871207, + 4.487600002903491, + 4.508400001213886, + 4.744399993796833 + ], + "indexed_fts": [ + 0.5877000075997785, + 0.5501999985426664, + 0.6098000012570992, + 0.5547999899135903, + 0.6742000114172697 + ] + }, + "old_median_ms": 4.511300008744001, + "new_median_ms": 0.5877000075997785, + "ratio_old_over_new": 7.6761952533718185 + }, + { + "memories": 10000, + "term_document_counts": [ + 13, + 13, + 13, + 13, + 13, + 13, + 13, + 13 + ], + "samples_ms": { + "old_like": [ + 46.04509999626316, + 47.350599998026155, + 46.61260001012124, + 50.578199996380135, + 46.93979999865405 + ], + "indexed_fts": [ + 5.866599996807054, + 5.22120000096038, + 5.880099997739308, + 5.72719999763649, + 5.997400003252551 + ] + }, + "old_median_ms": 46.93979999865405, + "new_median_ms": 5.866599996807054, + "ratio_old_over_new": 8.001193199502511 + }, + { + "memories": 100000, + "term_document_counts": [ + 124, + 124, + 124, + 124, + 124, + 124, + 124, + 124 + ], + "samples_ms": { + "old_like": [ + 491.7338999948697, + 524.3487999978242, + 558.9880000043195, + 586.2646999885328, + 579.4836999994004 + ], + "indexed_fts": [ + 62.46400000236463, + 63.29229999391828, + 78.13460000033956, + 71.80599999264814, + 75.48600000154693 + ] + }, + "old_median_ms": 558.9880000043195, + "new_median_ms": 71.80599999264814, + "ratio_old_over_new": 7.784697658434553 + }, + { + "memories": 1000000, + "term_document_counts": [ + 1238, + 1238, + 1238, + 1238, + 1238, + 1238, + 1238, + 1238 + ], + "samples_ms": { + "old_like": [ + 5989.831800005049, + 6055.378800010658, + 6006.278600005317, + 5817.408999995678, + 5850.40399999707 + ], + "indexed_fts": [ + 830.0755999953253, + 862.6494000054663, + 790.5230000033043, + 770.8706999983406, + 809.6593999944162 + ] + }, + "old_median_ms": 5989.831800005049, + "new_median_ms": 809.6593999944162, + "ratio_old_over_new": 7.397964872693825 + } + ] +} \ No newline at end of file diff --git a/docs/audits/2026-09-05-brainbench/run-comparisons.ps1 b/docs/audits/2026-09-05-brainbench/run-comparisons.ps1 new file mode 100644 index 0000000..3426c8c --- /dev/null +++ b/docs/audits/2026-09-05-brainbench/run-comparisons.ps1 @@ -0,0 +1,48 @@ +param( + [Parameter(Mandatory=$true)][string]$Baseline, + [Parameter(Mandatory=$true)][string]$Candidate, + [Parameter(Mandatory=$true)][string]$Harness, + [Parameter(Mandatory=$true)][string]$ModelCache, + [Parameter(Mandatory=$true)][string]$OutputRoot +) +$ErrorActionPreference = 'Stop' +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '../../..')).Path +$runner = Join-Path $repoRoot 'bench/scripts/compare_brainbench.py' +$harness = (Resolve-Path -LiteralPath $Harness).Path +$baselineBinary = (Resolve-Path -LiteralPath $Baseline).Path +$candidateBinary = (Resolve-Path -LiteralPath $Candidate).Path +$contractFixture = Join-Path $PSScriptRoot 'agent-memory-contract.json' +$developmentFixture = Join-Path $PSScriptRoot 'development-100.json' +if (Test-Path -LiteralPath $OutputRoot) { throw 'Choose a new OutputRoot to preserve previous measurements.' } +New-Item -ItemType Directory -Path $OutputRoot | Out-Null +$auditOutput = (Resolve-Path -LiteralPath $OutputRoot).Path +$env:FASTEMBED_CACHE_DIR = (Resolve-Path -LiteralPath $ModelCache).Path +$env:HF_HUB_OFFLINE = '1' +$env:KIMETSU_USER_BRAIN = '0' +$env:KIMETSU_BRAIN_EMBEDDER = 'bge-small-en-v1.5' +$env:KIMETSU_DETECT_CONFLICTS = '0' +$env:KIMETSU_RESOLVE_CONFLICTS = '0' +Remove-Item Env:KBENCH_RERANKER -ErrorAction SilentlyContinue +Remove-Item Env:KIMETSU_ABSTAIN_EVIDENCE -ErrorAction SilentlyContinue + +# Sequential experiments only: do not compile, test, or run other inference +# while this script owns the performance window. Artifacts fingerprint inputs. +$experiments = @( + @{ name='contract-512'; dataset=$contractFixture; budget=512; baseline=$baselineBinary; candidate=$candidateBinary; extra=@('--baseline-threads','0','--candidate-threads','0') }, + @{ name='contract-2048'; dataset=$contractFixture; budget=2048; baseline=$baselineBinary; candidate=$candidateBinary; extra=@('--baseline-threads','0','--candidate-threads','0') }, + @{ name='development-6000'; dataset=$developmentFixture; budget=6000; baseline=$baselineBinary; candidate=$candidateBinary; extra=@('--baseline-threads','0','--candidate-threads','0') }, + @{ name='threads-default-vs-4'; dataset=$developmentFixture; budget=6000; baseline=$candidateBinary; candidate=$candidateBinary; extra=@('--baseline-threads','0','--candidate-threads','4','--baseline-reranker','ms-marco-tinybert-l-2-v2','--candidate-reranker','ms-marco-tinybert-l-2-v2') }, + @{ name='tinybert-vs-minilm-4threads'; dataset=$developmentFixture; budget=6000; baseline=$candidateBinary; candidate=$candidateBinary; extra=@('--baseline-threads','4','--candidate-threads','4','--baseline-reranker','ms-marco-tinybert-l-2-v2','--candidate-reranker','ms-marco-minilm-l-4-v2') } +) +foreach ($experiment in $experiments) { + Write-Output "Starting $($experiment.name) at $([DateTimeOffset]::Now.ToString('o'))" + $comparisonArgs = @($runner,'--kbench',$harness,'--baseline',$experiment.baseline,'--candidate',$experiment.candidate,'--dataset',$experiment.dataset,'--budget-tokens',"$($experiment.budget)",'--repeats','3','--out',"$auditOutput/paired-$($experiment.name)") + $experiment.extra + python @comparisonArgs + if ($LASTEXITCODE -ne 0) { throw "Comparison $($experiment.name) failed ($LASTEXITCODE); inspect preserved artifacts before continuing." } + $pairedResult = Get-Content -Raw -LiteralPath "$auditOutput/paired-$($experiment.name)/comparison.json" | ConvertFrom-Json + if ($pairedResult.status -ne 'complete' -or $pairedResult.comparison.baseline_errors -ne 0 -or $pairedResult.comparison.candidate_errors -ne 0 -or $pairedResult.comparison.unpaired_scenarios.Count -ne 0) { + throw "Comparison $($experiment.name) contains errors or unpaired scenarios; inspect artifacts before continuing." + } +} +python "$PSScriptRoot/benchmark_idf_sql.py" --out "$auditOutput/idf-sql-comparison.json" +if ($LASTEXITCODE -ne 0) { throw "IDF helper experiment failed ($LASTEXITCODE)" } diff --git a/docs/audits/2026-09-05-brainbench/threads-default-vs-4.json b/docs/audits/2026-09-05-brainbench/threads-default-vs-4.json new file mode 100644 index 0000000..07f77e0 --- /dev/null +++ b/docs/audits/2026-09-05-brainbench/threads-default-vs-4.json @@ -0,0 +1,180 @@ +{ + "schema_version": 1, + "status": "complete", + "harness": { + "path": "E:\\Kimetsu\\bench\\target\\release\\kbench.exe", + "sha256": "62d20550bac2c915613b39f9381fdd6eb5c4d1a10a1782a91796a6d87ff5ea75", + "bytes": 9355776 + }, + "runner": { + "path": "E:\\tmp\\kimetsu-brain-hardening\\bench\\scripts\\compare_brainbench.py", + "sha256": "c9722466009db103d37e7701b3a42a7a7c0785be6ef3db018be80da4f6fc9b11", + "bytes": 22969 + }, + "binaries": { + "baseline": { + "path": "E:\\tmp\\kimetsu-brain-hardening\\tmp-tests\\kimetsu-candidate.exe", + "sha256": "5c87e542907a47917f23fad50ddff1e46789eaae14328ccc662ca748b66b5477", + "bytes": 47080448 + }, + "candidate": { + "path": "E:\\tmp\\kimetsu-brain-hardening\\tmp-tests\\kimetsu-candidate.exe", + "sha256": "5c87e542907a47917f23fad50ddff1e46789eaae14328ccc662ca748b66b5477", + "bytes": 47080448 + } + }, + "datasets": [ + { + "path": "E:\\tmp\\kimetsu-brain-hardening\\tmp-tests\\brainbench-development-100.json", + "sha256": "ff3c78f8b5dab7e9b2f10894af93f07965705502a4f9d3e8c45c529b9b6ca33f", + "bytes": 122806 + } + ], + "settings": { + "budget_tokens": 6000, + "dimensions": [ + "poisoning", + "render-contract", + "retrieval", + "workflow" + ], + "jobs": 1, + "warm_start": false, + "include_ambient": false, + "overrides": { + "KIMETSU_BRAIN_EMBEDDER": "bge-small-en-v1.5", + "KIMETSU_DETECT_CONFLICTS": "0", + "KIMETSU_RESOLVE_CONFLICTS": "0", + "FASTEMBED_CACHE_DIR": "E:/Kimetsu/.fastembed_cache" + }, + "baseline_threads": 0, + "candidate_threads": 4, + "baseline_reranker": "ms-marco-tinybert-l-2-v2", + "candidate_reranker": "ms-marco-tinybert-l-2-v2" + }, + "runs": [ + { + "label": "baseline", + "repeat": 1, + "intra_threads_override": null, + "reranker_override": "ms-marco-tinybert-l-2-v2", + "wall_seconds": 192.30587940000987, + "report_file": "1-baseline.json" + }, + { + "label": "candidate", + "repeat": 1, + "intra_threads_override": "4", + "reranker_override": "ms-marco-tinybert-l-2-v2", + "wall_seconds": 200.74955749999208, + "report_file": "1-candidate.json" + }, + { + "label": "candidate", + "repeat": 2, + "intra_threads_override": "4", + "reranker_override": "ms-marco-tinybert-l-2-v2", + "wall_seconds": 247.85622799998964, + "report_file": "2-candidate.json" + }, + { + "label": "baseline", + "repeat": 2, + "intra_threads_override": null, + "reranker_override": "ms-marco-tinybert-l-2-v2", + "wall_seconds": 210.26000479998766, + "report_file": "2-baseline.json" + }, + { + "label": "baseline", + "repeat": 3, + "intra_threads_override": null, + "reranker_override": "ms-marco-tinybert-l-2-v2", + "wall_seconds": 207.7461583999975, + "report_file": "3-baseline.json" + }, + { + "label": "candidate", + "repeat": 3, + "intra_threads_override": "4", + "reranker_override": "ms-marco-tinybert-l-2-v2", + "wall_seconds": 238.37547729999642, + "report_file": "3-candidate.json" + } + ], + "comparison": { + "measurement_summary": { + "baseline": { + "unique_queries": 210, + "query_observations": 630, + "positive_queries": 197, + "negative_queries": 13, + "stale_queries": 0, + "positive_recall_at_4": 0.6962774957698815, + "positive_hit_at_4": 0.7055837563451777, + "positive_mrr": 0.6979695431472082, + "negative_injection_rate": 0.5384615384615384, + "stale_injection_rate": null, + "first_query_mean_ms": 1093.0742, + "subsequent_query_p50_ms": 939.7666, + "subsequent_query_p95_ms": 1051.7809, + "subsequent_observations": 627, + "mean_model_text_bytes": 1167.6, + "mean_mcp_result_bytes": 1262.4285714285713, + "memory_observations": 630, + "mean_mcp_working_set_bytes": 286516103.72063494, + "max_mcp_peak_working_set_bytes": 293388288, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + }, + "candidate": { + "unique_queries": 210, + "query_observations": 630, + "positive_queries": 197, + "negative_queries": 13, + "stale_queries": 0, + "positive_recall_at_4": 0.6962774957698815, + "positive_hit_at_4": 0.7055837563451777, + "positive_mrr": 0.6979695431472082, + "negative_injection_rate": 0.5384615384615384, + "stale_injection_rate": null, + "first_query_mean_ms": 1066.6739666666667, + "subsequent_query_p50_ms": 950.6405, + "subsequent_query_p95_ms": 1103.8329, + "subsequent_observations": 627, + "mean_model_text_bytes": 1167.6, + "mean_mcp_result_bytes": 1262.4285714285713, + "memory_observations": 630, + "mean_mcp_working_set_bytes": 282804360.53333336, + "max_mcp_peak_working_set_bytes": 290373632, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + } + }, + "by_dimension": { + "retrieval": { + "n_scenarios": 1, + "baseline": 0.6817460317460317, + "candidate": 0.6817460317460317, + "mean_delta": 0.0, + "ci95": null, + "wins": 0, + "ties": 1, + "losses": 0 + } + }, + "scenarios": [ + { + "identity": "retrieval/existing-development-100", + "dimension": "retrieval", + "baseline": 0.6817460317460317, + "candidate": 0.6817460317460317, + "delta": 0.0 + } + ], + "unpaired_scenarios": [], + "unpaired_details": [], + "baseline_errors": 0, + "candidate_errors": 0, + "repeats": 3, + "uncertainty_note": "Exploratory paired bootstrap over scenario IDs after averaging repeats; correlated task families require a separate grouped holdout." + } +} \ No newline at end of file diff --git a/docs/audits/2026-09-05-brainbench/tinybert-vs-minilm-4threads.json b/docs/audits/2026-09-05-brainbench/tinybert-vs-minilm-4threads.json new file mode 100644 index 0000000..c9097d6 --- /dev/null +++ b/docs/audits/2026-09-05-brainbench/tinybert-vs-minilm-4threads.json @@ -0,0 +1,180 @@ +{ + "schema_version": 1, + "status": "complete", + "harness": { + "path": "E:\\Kimetsu\\bench\\target\\release\\kbench.exe", + "sha256": "62d20550bac2c915613b39f9381fdd6eb5c4d1a10a1782a91796a6d87ff5ea75", + "bytes": 9355776 + }, + "runner": { + "path": "E:\\tmp\\kimetsu-brain-hardening\\bench\\scripts\\compare_brainbench.py", + "sha256": "c9722466009db103d37e7701b3a42a7a7c0785be6ef3db018be80da4f6fc9b11", + "bytes": 22969 + }, + "binaries": { + "baseline": { + "path": "E:\\tmp\\kimetsu-brain-hardening\\tmp-tests\\kimetsu-candidate.exe", + "sha256": "5c87e542907a47917f23fad50ddff1e46789eaae14328ccc662ca748b66b5477", + "bytes": 47080448 + }, + "candidate": { + "path": "E:\\tmp\\kimetsu-brain-hardening\\tmp-tests\\kimetsu-candidate.exe", + "sha256": "5c87e542907a47917f23fad50ddff1e46789eaae14328ccc662ca748b66b5477", + "bytes": 47080448 + } + }, + "datasets": [ + { + "path": "E:\\tmp\\kimetsu-brain-hardening\\tmp-tests\\brainbench-development-100.json", + "sha256": "ff3c78f8b5dab7e9b2f10894af93f07965705502a4f9d3e8c45c529b9b6ca33f", + "bytes": 122806 + } + ], + "settings": { + "budget_tokens": 6000, + "dimensions": [ + "poisoning", + "render-contract", + "retrieval", + "workflow" + ], + "jobs": 1, + "warm_start": false, + "include_ambient": false, + "overrides": { + "KIMETSU_BRAIN_EMBEDDER": "bge-small-en-v1.5", + "KIMETSU_DETECT_CONFLICTS": "0", + "KIMETSU_RESOLVE_CONFLICTS": "0", + "FASTEMBED_CACHE_DIR": "E:/Kimetsu/.fastembed_cache" + }, + "baseline_threads": 4, + "candidate_threads": 4, + "baseline_reranker": "ms-marco-tinybert-l-2-v2", + "candidate_reranker": "ms-marco-minilm-l-4-v2" + }, + "runs": [ + { + "label": "baseline", + "repeat": 1, + "intra_threads_override": "4", + "reranker_override": "ms-marco-tinybert-l-2-v2", + "wall_seconds": 210.64015550000477, + "report_file": "1-baseline.json" + }, + { + "label": "candidate", + "repeat": 1, + "intra_threads_override": "4", + "reranker_override": "ms-marco-minilm-l-4-v2", + "wall_seconds": 247.87505580000288, + "report_file": "1-candidate.json" + }, + { + "label": "candidate", + "repeat": 2, + "intra_threads_override": "4", + "reranker_override": "ms-marco-minilm-l-4-v2", + "wall_seconds": 255.41933000000427, + "report_file": "2-candidate.json" + }, + { + "label": "baseline", + "repeat": 2, + "intra_threads_override": "4", + "reranker_override": "ms-marco-tinybert-l-2-v2", + "wall_seconds": 209.31176880000567, + "report_file": "2-baseline.json" + }, + { + "label": "baseline", + "repeat": 3, + "intra_threads_override": "4", + "reranker_override": "ms-marco-tinybert-l-2-v2", + "wall_seconds": 208.58476650000375, + "report_file": "3-baseline.json" + }, + { + "label": "candidate", + "repeat": 3, + "intra_threads_override": "4", + "reranker_override": "ms-marco-minilm-l-4-v2", + "wall_seconds": 247.92748620000202, + "report_file": "3-candidate.json" + } + ], + "comparison": { + "measurement_summary": { + "baseline": { + "unique_queries": 210, + "query_observations": 630, + "positive_queries": 197, + "negative_queries": 13, + "stale_queries": 0, + "positive_recall_at_4": 0.6962774957698815, + "positive_hit_at_4": 0.7055837563451777, + "positive_mrr": 0.6979695431472082, + "negative_injection_rate": 0.5384615384615384, + "stale_injection_rate": null, + "first_query_mean_ms": 1090.5101666666667, + "subsequent_query_p50_ms": 972.8915000000001, + "subsequent_query_p95_ms": 1049.3316, + "subsequent_observations": 627, + "mean_model_text_bytes": 1167.6, + "mean_mcp_result_bytes": 1262.4285714285713, + "memory_observations": 630, + "mean_mcp_working_set_bytes": 283007138.53968257, + "max_mcp_peak_working_set_bytes": 289460224, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + }, + "candidate": { + "unique_queries": 210, + "query_observations": 630, + "positive_queries": 197, + "negative_queries": 13, + "stale_queries": 0, + "positive_recall_at_4": 0.7089678510998308, + "positive_hit_at_4": 0.7208121827411168, + "positive_mrr": 0.7131979695431472, + "negative_injection_rate": 0.46153846153846156, + "stale_injection_rate": null, + "first_query_mean_ms": 1365.7657333333334, + "subsequent_query_p50_ms": 1139.2821, + "subsequent_query_p95_ms": 1345.9903, + "subsequent_observations": 627, + "mean_model_text_bytes": 1113.4142857142858, + "mean_mcp_result_bytes": 1205.9, + "memory_observations": 630, + "mean_mcp_working_set_bytes": 723838117.7904762, + "max_mcp_peak_working_set_bytes": 745439232, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + } + }, + "by_dimension": { + "retrieval": { + "n_scenarios": 1, + "baseline": 0.6817460317460317, + "candidate": 0.6984126984126984, + "mean_delta": 0.01666666666666672, + "ci95": null, + "wins": 1, + "ties": 0, + "losses": 0 + } + }, + "scenarios": [ + { + "identity": "retrieval/existing-development-100", + "dimension": "retrieval", + "baseline": 0.6817460317460317, + "candidate": 0.6984126984126984, + "delta": 0.01666666666666672 + } + ], + "unpaired_scenarios": [], + "unpaired_details": [], + "baseline_errors": 0, + "candidate_errors": 0, + "repeats": 3, + "uncertainty_note": "Exploratory paired bootstrap over scenario IDs after averaging repeats; correlated task families require a separate grouped holdout." + } +} \ No newline at end of file From 41ff583a1485fa5c5d8584f9d71fae3de9865071 Mon Sep 17 00:00:00 2001 From: RodCor Date: Mon, 7 Sep 2026 00:25:34 -0300 Subject: [PATCH 23/34] Add configurable rerank cutoff and pinned multilingual model option --- crates/kimetsu-brain/src/embeddings.rs | 34 ++++++++---- crates/kimetsu-brain/src/serving.rs | 10 +++- crates/kimetsu-chat/src/mcp_server.rs | 52 +++++++++++++++++-- crates/kimetsu-cli/src/commands/bench.rs | 8 +-- crates/kimetsu-cli/src/commands/brain.rs | 2 +- crates/kimetsu-cli/src/embed_daemon/server.rs | 3 +- crates/kimetsu-core/src/config.rs | 39 ++++++++++++++ 7 files changed, 128 insertions(+), 20 deletions(-) diff --git a/crates/kimetsu-brain/src/embeddings.rs b/crates/kimetsu-brain/src/embeddings.rs index 1941cab..33abe55 100644 --- a/crates/kimetsu-brain/src/embeddings.rs +++ b/crates/kimetsu-brain/src/embeddings.rs @@ -317,6 +317,7 @@ pub fn open_reranker_for_model(model_id: &str) -> Option> { "jina-reranker-v1-tiny-en", "ms-marco-tinybert-l-2-v2", "ms-marco-minilm-l-4-v2", + "mmarco-minilm-l12-v2-int8", ]; if CURATED.contains(&v.as_str()) { @@ -805,6 +806,7 @@ mod fastembed_backend { "jina-reranker-v1-tiny-en" => Some("jinaai/jina-reranker-v1-tiny-en"), "ms-marco-tinybert-l-2-v2" => Some("Xenova/ms-marco-TinyBERT-L-2-v2"), "ms-marco-minilm-l-4-v2" => Some("Xenova/ms-marco-MiniLM-L-4-v2"), + "mmarco-minilm-l12-v2-int8" => Some("cross-encoder/mmarco-mMiniLMv2-L12-H384-v1"), _ => None, } } @@ -835,7 +837,16 @@ mod fastembed_backend { let api = ApiBuilder::from_env().build().map_err(|e| { EmbedderError::LoadFailed(format!("hf-hub ApiBuilder::from_env failed: {e}")) })?; - let repo = api.model(repo_id.clone()); + let multilingual_int8 = lowercased == "mmarco-minilm-l12-v2-int8"; + let repo = if multilingual_int8 { + api.repo(hf_hub::Repo::with_revision( + repo_id.clone(), + hf_hub::RepoType::Model, + "1427fd652930e4ba29e8149678df786c240d8825".into(), + )) + } else { + api.model(repo_id.clone()) + }; // Helper: download a required file or return LoadFailed. let get_required = |filename: &str| -> Result, EmbedderError> { @@ -853,14 +864,19 @@ mod fastembed_backend { let special_tokens_map_file = get_required("special_tokens_map.json")?; // Try `onnx/model.onnx` first, then `model.onnx` at root. - let onnx_path = repo - .get("onnx/model.onnx") - .or_else(|_| repo.get("model.onnx")) - .map_err(|e| { - EmbedderError::LoadFailed(format!( - "{repo_id}: could not find onnx/model.onnx or model.onnx: {e}" - )) - })?; + let onnx_path = if multilingual_int8 { + // Pin the tested compact CPU export; never silently fetch the + // much larger float32 checkpoint for this explicit model ID. + repo.get("onnx/model_quint8_avx2.onnx") + } else { + repo.get("onnx/model.onnx") + .or_else(|_| repo.get("model.onnx")) + } + .map_err(|e| { + EmbedderError::LoadFailed(format!( + "{repo_id}: could not find onnx/model.onnx or model.onnx: {e}" + )) + })?; let tokenizer_files = fastembed::TokenizerFiles { tokenizer_file, diff --git a/crates/kimetsu-brain/src/serving.rs b/crates/kimetsu-brain/src/serving.rs index 2876dc8..f2ccbe6 100644 --- a/crates/kimetsu-brain/src/serving.rs +++ b/crates/kimetsu-brain/src/serving.rs @@ -66,6 +66,12 @@ impl Default for ServingPolicy { } } impl ServingPolicy { + pub fn from_config(config: &kimetsu_core::config::ProjectConfig) -> Self { + Self { + rerank_floor: config.broker.rerank_min_score, + ..Self::default() + } + } pub fn prepare(&self, mut request: ContextRequest, reranking: bool) -> ContextRequest { request.budget_tokens = if reranking { self.budget.max(DEFAULT_BUDGET) @@ -159,7 +165,9 @@ impl ServingPolicy { .iter() .any(|s| !s.is_finite() || !(0.0..=1.0).contains(s)) { - return Err("reranker returned invalid scores; no cross-encoder measurement".into()); + return Err( + "reranker returned invalid scores; no cross-encoder measurement".into(), + ); } Some(CheckedScores { model: rr.model_id(), diff --git a/crates/kimetsu-chat/src/mcp_server.rs b/crates/kimetsu-chat/src/mcp_server.rs index e2bf13a..75a7847 100644 --- a/crates/kimetsu-chat/src/mcp_server.rs +++ b/crates/kimetsu-chat/src/mcp_server.rs @@ -913,10 +913,12 @@ fn brain_context_tool_with_embedder_loader( config_ambient, ); + let session = kimetsu_brain::project::BrainSession::open_readonly(workspace) + .map_err(|e| e.to_string())?; let policy = kimetsu_brain::serving::ServingPolicy { budget: budget_tokens, cap, - ..Default::default() + ..kimetsu_brain::serving::ServingPolicy::from_config(session.config()) }; let request = ContextRequest { stage: stage.to_string(), @@ -938,8 +940,6 @@ fn brain_context_tool_with_embedder_loader( .map(|v| v as f32), ..Default::default() }; - let session = kimetsu_brain::project::BrainSession::open_readonly(workspace) - .map_err(|e| e.to_string())?; let exposure = kimetsu_core::event::Event::new( kimetsu_core::ids::RunId::new(), "context.injected", @@ -3072,6 +3072,52 @@ mod tests { }); } + #[test] + fn configured_rerank_cutoff_controls_actual_mcp_admission() { + struct ModerateScore; + impl kimetsu_brain::embeddings::Reranker for ModerateScore { + fn model_id(&self) -> &str { + "fixed-admission-evidence" + } + fn rerank( + &self, + _: &str, + docs: &[&str], + ) -> Result, kimetsu_brain::embeddings::EmbedderError> { + Ok(vec![0.6; docs.len()]) + } + } + kimetsu_brain::user_brain::with_user_brain_disabled(|| { + let root = temp_root("configured-rerank-cutoff"); + fs::create_dir_all(&root).unwrap(); + project::init_project(&root, false).unwrap(); + project::add_memory( + &root, + MemoryScope::Project, + MemoryKind::Fact, + "Use ripgrep to search files before reading source code.", + ) + .unwrap(); + let paths = kimetsu_core::paths::ProjectPaths::discover(&root).unwrap(); + let original = fs::read_to_string(&paths.project_toml).unwrap(); + let args = json!({"query":"ripgrep search files","include_ambient":false,"budget_tokens":6000}); + for (floor, count) in [(0.75, 0), (0.0, 1)] { + let mut value: toml::Value = toml::from_str(&original).unwrap(); + value["broker"] + .as_table_mut() + .unwrap() + .insert("rerank_min_score".into(), toml::Value::Float(floor)); + fs::write(&paths.project_toml, toml::to_string(&value).unwrap()).unwrap(); + let result = brain_context_tool(&root, &args, Some(&ModerateScore)).unwrap(); + assert_eq!( + result["capsule_count"], count, + "configured floor {floor}: {result}" + ); + } + fs::remove_dir_all(root).unwrap(); + }); + } + #[test] fn stdio_uses_configured_reranker_off_and_initialization_error_explicitly() { kimetsu_brain::user_brain::with_user_brain_disabled(|| { diff --git a/crates/kimetsu-cli/src/commands/bench.rs b/crates/kimetsu-cli/src/commands/bench.rs index 36ba732..392c38e 100644 --- a/crates/kimetsu-cli/src/commands/bench.rs +++ b/crates/kimetsu-cli/src/commands/bench.rs @@ -184,7 +184,7 @@ pub(crate) fn brain_eval_inner(args: EvalArgs) -> KimetsuResult<()> { } else { rerank_cap }, - ..Default::default() + ..kimetsu_brain::serving::ServingPolicy::from_config(session.config()) }; let request = ContextRequest { stage: "localization".into(), @@ -372,7 +372,7 @@ pub(crate) fn brain_eval_inner(args: EvalArgs) -> KimetsuResult<()> { let policy = kimetsu_brain::serving::ServingPolicy { pool, cap: rerank_cap, - ..Default::default() + ..kimetsu_brain::serving::ServingPolicy::from_config(session.config()) }; let request = ContextRequest { stage: "localization".into(), @@ -1866,7 +1866,7 @@ pub(crate) fn brain_bench_single(args: BrainBenchArgs) -> KimetsuResult<()> { let policy = kimetsu_brain::serving::ServingPolicy { pool: args.pool, cap: args.cap, - ..Default::default() + ..kimetsu_brain::serving::ServingPolicy::from_config(session.config()) }; let request = ContextRequest { stage: "localization".into(), @@ -2083,7 +2083,7 @@ pub(crate) fn brain_bench_single(args: BrainBenchArgs) -> KimetsuResult<()> { "measurement_policy":"canonical_brain_context_v1", "ambient":false,"warm_start":false, "cost_unit":"serialized_utf8_byte_bound", - "budget":6000,"pool":args.pool,"cap":args.cap,"rerank_floor":kimetsu_brain::serving::RERANK_FLOOR, + "budget":6000,"pool":args.pool,"cap":args.cap,"rerank_floor":session.config().broker.rerank_min_score, "summary": { "positive_count":signal_cases.len(),"negative_count":noise_cases.len(), "negative_accuracy":if noise_cases.is_empty() {None}else{Some(noise_cases.iter().filter(|(_,r)|r.obtained.is_empty()).count() as f64/noise_cases.len() as f64)}, diff --git a/crates/kimetsu-cli/src/commands/brain.rs b/crates/kimetsu-cli/src/commands/brain.rs index 2e4bcf0..eaf4c87 100644 --- a/crates/kimetsu-cli/src/commands/brain.rs +++ b/crates/kimetsu-cli/src/commands/brain.rs @@ -2709,7 +2709,7 @@ pub(crate) fn brain_tune_sweep( } let config = project::load_config(paths)?; let embedder = open_embedder_for_checked(config.embedder.enabled)?; - let policy = ServingPolicy::default(); + let policy = ServingPolicy::from_config(&config); let current_combo = TuneCombo { min_lexical_coverage: config.broker.min_lexical_coverage, min_semantic_score: config.broker.min_semantic_score, diff --git a/crates/kimetsu-cli/src/embed_daemon/server.rs b/crates/kimetsu-cli/src/embed_daemon/server.rs index f0448e9..fca933b 100644 --- a/crates/kimetsu-cli/src/embed_daemon/server.rs +++ b/crates/kimetsu-cli/src/embed_daemon/server.rs @@ -20,7 +20,6 @@ use std::time::Instant; const RERANK_POOL: usize = kimetsu_brain::serving::RERANK_POOL; /// Sigmoid-score floor — capsules the cross-encoder judges below this are noise. -const RERANK_FLOOR: f32 = kimetsu_brain::serving::RERANK_FLOOR; /// Process-global state shared by all worker threads. pub struct DaemonState { @@ -71,7 +70,7 @@ impl DaemonState { }, cap, pool: RERANK_POOL, - rerank_floor: RERANK_FLOOR, + rerank_floor: session.config().broker.rerank_min_score, }; let request = ContextRequest { stage: if args.stage.is_empty() { diff --git a/crates/kimetsu-core/src/config.rs b/crates/kimetsu-core/src/config.rs index bb19538..11eac11 100644 --- a/crates/kimetsu-core/src/config.rs +++ b/crates/kimetsu-core/src/config.rs @@ -875,6 +875,13 @@ pub struct BrokerSection { /// unchanged (off). #[serde(default = "default_abstain_min_score")] pub abstain_min_score: f32, + /// Final cross-encoder admission floor. Scores are model-specific, not + /// calibrated probabilities. Zero disables this floor (not cosine gating). + #[serde( + default = "default_rerank_min_score", + deserialize_with = "deserialize_rerank_min_score" + )] + pub rerank_min_score: f32, /// F3: floor for the adaptive per-stage brain budget. Small tasks /// receive at least this many tokens so the brain is never starved. /// `#[serde(default)]` keeps pre-F3 project.toml files loading cleanly. @@ -1027,6 +1034,23 @@ fn default_answer_grade_min_score() -> f32 { 0.92 } +fn default_rerank_min_score() -> f32 { + 0.30 +} + +fn deserialize_rerank_min_score<'de, D: serde::Deserializer<'de>>( + deserializer: D, +) -> Result { + let value = f32::deserialize(deserializer)?; + if value.is_finite() && (0.0..=1.0).contains(&value) { + Ok(value) + } else { + Err(serde::de::Error::custom( + "rerank_min_score must be finite and between 0 and 1", + )) + } +} + impl Default for BrokerSection { fn default() -> Self { Self { @@ -1038,6 +1062,7 @@ impl Default for BrokerSection { fusion: default_fusion(), normalization: default_normalization(), abstain_min_score: default_abstain_min_score(), + rerank_min_score: default_rerank_min_score(), budget_floor_tokens: default_budget_floor_tokens(), budget_run_cap_tokens: default_budget_run_cap_tokens(), ambient: default_true(), @@ -1428,6 +1453,20 @@ impl Default for LifecycleSection { #[cfg(test)] mod tests { + #[test] + fn rerank_cutoff_survives_configuration_roundtrip_and_rejects_invalid_values() { + let mut value = serde_json::to_value(ProjectConfig::default_for_project("cutoff")).unwrap(); + value["broker"]["rerank_min_score"] = serde_json::json!(0.75); + let config: ProjectConfig = serde_json::from_value(value.clone()).unwrap(); + assert_eq!( + serde_json::to_value(config).unwrap()["broker"]["rerank_min_score"], + 0.75 + ); + for invalid in [-0.1, 1.1] { + value["broker"]["rerank_min_score"] = serde_json::json!(invalid); + assert!(serde_json::from_value::(value.clone()).is_err()); + } + } use super::*; // ── v2.6: Free/Deep tier resolution ────────────────────────────────── From a5e1b6addba3146774f9b0f4c6dd78e0931d976b Mon Sep 17 00:00:00 2001 From: RodCor Date: Mon, 7 Sep 2026 01:25:38 -0300 Subject: [PATCH 24/34] Document corrected retrieval benchmarks and multilingual tradeoffs --- .gitattributes | 2 + docs/audits/2026-09-04-hardening-results.md | 3 + docs/audits/2026-09-07-retrieval-quality.md | 80 + docs/audits/2026-09-07-retrieval/README.md | 13 + .../agent-memory-contract.json | 262 + .../2026-09-07-retrieval/artifact-hashes.json | 36 + .../contract/1-baseline.json | 616 + .../contract/1-candidate.json | 630 + .../contract/2-baseline.json | 616 + .../contract/2-candidate.json | 630 + .../contract/3-baseline.json | 616 + .../contract/3-candidate.json | 630 + .../contract/comparison.json | 237 + .../2026-09-07-retrieval/development-100.json | 1678 ++ .../development-scores.json | 12804 ++++++++++++++++ .../development-sweep.json | 122 + .../development/1-baseline.json | 6811 ++++++++ .../development/1-candidate.json | 6391 ++++++++ .../development/2-baseline.json | 6811 ++++++++ .../development/2-candidate.json | 6391 ++++++++ .../development/3-baseline.json | 6811 ++++++++ .../development/3-candidate.json | 6391 ++++++++ .../development/comparison.json | 189 + .../2026-09-07-retrieval/model-manifest.json | 31 + .../2026-09-07-retrieval/retrieval_probe.rs | 89 + .../2026-09-07-retrieval/run-comparisons.ps1 | 39 + .../strict-contract-earlier-harness.json | 237 + docs/audits/2026-09-07-retrieval/summarize.py | 37 + docs/audits/2026-09-07-retrieval/summary.json | 194 + docs/audits/2026-09-07-retrieval/sweep.py | 17 + .../validation-frozen.json | 813 + .../validation/1-baseline.json | 1781 +++ .../validation/1-candidate.json | 2130 +++ .../validation/2-baseline.json | 1781 +++ .../validation/2-candidate.json | 2130 +++ .../validation/3-baseline.json | 1781 +++ .../validation/3-candidate.json | 2130 +++ .../validation/comparison.json | 213 + 38 files changed, 72173 insertions(+) create mode 100644 .gitattributes create mode 100644 docs/audits/2026-09-07-retrieval-quality.md create mode 100644 docs/audits/2026-09-07-retrieval/README.md create mode 100644 docs/audits/2026-09-07-retrieval/agent-memory-contract.json create mode 100644 docs/audits/2026-09-07-retrieval/artifact-hashes.json create mode 100644 docs/audits/2026-09-07-retrieval/contract/1-baseline.json create mode 100644 docs/audits/2026-09-07-retrieval/contract/1-candidate.json create mode 100644 docs/audits/2026-09-07-retrieval/contract/2-baseline.json create mode 100644 docs/audits/2026-09-07-retrieval/contract/2-candidate.json create mode 100644 docs/audits/2026-09-07-retrieval/contract/3-baseline.json create mode 100644 docs/audits/2026-09-07-retrieval/contract/3-candidate.json create mode 100644 docs/audits/2026-09-07-retrieval/contract/comparison.json create mode 100644 docs/audits/2026-09-07-retrieval/development-100.json create mode 100644 docs/audits/2026-09-07-retrieval/development-scores.json create mode 100644 docs/audits/2026-09-07-retrieval/development-sweep.json create mode 100644 docs/audits/2026-09-07-retrieval/development/1-baseline.json create mode 100644 docs/audits/2026-09-07-retrieval/development/1-candidate.json create mode 100644 docs/audits/2026-09-07-retrieval/development/2-baseline.json create mode 100644 docs/audits/2026-09-07-retrieval/development/2-candidate.json create mode 100644 docs/audits/2026-09-07-retrieval/development/3-baseline.json create mode 100644 docs/audits/2026-09-07-retrieval/development/3-candidate.json create mode 100644 docs/audits/2026-09-07-retrieval/development/comparison.json create mode 100644 docs/audits/2026-09-07-retrieval/model-manifest.json create mode 100644 docs/audits/2026-09-07-retrieval/retrieval_probe.rs create mode 100644 docs/audits/2026-09-07-retrieval/run-comparisons.ps1 create mode 100644 docs/audits/2026-09-07-retrieval/strict-contract-earlier-harness.json create mode 100644 docs/audits/2026-09-07-retrieval/summarize.py create mode 100644 docs/audits/2026-09-07-retrieval/summary.json create mode 100644 docs/audits/2026-09-07-retrieval/sweep.py create mode 100644 docs/audits/2026-09-07-retrieval/validation-frozen.json create mode 100644 docs/audits/2026-09-07-retrieval/validation/1-baseline.json create mode 100644 docs/audits/2026-09-07-retrieval/validation/1-candidate.json create mode 100644 docs/audits/2026-09-07-retrieval/validation/2-baseline.json create mode 100644 docs/audits/2026-09-07-retrieval/validation/2-candidate.json create mode 100644 docs/audits/2026-09-07-retrieval/validation/3-baseline.json create mode 100644 docs/audits/2026-09-07-retrieval/validation/3-candidate.json create mode 100644 docs/audits/2026-09-07-retrieval/validation/comparison.json diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..b5f5b65 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Preserve the exact bytes of reproducible audit artifacts. +docs/audits/2026-09-07-retrieval/** -text whitespace=cr-at-eol diff --git a/docs/audits/2026-09-04-hardening-results.md b/docs/audits/2026-09-04-hardening-results.md index 961d3a1..77682b7 100644 --- a/docs/audits/2026-09-04-hardening-results.md +++ b/docs/audits/2026-09-04-hardening-results.md @@ -1,5 +1,8 @@ # Kimetsu hardening and BrainBenchmark comparison +> Follow-up correction (2026-09-07): the benchmark text matcher undercounted dated, compressed memory deliveries. The retrieval-regression conclusion and affected hit/recall comparisons below are superseded by the [retrieval-quality follow-up](2026-09-07-retrieval-quality.md). Original artifacts are retained for provenance. + + Implementation branches: `codex/brain-hardening` (Kimetsu) and `codex/brain-benchmark-hardening` (the separate benchmark repository). The original `E:/Kimetsu` source and live project/user brains were not changed. The preserved pre-change binary is from `3ec56a8`; all comparisons use the same updated harness on both binaries. This work addresses the concrete defects in the [memory audit](2026-09-04-agent-memory-audit.md) and [model/parameter audit](2026-09-04-models-parameters-memory.md). Local retrieval and rule-based memory processing require no paid generation calls. Returned memory still consumes the receiving agent's context; zero-token information transfer is not an achievable delivery contract. diff --git a/docs/audits/2026-09-07-retrieval-quality.md b/docs/audits/2026-09-07-retrieval-quality.md new file mode 100644 index 0000000..2f43e59 --- /dev/null +++ b/docs/audits/2026-09-07-retrieval-quality.md @@ -0,0 +1,80 @@ +# Retrieval quality follow-up + +The configuration and benchmark fixes are implemented and verified. **Keep TinyBERT as the economical default.** The optional multilingual model recovers Spanish positives, but the frozen test exposes substantially more false injections on unanswerable questions. It is an experimental recall option, not a generally better memory policy. + +The earlier apparent English retrieval regression was largely a benchmark matching defect, not a demonstrated production regression. Separately, diagnostic probes identified relevant memories already in the six-candidate pool whose English TinyBERT scores failed admission; larger pools did not repair those specific cases. Spanish probes exposed a real limitation of that scorer. + +## Changes + +- Optional `mmarco-minilm-l12-v2-int8` reranker, pinned to revision `1427fd652930e4ba29e8149678df786c240d8825`; the alias selects only the quantized ONNX artifact, with no silent full-precision fallback. +- `broker.rerank_min_score` is validated as finite and within [0, 1], defaults to 0.30, and reaches MCP, daemon, canonical evaluation and tuning. Zero disables the final cross-encoder floor; cosine admission gates remain active. +- BrainBenchmark supports separate baseline/candidate cutoff overrides, records them and reads back effective configuration to reject unsupported settings. +- Existing project defaults and installed/live brains remain unchanged. The experimental configuration uses `retrieval.level=custom`, the new alias and cutoff 0.55. + +The [official model card](https://huggingface.co/cross-encoder/mmarco-mMiniLMv2-L12-H384-v1) describes multilingual mMARCO training and Apache-2.0 licensing. The [official ONNX repository](https://huggingface.co/cross-encoder/mmarco-mMiniLMv2-L12-H384-v1/tree/1427fd652930e4ba29e8149678df786c240d8825/onnx) supplies the selected artifact. File sizes and hashes are in `2026-09-07-retrieval/model-manifest.json`; weights were downloaded into an isolated scratch cache. + +## Benchmark defect discovered during production verification + +Chronological rendering adds `[YYYY-MM-DD]` before memory text. When compression also removes the tail, neither the decorated rendered body nor the full fixture text contains the other. The old matcher called these delivered memories `__unmatched__`, understating hit/recall. A regression test reproduced this and the harness now ignores the date decoration when matching visible text, preserving ambiguity as unmatched. Reports also retain delivered capsules for independent audit. Earlier hardening report retrieval comparisons using the affected matcher must not be treated as evidence of a retrieval regression. The interrupted campaign is preserved in scratch artifacts and superseded by fresh runs with the corrected harness. + +## Development selection and mathematics + +The diagnostic uses actual embeddings and a six-document reranking batch, but its threshold sweep is post-processing: it does not re-render every threshold or measure production latency. At 0.90 it predicted 154/197 positive hits and 1/13 negative injections. After the production run exposed the matcher defect, that threshold was rejected before validation: the corrected TinyBERT baseline was much stronger than the old 139/197 count suggested. The existing development sweep at 0.55 predicted 170/197 hits and 5/13 injections, which production MCP confirmed. No labels were changed. + +Hit@4 = positive queries with at least one delivered gold memory / positive queries. Recall@4 averages the fraction of each query's gold memories delivered. MRR averages the reciprocal rank of the first delivered gold memory. Negative injection rate = negative queries receiving any memory / negative queries. A threshold is a model-specific score, not a calibrated probability. + +The optional equal-class selection score is `(Hit@4 + 1 - negative_injection_rate) / 2`; it gives equal weight to the two query classes despite the development set's 197:13 imbalance. It does not express real deployment costs. More generally choose a threshold against `C_miss * P(positive) * miss_rate + C_noise * P(negative) * injection_rate`, with costs and class frequencies from deployment. Thirteen negative queries are too few for a precise false-injection estimate. Repeating deterministic queries measures runtime variation, not additional independent accuracy evidence. + +The historical negative labels were preserved, including ambiguous questions about benchmark quality and tokenizer customization. Do not relabel them to improve scores. + +## Frozen validation protocol + +`validation-frozen.json` SHA256 `1a09270e09a9521c38f9dca14dfebe4349fed0e10b1537a92857eb18d3f6bb6f` was frozen before production policy changes and before inference on it. Four assistant-authored synthetic project families contain 48 positive queries (24 English, 24 Spanish), 24 missing-attribute negatives, and expired/future facts. This is held-out synthetic coverage, not independent real-repository or agent-task certification. The initial development choice 0.90 was rejected before validation after production testing exposed a benchmark text-matching defect. The corrected candidate cutoff 0.55 was chosen from the same development sweep to preserve more positive recall. Frozen validation was first run after cutoff 0.55 and the corrected harness were fixed. + +Completed paired production runs used the previously hardened binary versus this candidate, identical BGE-small embeddings, six rerank candidates, cap four, warm-start/ambient/global brain disabled, default thread count, and three alternating repetitions. Contract budget is 2048 tokens; development and frozen validation budgets are 6000. No builds or other owned inference ran during timing. Each comparison fingerprints binaries, runner, harness and fixture inputs. + +## Applying the optional configuration + +For an explicit experiment requiring multilingual recall and accepting the measured false-injection risk, use an embeddings-enabled candidate binary in the intended project: + +```powershell +kimetsu config set retrieval.level custom +kimetsu config set embedder.reranker mmarco-minilm-l12-v2-int8 +kimetsu config set broker.rerank_min_score 0.55 +``` + +The first model load requires the pinned local weights or their download. The selected ONNX file is 118,620,016 bytes; runtime memory is measured separately. These commands have not been applied to the user's live project. + +## Results and remaining limits + +Three alternating repetitions per campaign produced **1,824 query observations over 304 distinct fixture queries**. All comparisons completed with zero scenario errors and no unpaired scenarios. Rankings were identical across repetitions; repeats do not increase the independent accuracy sample size. + +| Dataset | Positive hits: Tiny → multilingual | False injections: Tiny → multilingual | Stale injections | +|---|---:|---:|---:| +| Contract, 2048 budget | 8/11 → 11/11 | 2/11 → 1/11 | 0/2 → 0/2 | +| Development, 6000 budget | 169/197 → 170/197 | 7/13 → 5/13 | Not tested | +| Frozen synthetic validation, 6000 budget | 24/48 → 48/48 | 12/24 → 19/24 | 0/8 → 0/8 | + +Frozen validation English positives were 24/24 for both models; Spanish positives were 0/24 → 24/24. English missing-fact injections were 12/12 → 8/12; Spanish missing-fact injections were 0/12 → 11/12. TinyBERT's Spanish abstention also discarded every answerable Spanish query: it does not establish language-aware answerability. The multilingual model recognizes related topics but too often injects facts that do not answer the requested attribute. Neither scorer alone solves that problem. + +| Dataset | Subsequent-query p50, ms | Subsequent-query p95, ms | Peak MCP MiB | +|---|---:|---:|---:| +| Contract | 429 → 445 | 802 → 470 | 237 → 653 | +| Development | 970 → 1336 | 1107 → 1768 | 281 → 955 | +| Frozen validation | 324 → 369 | 355 → 401 | 237 → 653 | + +The contract p95 varies strongly across small runs; its reversed ordering is not evidence that the larger model is generally faster. On the larger development workload the candidate's p95 was about 60% higher and peak memory about 3.4 times higher. Development mean MCP result size fell from 1262 to 1112 bytes. First-query mean latency, including model loading where applicable, rose from 1200 to 2615 ms. These are measurements on this Windows machine, not portable service-level guarantees or million-memory capacity results. + +Development positive changes were six gains and five losses. That modest net gain, observed on data used to choose the threshold, is not independent evidence of general superiority. On the frozen fixture, ignoring runtime and using its query frequencies, the miss/noise costs are `24*C_miss + 12*C_noise` for TinyBERT and `19*C_noise` for the candidate. The candidate wins that specific cost model only when `C_noise < (24/7)*C_miss`; this is not a calibrated threshold rule or an estimate of real production frequencies. Four related synthetic project templates are not 72 independent real-world tasks. + +**Decision:** retain current defaults; expose the tested model and threshold as explicit experimental controls. Do not present the multilingual option as an abstention fix. The remaining substantive gap is an answerability check that distinguishes a requested fact from a merely related topic, supported by independently evaluated structured claims or another validated verifier. The frozen results are reported as a failed abstention criterion, not tuned away. + +No zero-context-token claim: local retrieval needs no paid generation calls, but memories supplied to the agent still occupy its context. Large-corpus capacity, independent agent-task success, and richer claim extraction remain unverified by this follow-up. + +## Verification and provenance + +- Production source `41ff583`: full workspace **1373 passed, 0 failed, 5 ignored**; embeddings-enabled release built successfully. Core config and actual MCP cutoff tests were observed failing before the fix, then passing. The new model alias failed on the old binary and loaded successfully on the candidate from the pinned isolated cache. +- Benchmark source `3c275f6` (includes `e6cfc3c`): **131 Rust and 17 Python tests passed**, release built. The dated/compressed matcher test was observed failing before the fix. An actual old binary was rejected when it could not report the requested effective cutoff. Independent code review found no remaining blocking issues. +- Candidate SHA256: `2445e8adc06c4a6b59d5ee51be46a6f5aa6e777514dfbb60ac4b136b69ba0ba6`. Baseline SHA256: `5c87e542907a47917f23fad50ddff1e46789eaae14328ccc662ca748b66b5477`. +- Full input/harness/runner fingerprints, per-query delivered capsules and runtime measurements are under [the artifact directory](2026-09-07-retrieval/README.md). `summarize.py` reproduces the saved cohort summary without inference; `run-comparisons.ps1` reruns the campaign with explicit binaries and caches. +- Changes are committed in the isolated audit worktrees. The original source checkout, installed daemon, live configuration and live brains were not changed. No push, merge or installation was performed. diff --git a/docs/audits/2026-09-07-retrieval/README.md b/docs/audits/2026-09-07-retrieval/README.md new file mode 100644 index 0000000..e452a76 --- /dev/null +++ b/docs/audits/2026-09-07-retrieval/README.md @@ -0,0 +1,13 @@ +# Retrieval comparison artifacts + +Production source: `41ff583`; benchmark source: `3c275f6` (includes effective-cutoff readback and dated/compressed memory matching). + +`development-scores.json` and `development-sweep.json` are diagnostic development evidence. Run `python sweep.py` to reproduce the score sweep; this does not rerun rendering or measure runtime. `retrieval_probe.rs` is the throwaway diagnostic source, retained for audit and intentionally excluded from normal Cargo builds. + +`validation-frozen.json` is the untouched assistant-authored synthetic validation set. Its SHA256 is `1a09270e09a9521c38f9dca14dfebe4349fed0e10b1537a92857eb18d3f6bb6f`. The final candidate threshold is 0.55, chosen before inference on validation. An earlier 0.90 development campaign was interrupted after identifying the text-matching bug; it was never used to tune against validation. + +Run `run-comparisons.ps1` with explicit `-Baseline`, `-Candidate`, `-Harness`, `-ModelCache`, `-HfHome`, and a new `-OutputRoot`. The baseline is the preceding hardened binary; candidate uses the optional multilingual alias and cutoff 0.55. ModelCache holds BGE-small; HfHome holds the TinyBERT and pinned mMARCO caches. Keep builds and other inference out of the timing window. The script runs all three fixtures with three alternating repetitions, checks frozen fixture identity, and rejects scenario errors or unpaired results. + +The model weights are not committed. `model-manifest.json` records their source revision, sizes and hashes. Both model options run locally without generation API calls. Injected memories still consume agent context. + +Final production comparisons and all per-query reports are in `contract/`, `development/`, and `validation/`. Run `python summarize.py` to reproduce `summary.json`. `strict-contract-earlier-harness.json` preserves the earlier 0.90 contract-only experiment; it is not part of the corrected 0.55 campaign. Delivered capsule text is synthetic/development fixture content and is preserved to audit matching, compression and ordering. Latency measurements stop before artifact serialization. Repeated quality measurements are averaged per query, not treated as independent samples. diff --git a/docs/audits/2026-09-07-retrieval/agent-memory-contract.json b/docs/audits/2026-09-07-retrieval/agent-memory-contract.json new file mode 100644 index 0000000..be4c897 --- /dev/null +++ b/docs/audits/2026-09-07-retrieval/agent-memory-contract.json @@ -0,0 +1,262 @@ +{ + "scenarios": [ + { + "id": "exact-code-evidence", + "dimension": "retrieval", + "tier": "easy", + "description": "Exact identifiers, multi-fact recall, and unrelated no-answer queries.", + "memories": [ + { + "key": "stderr", + "text": "MCP diagnostic logs must go to stderr. Stdout is reserved for JSON-RPC messages." + }, + { + "key": "foreign-keys", + "text": "Enable SQLite foreign key constraints explicitly on every new connection using PRAGMA foreign_keys = ON." + }, + { + "key": "blocking", + "text": "Move long synchronous operations into tokio::task::spawn_blocking instead of blocking asynchronous executor threads." + }, + { + "key": "test-isolation", + "text": "Initialize a Git boundary in each temporary test workspace so repository discovery cannot write to the real project." + } + ], + "queries": [ + { + "query": "Where must MCP diagnostic logs be written?", + "relevant": [ + "stderr" + ] + }, + { + "query": "How do I enable SQLite foreign key constraints on a new connection?", + "relevant": [ + "foreign-keys" + ] + }, + { + "query": "How should I handle a long synchronous operation in the Tokio executor?", + "relevant": [ + "blocking" + ] + }, + { + "query": "What is the weather forecast for tomorrow?", + "relevant": [] + }, + { + "query": "What is the current production database password?", + "relevant": [] + } + ] + }, + { + "id": "related-but-unanswerable", + "dimension": "retrieval", + "tier": "hard", + "description": "Related vocabulary must not imply that a stored fact answers a different question.", + "memories": [ + { + "key": "http-port", + "text": "The local development HTTP server listens on port 4317." + }, + { + "key": "storage", + "text": "The project stores local agent memories in a SQLite database." + }, + { + "key": "logs", + "text": "Development request logs include the route and elapsed milliseconds." + } + ], + "queries": [ + { + "query": "Which port does the local development HTTP server use?", + "relevant": [ + "http-port" + ] + }, + { + "query": "What authentication password does the local development HTTP server require?", + "relevant": [] + }, + { + "query": "Which encryption key protects the SQLite database?", + "relevant": [] + }, + { + "query": "What is the production request log retention duration?", + "relevant": [] + } + ] + }, + { + "id": "cross-language-code", + "dimension": "retrieval", + "tier": "hard", + "description": "Small exploratory Spanish query track against English code memories.", + "memories": [ + { + "key": "stderr", + "text": "Write MCP diagnostic messages to stderr because stdout carries the JSON-RPC protocol." + }, + { + "key": "foreign-keys", + "text": "SQLite foreign key enforcement must be enabled separately for every connection with PRAGMA foreign_keys = ON." + }, + { + "key": "blocking", + "text": "Use tokio::task::spawn_blocking for long synchronous work inside an asynchronous application." + } + ], + "queries": [ + { + "query": "¿Dónde deben escribirse los mensajes de diagnóstico MCP para no romper JSON-RPC?", + "relevant": [ + "stderr" + ] + }, + { + "query": "¿Cómo se activan las claves foráneas de SQLite en cada conexión?", + "relevant": [ + "foreign-keys" + ] + }, + { + "query": "¿Cómo ejecuto trabajo síncrono largo sin bloquear Tokio?", + "relevant": [ + "blocking" + ] + }, + { + "query": "¿Cuál es el precio actual de la electricidad?", + "relevant": [] + } + ] + }, + { + "id": "multi-fact-retrieval", + "dimension": "retrieval", + "tier": "medium", + "description": "Fraction recall requires both expected facts; finding either is only a hit.", + "memories": [ + { + "key": "git-boundary", + "text": "For safe temporary memory tests, run git init in the temporary workspace to establish a repository boundary." + }, + { + "key": "user-brain", + "text": "For isolated memory tests, set KIMETSU_USER_BRAIN=0 to prevent cross-project memory leakage." + }, + { + "key": "release", + "text": "Release packaging produces a compressed archive containing the executable." + } + ], + "queries": [ + { + "query": "How do temporary memory tests establish a Git boundary and prevent user brain leakage?", + "relevant": [ + "git-boundary", + "user-brain" + ] + }, + { + "query": "Which executable signing certificate is used for releases?", + "relevant": [] + } + ] + }, + { + "id": "live-temporal-applicability", + "dimension": "retrieval", + "tier": "hard", + "description": "Future starts and expired claims must not appear in current context; these are storage validity fields, not text hints.", + "memories": [ + { + "key": "expired", + "text": "The Atlas staging listener uses port 4001.", + "valid_to": "2020-01-01T00:00:00Z" + }, + { + "key": "future", + "text": "The Atlas staging listener uses port 4003.", + "valid_from": "2099-01-01T00:00:00Z" + }, + { + "key": "current", + "text": "The Atlas staging listener uses port 4002." + } + ], + "queries": [ + { + "query": "Which port does the Atlas staging listener currently use?", + "relevant": [ + "current" + ], + "stale": [ + "expired", + "future" + ] + }, + { + "query": "What authentication password does the Atlas staging listener require?", + "relevant": [], + "stale": [ + "expired", + "future" + ] + } + ] + }, + { + "id": "persistent-mcp-observes-new-writes", + "dimension": "workflow", + "tier": "medium", + "description": "A persistent MCP process must see new claims written by another process, while related unknowns remain unanswered.", + "workflow": { + "seed": [], + "episodes": [ + { + "task": "Which port does the Zephyr local development HTTP server use?", + "relevant": [], + "record": [ + { + "key": "port", + "text": "The Zephyr local development HTTP server listens on port 5243." + } + ] + }, + { + "task": "Which port does the Zephyr local development HTTP server use?", + "relevant": [ + "port" + ] + }, + { + "task": "What authentication password does the Zephyr HTTP server require?", + "relevant": [] + }, + { + "task": "Where should MCP diagnostic logs be written?", + "relevant": [], + "record": [ + { + "key": "logs", + "text": "MCP diagnostic logs go to stderr; stdout is reserved for JSON-RPC messages." + } + ] + }, + { + "task": "Where should MCP diagnostic logs be written?", + "relevant": [ + "logs" + ] + } + ] + } + } + ] +} diff --git a/docs/audits/2026-09-07-retrieval/artifact-hashes.json b/docs/audits/2026-09-07-retrieval/artifact-hashes.json new file mode 100644 index 0000000..0efaf0b --- /dev/null +++ b/docs/audits/2026-09-07-retrieval/artifact-hashes.json @@ -0,0 +1,36 @@ +{ + "agent-memory-contract.json": "ef30967945d2230f84539f2c81d275f00c48765b5f8850a44068948f6825ace4", + "contract/1-baseline.json": "da2cfba1aeeb9f612bdb744228351ae9937552dbb218966fc72b0a74af79b24a", + "contract/1-candidate.json": "23f1187176c60f37eea671f4106a6eba550341cf98e23c118b814f96f724a9ae", + "contract/2-baseline.json": "ee3c642a554ead9950855bf6ef37f77f835b8812cb17552da40e9e3c2b1a6e4f", + "contract/2-candidate.json": "9bf1f7317934f6f7cd5591759297354e75dbe02633cef98a0a9a3a50ae7c8e0d", + "contract/3-baseline.json": "681ebe3d788bc0600475747f68380fdf106afc70c42ce9ebdf3fcabc2dc74fef", + "contract/3-candidate.json": "518dee30f5478026ca6dea2fd02314e9771a35d4a350958df08a6e7bb60e88fe", + "contract/comparison.json": "ee3c539fa9b39f584fb2514decfc600d4a9bda81d5ee4f2e606af0ce2755f555", + "development/1-baseline.json": "99081dc50791b48f1f0b30afe49f5435355618a59376574bcf1ca19a5e7a3430", + "development/1-candidate.json": "4050c01afdd70f8a67dbbc8319fda6d771bda4760b4cc82c22d6e9c470ddd9ae", + "development/2-baseline.json": "6ffd354f7de026011e27ffab9401a4eec986a6ed882152e6a9dcb1ab6692d4ee", + "development/2-candidate.json": "352146ff9e1ba4efc2eece0fc8d1d375c0e4d6e23b4a71a0ae01b8d802e85d3b", + "development/3-baseline.json": "1c6615fcb7c938abd9e58dfef726c90b3574e243270c6f3c4d1555ed67fbdee8", + "development/3-candidate.json": "ae6822ab01d6fd607c5f8d2fcbd1cc3fd3d298b729f5b635c5cb1b7e2b6425e9", + "development/comparison.json": "1a6a7fa3a304afd722bb6e56804db831cf577fc5d238bc85aee7fe4262622450", + "development-100.json": "ff3c78f8b5dab7e9b2f10894af93f07965705502a4f9d3e8c45c529b9b6ca33f", + "development-scores.json": "1731dbae588a8bf67f165ee462ecb5209b210fbb82b48efe53bfc60c5e78b01d", + "development-sweep.json": "e4c3828c1997f714dc9dbc72a2427a153a7ed1285ed84ec6c5e2ce6b1cdaa0ae", + "model-manifest.json": "d4b551d4ba71a83a511cd19869c0f9c69265b8e023288b0d43f0e3999fb99a00", + "README.md": "321e9d2cf47ea616a8ae9a083cf226a725380dcc1d3934f3db2f96c83d30bc84", + "retrieval_probe.rs": "de00aaab2ec1035afed93d110b79360452d74612d9d75c9d618b84730d4f9bbe", + "run-comparisons.ps1": "22c7b5b71577e629cc85f2b516966698fc427480d05ec979285d6d276f8b5c05", + "strict-contract-earlier-harness.json": "c2c9927b7fd1201c1227ce3da85c456a25816a95433d3f176bd3c597efb7ccb4", + "summarize.py": "67c842dbe63af784f79fa559876d79b36e3a1c44b9ee592867f8fa77f0e396b9", + "summary.json": "a5b0d36db4aec8e6e43eac1ed840805b54ac2c8c99782d3dcbb3698c3502b540", + "sweep.py": "8cf7383f03d3d31bd9f1d60551e990194a8f2f10871e30034a4319e883880d61", + "validation/1-baseline.json": "934fde4676d6cb75d83dda177688be5e7ba4a28fa5047ee714dda566ff0e9649", + "validation/1-candidate.json": "810574c0dbca71e0dbd603a34fde2845105cdaa5a3b5afd72d73aee45cf464dc", + "validation/2-baseline.json": "9b931bc346abcad496caea238c6c646a77f817ff96caaa5c24e8f35fe4401280", + "validation/2-candidate.json": "d98a0358378ec1cc82d5b8765e15a81cbf520a076bbefa9568b91b0100ab5024", + "validation/3-baseline.json": "7bcc26894bc772fb9da1354503d10b6f1cfe126e935da438c24aebd059fccb8d", + "validation/3-candidate.json": "1a546dd9fedfe20ab9ccca7e5a2f3216455b8285753b32e9328537f5217c94bb", + "validation/comparison.json": "c5c991f8cf5a257c3dda4c01cce9ffba2d74adc646052b90cd65121f6c264dcc", + "validation-frozen.json": "1a09270e09a9521c38f9dca14dfebe4349fed0e10b1537a92857eb18d3f6bb6f" +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-retrieval/contract/1-baseline.json b/docs/audits/2026-09-07-retrieval/contract/1-baseline.json new file mode 100644 index 0000000..d3c6ec7 --- /dev/null +++ b/docs/audits/2026-09-07-retrieval/contract/1-baseline.json @@ -0,0 +1,616 @@ +{ + "generated_at": "2026-09-07T03:50:44.97943Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\bench\\datasets\\brainbench\\agent-memory-contract.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "Where must MCP diagnostic logs be written?", + "ranked": [ + "stderr" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZNS2Y1PCDFQGSR0ST7HCY", + "id": "01M1WZNT0N6PH40DDBAS9E4CMJ", + "kind": "memory", + "score": 0.9994274377822876, + "summary": "project:fact - MCP diagnostic logs must go to stderr. Stdout is reserved for JSON-RPC messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 868.8719, + "first_query": true, + "server_startup_ms": 94.66149999999999, + "model_text_bytes": 468, + "mcp_result_bytes": 549, + "wire_bytes": 584, + "reported_used_tokens": 549, + "working_set_bytes": 214622208, + "peak_working_set_bytes": 248340480 + }, + { + "query": "How do I enable SQLite foreign key constraints on a new connection?", + "ranked": [ + "foreign-keys" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZNS3HWWWWP4TQJ8JE9F3C", + "id": "01M1WZNTEXWTG08Z8ENKNX5T44", + "kind": "memory", + "score": 0.9999536275863647, + "summary": "project:fact - Enable SQLite foreign key constraints explicitly on every new connection using PRAGMA foreign_keys = ON." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 429.5845, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 609, + "reported_used_tokens": 574, + "working_set_bytes": 215420928, + "peak_working_set_bytes": 248340480 + }, + { + "query": "How should I handle a long synchronous operation in the Tokio executor?", + "ranked": [ + "blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZNS3YT2ZMPQKX71B0A890", + "id": "01M1WZNTVTN5258Y5124K0BH5W", + "kind": "memory", + "score": 0.982628345489502, + "summary": "project:fact - Move long synchronous operations into tokio::task::spawn_blocking instead of blocking asynchronous executor threads." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 418.1653, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 503, + "mcp_result_bytes": 584, + "wire_bytes": 619, + "reported_used_tokens": 584, + "working_set_bytes": 215609344, + "peak_working_set_bytes": 248340480 + }, + { + "query": "What is the weather forecast for tomorrow?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 427.3685, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 215625728, + "peak_working_set_bytes": 248340480 + }, + { + "query": "What is the current production database password?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 447.70959999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 215638016, + "peak_working_set_bytes": 248340480 + } + ], + "id": "exact-code-evidence", + "dimension": "retrieval", + "tier": "easy", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.000 (n=2) positive-n=3 negative-n=2 (5 queries)" + }, + { + "observations": [ + { + "query": "Which port does the local development HTTP server use?", + "ranked": [ + "http-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZNX2473RYS4HMQQQ806TR", + "id": "01M1WZNXXS3YVP97AE31GZFRNH", + "kind": "memory", + "score": 0.9998925924301147, + "summary": "project:fact - The local development HTTP server listens on port 4317." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 849.1672, + "first_query": true, + "server_startup_ms": 95.20349999999999, + "model_text_bytes": 444, + "mcp_result_bytes": 525, + "wire_bytes": 560, + "reported_used_tokens": 525, + "working_set_bytes": 213925888, + "peak_working_set_bytes": 248291328 + }, + { + "query": "What authentication password does the local development HTTP server require?", + "ranked": [ + "http-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZNX2473RYS4HMQQQ806TR", + "id": "01M1WZNYBSPANYHDQ4Z55911HN", + "kind": "memory", + "score": 0.5794959664344788, + "summary": "project:fact - The local development HTTP server listens on port 4317." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 445.41720000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 443, + "mcp_result_bytes": 524, + "wire_bytes": 559, + "reported_used_tokens": 524, + "working_set_bytes": 213983232, + "peak_working_set_bytes": 248291328 + }, + { + "query": "Which encryption key protects the SQLite database?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 450.1351, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 214065152, + "peak_working_set_bytes": 248291328 + }, + { + "query": "What is the production request log retention duration?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 744.0048, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 214278144, + "peak_working_set_bytes": 248291328 + } + ], + "id": "related-but-unanswerable", + "dimension": "retrieval", + "tier": "hard", + "score": 0.75, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.333 (n=3) positive-n=1 negative-n=3 (4 queries)" + }, + { + "observations": [ + { + "query": "\u00bfD\u00f3nde deben escribirse los mensajes de diagn\u00f3stico MCP para no romper JSON-RPC?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 3534.4887, + "first_query": true, + "server_startup_ms": 117.7078, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 215130112, + "peak_working_set_bytes": 248401920 + }, + { + "query": "\u00bfC\u00f3mo se activan las claves for\u00e1neas de SQLite en cada conexi\u00f3n?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 896.4107, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 215187456, + "peak_working_set_bytes": 248401920 + }, + { + "query": "\u00bfC\u00f3mo ejecuto trabajo s\u00edncrono largo sin bloquear Tokio?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 642.9631999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 215203840, + "peak_working_set_bytes": 248401920 + }, + { + "query": "\u00bfCu\u00e1l es el precio actual de la electricidad?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 801.7253, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 215203840, + "peak_working_set_bytes": 248401920 + } + ], + "id": "cross-language-code", + "dimension": "retrieval", + "tier": "hard", + "score": 0.25, + "skipped": false, + "detail": "positive-recall@4=0.00 mrr=0.00 stale-hit=n/a resolution=n/a false-injection=0.000 (n=1) positive-n=3 negative-n=1 (4 queries)" + }, + { + "observations": [ + { + "query": "How do temporary memory tests establish a Git boundary and prevent user brain leakage?", + "ranked": [ + "git-boundary", + "user-brain" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZRZF6KTPVWXEEYZRBV1AD", + "id": "01M1WZS15796QGAHH2507DT5P8", + "kind": "memory", + "score": 0.9979111552238464, + "summary": "project:fact - For safe temporary memory tests, run git init in the temporary workspace to establish a repository boundary." + }, + { + "expansion_handle": "memory:01M1WZRZKGXC3MSQHBW4632EG1", + "id": "01M1WZS1573C3GEGPDFQSB6EPX", + "kind": "memory", + "score": 0.9784100651741028, + "summary": "project:fact - For isolated memory tests, set KIMETSU_USER_BRAIN=0 to prevent cross-project memory leakage." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1448.8267, + "first_query": true, + "server_startup_ms": 121.64139999999999, + "model_text_bytes": 751, + "mcp_result_bytes": 850, + "wire_bytes": 885, + "reported_used_tokens": 850, + "working_set_bytes": 214351872, + "peak_working_set_bytes": 248426496 + }, + { + "query": "Which executable signing certificate is used for releases?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 525.3429, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 214618112, + "peak_working_set_bytes": 248426496 + } + ], + "id": "multi-fact-retrieval", + "dimension": "retrieval", + "tier": "medium", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.000 (n=1) positive-n=1 negative-n=1 (2 queries)" + }, + { + "observations": [ + { + "query": "Which port does the Atlas staging listener currently use?", + "ranked": [ + "current" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZS4353NCXPXGW4SED2NC5", + "id": "01M1WZS4ZFBJBC9CE2B89G7XRD", + "kind": "memory", + "score": 0.9999620914459229, + "summary": "project:fact - The Atlas staging listener uses port 4002." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1079.1604, + "first_query": true, + "server_startup_ms": 108.3661, + "model_text_bytes": 430, + "mcp_result_bytes": 511, + "wire_bytes": 546, + "reported_used_tokens": 511, + "working_set_bytes": 212828160, + "peak_working_set_bytes": 248377344 + }, + { + "query": "What authentication password does the Atlas staging listener require?", + "ranked": [ + "current" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZS4353NCXPXGW4SED2NC5", + "id": "01M1WZS5N2CPF46F3R75QWS05D", + "kind": "memory", + "score": 0.9969580173492432, + "summary": "project:fact - The Atlas staging listener uses port 4002." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": false, + "latency_ms": 480.9538, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 430, + "mcp_result_bytes": 511, + "wire_bytes": 546, + "reported_used_tokens": 511, + "working_set_bytes": 212926464, + "peak_working_set_bytes": 248377344 + } + ], + "id": "live-temporal-applicability", + "dimension": "retrieval", + "tier": "hard", + "score": 0.5, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=0.500 (n=2) false-injection=1.000 (n=1) positive-n=1 negative-n=1 (2 queries)" + }, + { + "observations": [ + { + "query": "Which port does the Zephyr local development HTTP server use?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 913.3221, + "first_query": true, + "server_startup_ms": 117.1071, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 212299776, + "peak_working_set_bytes": 248115200 + }, + { + "query": "Which port does the Zephyr local development HTTP server use?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZTQ0EHBCB2M8R6H8XRNMR", + "id": "01M1WZTQDJ7RVMWP06RA89SG3Q", + "kind": "memory", + "score": 0.9999316930770874, + "summary": "project:fact - The Zephyr local development HTTP server listens on port 5243." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 490.6567, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 451, + "mcp_result_bytes": 532, + "wire_bytes": 567, + "reported_used_tokens": 532, + "working_set_bytes": 213024768, + "peak_working_set_bytes": 248115200 + }, + { + "query": "What authentication password does the Zephyr HTTP server require?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 441.9486, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 213028864, + "peak_working_set_bytes": 248115200 + }, + { + "query": "Where should MCP diagnostic logs be written?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 438.08770000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 213032960, + "peak_working_set_bytes": 248115200 + }, + { + "query": "Where should MCP diagnostic logs be written?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZTRYFQG6BDT0WY50A190V", + "id": "01M1WZTSA4ZRB2VR8SXASAS4K5", + "kind": "memory", + "score": 0.9979764819145204, + "summary": "project:fact - MCP diagnostic logs go to stderr; stdout is reserved for JSON-RPC messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 436.7574, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 463, + "mcp_result_bytes": 544, + "wire_bytes": 579, + "reported_used_tokens": 544, + "working_set_bytes": 214114304, + "peak_working_set_bytes": 248115200 + } + ], + "id": "persistent-mcp-observes-new-writes", + "dimension": "workflow", + "tier": "medium", + "score": 1.0, + "skipped": false, + "detail": "useful-hit=1.00 mrr=1.00 false-inj=0.00 trap-hit=n/a resolution=n/a curve=1.00\u21921.00 (5 episodes: 2 gold, 3 abstention; brain=2 mems)" + } + ], + "by_dimension_tier": { + "retrieval/easy": [ + 1.0, + 1 + ], + "retrieval/hard": [ + 1.5, + 3 + ], + "retrieval/medium": [ + 1.0, + 1 + ], + "workflow/medium": [ + 1.0, + 1 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 0.7, + "n": 5, + "ci95": 0.2857166428474197 + }, + "workflow": { + "mean": 1.0, + "n": 1, + "ci95": null + } + }, + "overall_index": 0.85, + "scenario_weighted_index": 0.75 +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-retrieval/contract/1-candidate.json b/docs/audits/2026-09-07-retrieval/contract/1-candidate.json new file mode 100644 index 0000000..2d862f0 --- /dev/null +++ b/docs/audits/2026-09-07-retrieval/contract/1-candidate.json @@ -0,0 +1,630 @@ +{ + "generated_at": "2026-09-07T03:51:15.9695372Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\bench\\datasets\\brainbench\\agent-memory-contract.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "Where must MCP diagnostic logs be written?", + "ranked": [ + "stderr" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZTV1ZVAPZBAJDCV2JFP8T", + "id": "01M1WZTXCYTQZ7D5VHHZ9YWZJT", + "kind": "memory", + "score": 0.9986107349395752, + "summary": "project:fact - MCP diagnostic logs must go to stderr. Stdout is reserved for JSON-RPC messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2356.9606, + "first_query": true, + "server_startup_ms": 102.2836, + "model_text_bytes": 468, + "mcp_result_bytes": 549, + "wire_bytes": 584, + "reported_used_tokens": 549, + "working_set_bytes": 629903360, + "peak_working_set_bytes": 684896256 + }, + { + "query": "How do I enable SQLite foreign key constraints on a new connection?", + "ranked": [ + "foreign-keys" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZTV2H4B33HA305CYW8MB2", + "id": "01M1WZTXVA9SS8SDYCY9E2TTD0", + "kind": "memory", + "score": 0.999981164932251, + "summary": "project:fact - Enable SQLite foreign key constraints explicitly on every new connection using PRAGMA foreign_keys = ON." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 454.8856, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 632070144, + "peak_working_set_bytes": 684896256 + }, + { + "query": "How should I handle a long synchronous operation in the Tokio executor?", + "ranked": [ + "blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZTV2Z5HD9KE5R8EMHJQ90", + "id": "01M1WZTY9ZEK2X14DMB3M5AS7Z", + "kind": "memory", + "score": 0.9949350953102112, + "summary": "project:fact - Move long synchronous operations into tokio::task::spawn_blocking instead of blocking asynchronous executor threads." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 479.6293, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 504, + "mcp_result_bytes": 585, + "wire_bytes": 620, + "reported_used_tokens": 585, + "working_set_bytes": 632602624, + "peak_working_set_bytes": 684896256 + }, + { + "query": "What is the weather forecast for tomorrow?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 447.7583, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 632639488, + "peak_working_set_bytes": 684896256 + }, + { + "query": "What is the current production database password?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 470.27200000000005, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 632672256, + "peak_working_set_bytes": 684896256 + } + ], + "id": "exact-code-evidence", + "dimension": "retrieval", + "tier": "easy", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.000 (n=2) positive-n=3 negative-n=2 (5 queries)" + }, + { + "observations": [ + { + "query": "Which port does the local development HTTP server use?", + "ranked": [ + "http-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZV0RW98W8F7FBK8QZB2WD", + "id": "01M1WZV2WVXGW9TZD2S7G9YW6Y", + "kind": "memory", + "score": 0.9999812841415404, + "summary": "project:fact - The local development HTTP server listens on port 4317." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2136.8434, + "first_query": true, + "server_startup_ms": 97.3645, + "model_text_bytes": 444, + "mcp_result_bytes": 525, + "wire_bytes": 560, + "reported_used_tokens": 525, + "working_set_bytes": 630190080, + "peak_working_set_bytes": 685039616 + }, + { + "query": "What authentication password does the local development HTTP server require?", + "ranked": [ + "http-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZV0RW98W8F7FBK8QZB2WD", + "id": "01M1WZV3BFDGZ6P8TT075BJKF5", + "kind": "memory", + "score": 0.7880221605300903, + "summary": "project:fact - The local development HTTP server listens on port 4317." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 447.4881, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 443, + "mcp_result_bytes": 524, + "wire_bytes": 559, + "reported_used_tokens": 524, + "working_set_bytes": 631746560, + "peak_working_set_bytes": 685039616 + }, + { + "query": "Which encryption key protects the SQLite database?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 433.30400000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 631799808, + "peak_working_set_bytes": 685039616 + }, + { + "query": "What is the production request log retention duration?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 437.8376, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 631816192, + "peak_working_set_bytes": 685039616 + } + ], + "id": "related-but-unanswerable", + "dimension": "retrieval", + "tier": "hard", + "score": 0.75, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.333 (n=3) positive-n=1 negative-n=3 (4 queries)" + }, + { + "observations": [ + { + "query": "\u00bfD\u00f3nde deben escribirse los mensajes de diagn\u00f3stico MCP para no romper JSON-RPC?", + "ranked": [ + "stderr" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZV5Q9S4Z4MWBWXRW815ZY", + "id": "01M1WZV7XV88ZSWSHQ5APGCMSE", + "kind": "memory", + "score": 0.9584800601005554, + "summary": "project:fact - Write MCP diagnostic messages to stderr because stdout carries the JSON-RPC protocol." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2226.572, + "first_query": true, + "server_startup_ms": 97.6527, + "model_text_bytes": 473, + "mcp_result_bytes": 554, + "wire_bytes": 589, + "reported_used_tokens": 554, + "working_set_bytes": 631300096, + "peak_working_set_bytes": 684892160 + }, + { + "query": "\u00bfC\u00f3mo se activan las claves for\u00e1neas de SQLite en cada conexi\u00f3n?", + "ranked": [ + "foreign-keys" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZV5QXSYFAX6DNW0WD081B", + "id": "01M1WZV8CJQWR5VKCM29C8P1EF", + "kind": "memory", + "score": 0.993928074836731, + "summary": "project:fact - SQLite foreign key enforcement must be enabled separately for every connection with PRAGMA foreign_keys = ON." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 457.5797, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 496, + "mcp_result_bytes": 577, + "wire_bytes": 612, + "reported_used_tokens": 577, + "working_set_bytes": 632168448, + "peak_working_set_bytes": 684892160 + }, + { + "query": "\u00bfC\u00f3mo ejecuto trabajo s\u00edncrono largo sin bloquear Tokio?", + "ranked": [ + "blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZV5RB0RZ8RNB6CH88733E", + "id": "01M1WZV8TNZG0FCCNZFD9AH7K9", + "kind": "memory", + "score": 0.9620424509048462, + "summary": "project:fact - Use tokio::task::spawn_blocking for long synchronous work inside an asynchronous application." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 459.3469, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 481, + "mcp_result_bytes": 562, + "wire_bytes": 597, + "reported_used_tokens": 562, + "working_set_bytes": 632254464, + "peak_working_set_bytes": 684892160 + }, + { + "query": "\u00bfCu\u00e1l es el precio actual de la electricidad?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 434.1946, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 632274944, + "peak_working_set_bytes": 684892160 + } + ], + "id": "cross-language-code", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.000 (n=1) positive-n=3 negative-n=1 (4 queries)" + }, + { + "observations": [ + { + "query": "How do temporary memory tests establish a Git boundary and prevent user brain leakage?", + "ranked": [ + "git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZVAT824N0AQ3J5XFGHX62", + "id": "01M1WZVCYNX2YD0QDPYED40DQ9", + "kind": "memory", + "score": 0.9905676245689392, + "summary": "project:fact - For safe temporary memory tests, run git init in the temporary workspace to establish a repository boundary." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2115.249, + "first_query": true, + "server_startup_ms": 100.7616, + "model_text_bytes": 497, + "mcp_result_bytes": 578, + "wire_bytes": 613, + "reported_used_tokens": 578, + "working_set_bytes": 626737152, + "peak_working_set_bytes": 684793856 + }, + { + "query": "Which executable signing certificate is used for releases?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 463.9161, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 626929664, + "peak_working_set_bytes": 684793856 + } + ], + "id": "multi-fact-retrieval", + "dimension": "retrieval", + "tier": "medium", + "score": 0.75, + "skipped": false, + "detail": "positive-recall@4=0.50 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.000 (n=1) positive-n=1 negative-n=1 (2 queries)" + }, + { + "observations": [ + { + "query": "Which port does the Atlas staging listener currently use?", + "ranked": [ + "current" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZVF257705XNKQVNDQ725C", + "id": "01M1WZVH4D99VZDVKR7ZCC381W", + "kind": "memory", + "score": 0.999972939491272, + "summary": "project:fact - The Atlas staging listener uses port 4002." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 2128.8614, + "first_query": true, + "server_startup_ms": 106.8232, + "model_text_bytes": 429, + "mcp_result_bytes": 510, + "wire_bytes": 545, + "reported_used_tokens": 510, + "working_set_bytes": 628359168, + "peak_working_set_bytes": 685191168 + }, + { + "query": "What authentication password does the Atlas staging listener require?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": false, + "latency_ms": 452.8955, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 629186560, + "peak_working_set_bytes": 685191168 + } + ], + "id": "live-temporal-applicability", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=0.500 (n=2) false-injection=0.000 (n=1) positive-n=1 negative-n=1 (2 queries)" + }, + { + "observations": [ + { + "query": "Which port does the Zephyr local development HTTP server use?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 2216.2516, + "first_query": true, + "server_startup_ms": 100.64389999999999, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 623370240, + "peak_working_set_bytes": 684752896 + }, + { + "query": "Which port does the Zephyr local development HTTP server use?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZVNBEAZCAVN3DQAWVPYG6", + "id": "01M1WZVNPAT3XXEYNQVSHYPGJ0", + "kind": "memory", + "score": 0.99998140335083, + "summary": "project:fact - The Zephyr local development HTTP server listens on port 5243." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 444.8203, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 451, + "mcp_result_bytes": 532, + "wire_bytes": 567, + "reported_used_tokens": 532, + "working_set_bytes": 624906240, + "peak_working_set_bytes": 684752896 + }, + { + "query": "What authentication password does the Zephyr HTTP server require?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 433.2609, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 625143808, + "peak_working_set_bytes": 684752896 + }, + { + "query": "Where should MCP diagnostic logs be written?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 433.7088, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 625733632, + "peak_working_set_bytes": 684752896 + }, + { + "query": "Where should MCP diagnostic logs be written?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZVQ652K9086GQS1JDX25A", + "id": "01M1WZVQHJ9V2R70HT4Z5A264W", + "kind": "memory", + "score": 0.9523543119430542, + "summary": "project:fact - MCP diagnostic logs go to stderr; stdout is reserved for JSON-RPC messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 460.90930000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 463, + "mcp_result_bytes": 544, + "wire_bytes": 579, + "reported_used_tokens": 544, + "working_set_bytes": 627052544, + "peak_working_set_bytes": 684752896 + } + ], + "id": "persistent-mcp-observes-new-writes", + "dimension": "workflow", + "tier": "medium", + "score": 1.0, + "skipped": false, + "detail": "useful-hit=1.00 mrr=1.00 false-inj=0.00 trap-hit=n/a resolution=n/a curve=1.00\u21921.00 (5 episodes: 2 gold, 3 abstention; brain=2 mems)" + } + ], + "by_dimension_tier": { + "retrieval/easy": [ + 1.0, + 1 + ], + "retrieval/hard": [ + 2.75, + 3 + ], + "retrieval/medium": [ + 0.75, + 1 + ], + "workflow/medium": [ + 1.0, + 1 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 0.9, + "n": 5, + "ci95": 0.12002499739637572 + }, + "workflow": { + "mean": 1.0, + "n": 1, + "ci95": null + } + }, + "overall_index": 0.95, + "scenario_weighted_index": 0.9166666666666666 +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-retrieval/contract/2-baseline.json b/docs/audits/2026-09-07-retrieval/contract/2-baseline.json new file mode 100644 index 0000000..db4483c --- /dev/null +++ b/docs/audits/2026-09-07-retrieval/contract/2-baseline.json @@ -0,0 +1,616 @@ +{ + "generated_at": "2026-09-07T03:52:11.4680793Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\bench\\datasets\\brainbench\\agent-memory-contract.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "Where must MCP diagnostic logs be written?", + "ranked": [ + "stderr" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZWPVGQWD3N6F00HBRK5K0", + "id": "01M1WZWS6AQNA82FX0ZBMD9K0S", + "kind": "memory", + "score": 0.9994274377822876, + "summary": "project:fact - MCP diagnostic logs must go to stderr. Stdout is reserved for JSON-RPC messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 787.264, + "first_query": true, + "server_startup_ms": 106.75370000000001, + "model_text_bytes": 468, + "mcp_result_bytes": 549, + "wire_bytes": 584, + "reported_used_tokens": 549, + "working_set_bytes": 214597632, + "peak_working_set_bytes": 248389632 + }, + { + "query": "How do I enable SQLite foreign key constraints on a new connection?", + "ranked": [ + "foreign-keys" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZWQHGX9TKCHCA59YJ6573", + "id": "01M1WZWSKNZFQMG360ZRGWY561", + "kind": "memory", + "score": 0.9999536275863647, + "summary": "project:fact - Enable SQLite foreign key constraints explicitly on every new connection using PRAGMA foreign_keys = ON." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 515.2482, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 609, + "reported_used_tokens": 574, + "working_set_bytes": 215355392, + "peak_working_set_bytes": 248389632 + }, + { + "query": "How should I handle a long synchronous operation in the Tokio executor?", + "ranked": [ + "blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZWQHYFA25XC2PH2XGY2T3", + "id": "01M1WZWT3P5NGWZ3H4A7XQMYPP", + "kind": "memory", + "score": 0.982628345489502, + "summary": "project:fact - Move long synchronous operations into tokio::task::spawn_blocking instead of blocking asynchronous executor threads." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 512.5738, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 503, + "mcp_result_bytes": 584, + "wire_bytes": 619, + "reported_used_tokens": 584, + "working_set_bytes": 215535616, + "peak_working_set_bytes": 248389632 + }, + { + "query": "What is the weather forecast for tomorrow?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 1018.2580000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 215543808, + "peak_working_set_bytes": 248389632 + }, + { + "query": "What is the current production database password?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 503.55019999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 215560192, + "peak_working_set_bytes": 248389632 + } + ], + "id": "exact-code-evidence", + "dimension": "retrieval", + "tier": "easy", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.000 (n=2) positive-n=3 negative-n=2 (5 queries)" + }, + { + "observations": [ + { + "query": "Which port does the local development HTTP server use?", + "ranked": [ + "http-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZWYJ7470NT82MP76W64H3", + "id": "01M1WZWZFDXVRYVKA1N1DD3XMF", + "kind": "memory", + "score": 0.9998925924301147, + "summary": "project:fact - The local development HTTP server listens on port 4317." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 796.5501, + "first_query": true, + "server_startup_ms": 97.3342, + "model_text_bytes": 444, + "mcp_result_bytes": 525, + "wire_bytes": 560, + "reported_used_tokens": 525, + "working_set_bytes": 213708800, + "peak_working_set_bytes": 248410112 + }, + { + "query": "What authentication password does the local development HTTP server require?", + "ranked": [ + "http-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZWYJ7470NT82MP76W64H3", + "id": "01M1WZWZX9WK48AA3K69F2KJ9V", + "kind": "memory", + "score": 0.5794959664344788, + "summary": "project:fact - The local development HTTP server listens on port 4317." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 430.1651, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 443, + "mcp_result_bytes": 524, + "wire_bytes": 559, + "reported_used_tokens": 524, + "working_set_bytes": 213839872, + "peak_working_set_bytes": 248410112 + }, + { + "query": "Which encryption key protects the SQLite database?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 426.08570000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 213942272, + "peak_working_set_bytes": 248410112 + }, + { + "query": "What is the production request log retention duration?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 427.6801, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 214147072, + "peak_working_set_bytes": 248410112 + } + ], + "id": "related-but-unanswerable", + "dimension": "retrieval", + "tier": "hard", + "score": 0.75, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.333 (n=3) positive-n=1 negative-n=3 (4 queries)" + }, + { + "observations": [ + { + "query": "\u00bfD\u00f3nde deben escribirse los mensajes de diagn\u00f3stico MCP para no romper JSON-RPC?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 812.5019, + "first_query": true, + "server_startup_ms": 96.7842, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 215265280, + "peak_working_set_bytes": 248709120 + }, + { + "query": "\u00bfC\u00f3mo se activan las claves for\u00e1neas de SQLite en cada conexi\u00f3n?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 421.1359, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 215310336, + "peak_working_set_bytes": 248709120 + }, + { + "query": "\u00bfC\u00f3mo ejecuto trabajo s\u00edncrono largo sin bloquear Tokio?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 424.8408, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 215314432, + "peak_working_set_bytes": 248709120 + }, + { + "query": "\u00bfCu\u00e1l es el precio actual de la electricidad?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 418.2973, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 215318528, + "peak_working_set_bytes": 248709120 + } + ], + "id": "cross-language-code", + "dimension": "retrieval", + "tier": "hard", + "score": 0.25, + "skipped": false, + "detail": "positive-recall@4=0.00 mrr=0.00 stale-hit=n/a resolution=n/a false-injection=0.000 (n=1) positive-n=3 negative-n=1 (4 queries)" + }, + { + "observations": [ + { + "query": "How do temporary memory tests establish a Git boundary and prevent user brain leakage?", + "ranked": [ + "git-boundary", + "user-brain" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZX5JMQ3E9P6AVNX44BNNQ", + "id": "01M1WZX6E55081J71M6N4XXP88", + "kind": "memory", + "score": 0.9979111552238464, + "summary": "project:fact - For safe temporary memory tests, run git init in the temporary workspace to establish a repository boundary." + }, + { + "expansion_handle": "memory:01M1WZX5K7QQGYKXMSG8SBZVEC", + "id": "01M1WZX6E5E8FBG9CHMH05XPMC", + "kind": "memory", + "score": 0.9784100651741028, + "summary": "project:fact - For isolated memory tests, set KIMETSU_USER_BRAIN=0 to prevent cross-project memory leakage." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 807.8399000000001, + "first_query": true, + "server_startup_ms": 99.23410000000001, + "model_text_bytes": 751, + "mcp_result_bytes": 850, + "wire_bytes": 885, + "reported_used_tokens": 850, + "working_set_bytes": 214052864, + "peak_working_set_bytes": 248434688 + }, + { + "query": "Which executable signing certificate is used for releases?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 447.9226, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 214241280, + "peak_working_set_bytes": 248434688 + } + ], + "id": "multi-fact-retrieval", + "dimension": "retrieval", + "tier": "medium", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.000 (n=1) positive-n=1 negative-n=1 (2 queries)" + }, + { + "observations": [ + { + "query": "Which port does the Atlas staging listener currently use?", + "ranked": [ + "current" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZX86923J7A4RN0N4Z0Y2V", + "id": "01M1WZX907P53V14VYT358E02B", + "kind": "memory", + "score": 0.9999620914459229, + "summary": "project:fact - The Atlas staging listener uses port 4002." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 792.0697, + "first_query": true, + "server_startup_ms": 99.99180000000001, + "model_text_bytes": 430, + "mcp_result_bytes": 511, + "wire_bytes": 546, + "reported_used_tokens": 511, + "working_set_bytes": 212754432, + "peak_working_set_bytes": 248332288 + }, + { + "query": "What authentication password does the Atlas staging listener require?", + "ranked": [ + "current" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZX86923J7A4RN0N4Z0Y2V", + "id": "01M1WZX9DN2S4KMD5SZYKW4XE3", + "kind": "memory", + "score": 0.9969580173492432, + "summary": "project:fact - The Atlas staging listener uses port 4002." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": false, + "latency_ms": 418.1092, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 430, + "mcp_result_bytes": 511, + "wire_bytes": 546, + "reported_used_tokens": 511, + "working_set_bytes": 212901888, + "peak_working_set_bytes": 248332288 + } + ], + "id": "live-temporal-applicability", + "dimension": "retrieval", + "tier": "hard", + "score": 0.5, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=0.500 (n=2) false-injection=1.000 (n=1) positive-n=1 negative-n=1 (2 queries)" + }, + { + "observations": [ + { + "query": "Which port does the Zephyr local development HTTP server use?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 809.791, + "first_query": true, + "server_startup_ms": 101.4367, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 212369408, + "peak_working_set_bytes": 248487936 + }, + { + "query": "Which port does the Zephyr local development HTTP server use?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZXBKS4SHP0X8ZHA7QJJB5", + "id": "01M1WZXBZ5A0HNGTE4EQ14TPJA", + "kind": "memory", + "score": 0.9999316930770874, + "summary": "project:fact - The Zephyr local development HTTP server listens on port 5243." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 431.2763, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 451, + "mcp_result_bytes": 532, + "wire_bytes": 567, + "reported_used_tokens": 532, + "working_set_bytes": 213168128, + "peak_working_set_bytes": 248487936 + }, + { + "query": "What authentication password does the Zephyr HTTP server require?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 434.9335, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 213200896, + "peak_working_set_bytes": 248487936 + }, + { + "query": "Where should MCP diagnostic logs be written?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 412.0929, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 213200896, + "peak_working_set_bytes": 248487936 + }, + { + "query": "Where should MCP diagnostic logs be written?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZXDE0CNAJ0CAKZJ21P8YJ", + "id": "01M1WZXDSAD5ZYRAKKGE466JVY", + "kind": "memory", + "score": 0.9979764819145204, + "summary": "project:fact - MCP diagnostic logs go to stderr; stdout is reserved for JSON-RPC messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 428.7726, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 463, + "mcp_result_bytes": 544, + "wire_bytes": 579, + "reported_used_tokens": 544, + "working_set_bytes": 213647360, + "peak_working_set_bytes": 248487936 + } + ], + "id": "persistent-mcp-observes-new-writes", + "dimension": "workflow", + "tier": "medium", + "score": 1.0, + "skipped": false, + "detail": "useful-hit=1.00 mrr=1.00 false-inj=0.00 trap-hit=n/a resolution=n/a curve=1.00\u21921.00 (5 episodes: 2 gold, 3 abstention; brain=2 mems)" + } + ], + "by_dimension_tier": { + "retrieval/easy": [ + 1.0, + 1 + ], + "retrieval/hard": [ + 1.5, + 3 + ], + "retrieval/medium": [ + 1.0, + 1 + ], + "workflow/medium": [ + 1.0, + 1 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 0.7, + "n": 5, + "ci95": 0.2857166428474197 + }, + "workflow": { + "mean": 1.0, + "n": 1, + "ci95": null + } + }, + "overall_index": 0.85, + "scenario_weighted_index": 0.75 +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-retrieval/contract/2-candidate.json b/docs/audits/2026-09-07-retrieval/contract/2-candidate.json new file mode 100644 index 0000000..8c04716 --- /dev/null +++ b/docs/audits/2026-09-07-retrieval/contract/2-candidate.json @@ -0,0 +1,630 @@ +{ + "generated_at": "2026-09-07T03:51:46.2992803Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\bench\\datasets\\brainbench\\agent-memory-contract.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "Where must MCP diagnostic logs be written?", + "ranked": [ + "stderr" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZVS49814EV1TTE3JRMMPA", + "id": "01M1WZVV8AFMNT5AMHB46SZXSD", + "kind": "memory", + "score": 0.9986107349395752, + "summary": "project:fact - MCP diagnostic logs must go to stderr. Stdout is reserved for JSON-RPC messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2139.3044, + "first_query": true, + "server_startup_ms": 96.1143, + "model_text_bytes": 468, + "mcp_result_bytes": 549, + "wire_bytes": 584, + "reported_used_tokens": 549, + "working_set_bytes": 632340480, + "peak_working_set_bytes": 684855296 + }, + { + "query": "How do I enable SQLite foreign key constraints on a new connection?", + "ranked": [ + "foreign-keys" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZVS4WVEE2QVM4VXXAGF42", + "id": "01M1WZVVPNCY73ATA1W23M415Q", + "kind": "memory", + "score": 0.999981164932251, + "summary": "project:fact - Enable SQLite foreign key constraints explicitly on every new connection using PRAGMA foreign_keys = ON." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 457.0156, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 634474496, + "peak_working_set_bytes": 684855296 + }, + { + "query": "How should I handle a long synchronous operation in the Tokio executor?", + "ranked": [ + "blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZVS5AY4HPBMPHVCTX18AA", + "id": "01M1WZVW4ZGP15YC2PQ0F9B75K", + "kind": "memory", + "score": 0.9949350953102112, + "summary": "project:fact - Move long synchronous operations into tokio::task::spawn_blocking instead of blocking asynchronous executor threads." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 469.1574, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 504, + "mcp_result_bytes": 585, + "wire_bytes": 620, + "reported_used_tokens": 585, + "working_set_bytes": 635162624, + "peak_working_set_bytes": 684855296 + }, + { + "query": "What is the weather forecast for tomorrow?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 451.30740000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 635195392, + "peak_working_set_bytes": 684855296 + }, + { + "query": "What is the current production database password?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 476.1155, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 635219968, + "peak_working_set_bytes": 684855296 + } + ], + "id": "exact-code-evidence", + "dimension": "retrieval", + "tier": "easy", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.000 (n=2) positive-n=3 negative-n=2 (5 queries)" + }, + { + "observations": [ + { + "query": "Which port does the local development HTTP server use?", + "ranked": [ + "http-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZVYK3B80BZSJB2NH8SY1D", + "id": "01M1WZW0PC2Q5KQT0BE5B33T52", + "kind": "memory", + "score": 0.9999812841415404, + "summary": "project:fact - The local development HTTP server listens on port 4317." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2117.5014, + "first_query": true, + "server_startup_ms": 95.5917, + "model_text_bytes": 444, + "mcp_result_bytes": 525, + "wire_bytes": 560, + "reported_used_tokens": 525, + "working_set_bytes": 628834304, + "peak_working_set_bytes": 684679168 + }, + { + "query": "What authentication password does the local development HTTP server require?", + "ranked": [ + "http-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZVYK3B80BZSJB2NH8SY1D", + "id": "01M1WZW13ZF4M1EMR3ARXEY489", + "kind": "memory", + "score": 0.7880221605300903, + "summary": "project:fact - The local development HTTP server listens on port 4317." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 434.93179999999995, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 443, + "mcp_result_bytes": 524, + "wire_bytes": 559, + "reported_used_tokens": 524, + "working_set_bytes": 630460416, + "peak_working_set_bytes": 684679168 + }, + { + "query": "Which encryption key protects the SQLite database?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 439.5272, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 630546432, + "peak_working_set_bytes": 684679168 + }, + { + "query": "What is the production request log retention duration?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 443.33050000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 630583296, + "peak_working_set_bytes": 684679168 + } + ], + "id": "related-but-unanswerable", + "dimension": "retrieval", + "tier": "hard", + "score": 0.75, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.333 (n=3) positive-n=1 negative-n=3 (4 queries)" + }, + { + "observations": [ + { + "query": "\u00bfD\u00f3nde deben escribirse los mensajes de diagn\u00f3stico MCP para no romper JSON-RPC?", + "ranked": [ + "stderr" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZW3F8VTNH5JHJWJCFA554", + "id": "01M1WZW5PBZKT19R6GHYP3SCZT", + "kind": "memory", + "score": 0.9584800601005554, + "summary": "project:fact - Write MCP diagnostic messages to stderr because stdout carries the JSON-RPC protocol." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2230.772, + "first_query": true, + "server_startup_ms": 134.7765, + "model_text_bytes": 473, + "mcp_result_bytes": 554, + "wire_bytes": 589, + "reported_used_tokens": 554, + "working_set_bytes": 625610752, + "peak_working_set_bytes": 684867584 + }, + { + "query": "\u00bfC\u00f3mo se activan las claves for\u00e1neas de SQLite en cada conexi\u00f3n?", + "ranked": [ + "foreign-keys" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZW3FSDHJJ4NNZN43XN4RM", + "id": "01M1WZW64W6DADYT4QBENPQ84J", + "kind": "memory", + "score": 0.993928074836731, + "summary": "project:fact - SQLite foreign key enforcement must be enabled separately for every connection with PRAGMA foreign_keys = ON." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 449.0753, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 496, + "mcp_result_bytes": 577, + "wire_bytes": 612, + "reported_used_tokens": 577, + "working_set_bytes": 626479104, + "peak_working_set_bytes": 684867584 + }, + { + "query": "\u00bfC\u00f3mo ejecuto trabajo s\u00edncrono largo sin bloquear Tokio?", + "ranked": [ + "blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZW3G79626S2820QM5HKWG", + "id": "01M1WZW6K9M3CTRYB420XX8F1S", + "kind": "memory", + "score": 0.9620424509048462, + "summary": "project:fact - Use tokio::task::spawn_blocking for long synchronous work inside an asynchronous application." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 457.0531, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 481, + "mcp_result_bytes": 562, + "wire_bytes": 597, + "reported_used_tokens": 562, + "working_set_bytes": 626548736, + "peak_working_set_bytes": 684867584 + }, + { + "query": "\u00bfCu\u00e1l es el precio actual de la electricidad?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 424.0235, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 626569216, + "peak_working_set_bytes": 684867584 + } + ], + "id": "cross-language-code", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.000 (n=1) positive-n=3 negative-n=1 (4 queries)" + }, + { + "observations": [ + { + "query": "How do temporary memory tests establish a Git boundary and prevent user brain leakage?", + "ranked": [ + "git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZW8JA5KDNATNFVFC10YAB", + "id": "01M1WZWANWF3ZEPPDADPNS6WJD", + "kind": "memory", + "score": 0.9905676245689392, + "summary": "project:fact - For safe temporary memory tests, run git init in the temporary workspace to establish a repository boundary." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2138.2916999999998, + "first_query": true, + "server_startup_ms": 98.5391, + "model_text_bytes": 497, + "mcp_result_bytes": 578, + "wire_bytes": 613, + "reported_used_tokens": 578, + "working_set_bytes": 631062528, + "peak_working_set_bytes": 684949504 + }, + { + "query": "Which executable signing certificate is used for releases?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 441.7754, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 631238656, + "peak_working_set_bytes": 684949504 + } + ], + "id": "multi-fact-retrieval", + "dimension": "retrieval", + "tier": "medium", + "score": 0.75, + "skipped": false, + "detail": "positive-recall@4=0.50 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.000 (n=1) positive-n=1 negative-n=1 (2 queries)" + }, + { + "observations": [ + { + "query": "Which port does the Atlas staging listener currently use?", + "ranked": [ + "current" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZWCNYZH0VSMB91TFAGDW8", + "id": "01M1WZWEQSW6S7ECEAX7TQ0SNA", + "kind": "memory", + "score": 0.999972939491272, + "summary": "project:fact - The Atlas staging listener uses port 4002." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 2102.757, + "first_query": true, + "server_startup_ms": 96.7274, + "model_text_bytes": 429, + "mcp_result_bytes": 510, + "wire_bytes": 545, + "reported_used_tokens": 510, + "working_set_bytes": 628269056, + "peak_working_set_bytes": 684793856 + }, + { + "query": "What authentication password does the Atlas staging listener require?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": false, + "latency_ms": 428.7071, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 629084160, + "peak_working_set_bytes": 684793856 + } + ], + "id": "live-temporal-applicability", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=0.500 (n=2) false-injection=0.000 (n=1) positive-n=1 negative-n=1 (2 queries)" + }, + { + "observations": [ + { + "query": "Which port does the Zephyr local development HTTP server use?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 2101.1009, + "first_query": true, + "server_startup_ms": 96.02539999999999, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 626884608, + "peak_working_set_bytes": 684806144 + }, + { + "query": "Which port does the Zephyr local development HTTP server use?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZWJTSGGGEHQM49YMVKFBV", + "id": "01M1WZWK5SK2NYPTYAEC26QAAT", + "kind": "memory", + "score": 0.99998140335083, + "summary": "project:fact - The Zephyr local development HTTP server listens on port 5243." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 440.42019999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 451, + "mcp_result_bytes": 532, + "wire_bytes": 567, + "reported_used_tokens": 532, + "working_set_bytes": 628477952, + "peak_working_set_bytes": 684806144 + }, + { + "query": "What authentication password does the Zephyr HTTP server require?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 465.95750000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 628756480, + "peak_working_set_bytes": 684806144 + }, + { + "query": "Where should MCP diagnostic logs be written?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 435.7158, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 629297152, + "peak_working_set_bytes": 684806144 + }, + { + "query": "Where should MCP diagnostic logs be written?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZWMSC7WE8VNE7Z5TRQZMJ", + "id": "01M1WZWN4QBW6CWGEQ09HXY7P6", + "kind": "memory", + "score": 0.9523543119430542, + "summary": "project:fact - MCP diagnostic logs go to stderr; stdout is reserved for JSON-RPC messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 470.4541, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 463, + "mcp_result_bytes": 544, + "wire_bytes": 579, + "reported_used_tokens": 544, + "working_set_bytes": 630734848, + "peak_working_set_bytes": 684806144 + } + ], + "id": "persistent-mcp-observes-new-writes", + "dimension": "workflow", + "tier": "medium", + "score": 1.0, + "skipped": false, + "detail": "useful-hit=1.00 mrr=1.00 false-inj=0.00 trap-hit=n/a resolution=n/a curve=1.00\u21921.00 (5 episodes: 2 gold, 3 abstention; brain=2 mems)" + } + ], + "by_dimension_tier": { + "retrieval/easy": [ + 1.0, + 1 + ], + "retrieval/hard": [ + 2.75, + 3 + ], + "retrieval/medium": [ + 0.75, + 1 + ], + "workflow/medium": [ + 1.0, + 1 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 0.9, + "n": 5, + "ci95": 0.12002499739637572 + }, + "workflow": { + "mean": 1.0, + "n": 1, + "ci95": null + } + }, + "overall_index": 0.95, + "scenario_weighted_index": 0.9166666666666666 +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-retrieval/contract/3-baseline.json b/docs/audits/2026-09-07-retrieval/contract/3-baseline.json new file mode 100644 index 0000000..35b0126 --- /dev/null +++ b/docs/audits/2026-09-07-retrieval/contract/3-baseline.json @@ -0,0 +1,616 @@ +{ + "generated_at": "2026-09-07T03:52:32.2702817Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\bench\\datasets\\brainbench\\agent-memory-contract.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "Where must MCP diagnostic logs be written?", + "ranked": [ + "stderr" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZXF4NF91YWY55T01GS9G6", + "id": "01M1WZXG04T024PWMBXW848X1Y", + "kind": "memory", + "score": 0.9994274377822876, + "summary": "project:fact - MCP diagnostic logs must go to stderr. Stdout is reserved for JSON-RPC messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 807.1945000000001, + "first_query": true, + "server_startup_ms": 102.9099, + "model_text_bytes": 468, + "mcp_result_bytes": 549, + "wire_bytes": 584, + "reported_used_tokens": 549, + "working_set_bytes": 214380544, + "peak_working_set_bytes": 248377344 + }, + { + "query": "How do I enable SQLite foreign key constraints on a new connection?", + "ranked": [ + "foreign-keys" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZXF588VQFKDJ2JST7MFH9", + "id": "01M1WZXGDGXJ1AY0X1HAGBH0DG", + "kind": "memory", + "score": 0.9999536275863647, + "summary": "project:fact - Enable SQLite foreign key constraints explicitly on every new connection using PRAGMA foreign_keys = ON." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 428.5002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 609, + "reported_used_tokens": 574, + "working_set_bytes": 215121920, + "peak_working_set_bytes": 248377344 + }, + { + "query": "How should I handle a long synchronous operation in the Tokio executor?", + "ranked": [ + "blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZXF5PHGNN3XYJ6QBCQZ4D", + "id": "01M1WZXGTK0K94SVW3Y2F6R0AM", + "kind": "memory", + "score": 0.982628345489502, + "summary": "project:fact - Move long synchronous operations into tokio::task::spawn_blocking instead of blocking asynchronous executor threads." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 422.376, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 503, + "mcp_result_bytes": 584, + "wire_bytes": 619, + "reported_used_tokens": 584, + "working_set_bytes": 215650304, + "peak_working_set_bytes": 248377344 + }, + { + "query": "What is the weather forecast for tomorrow?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 436.29220000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 215666688, + "peak_working_set_bytes": 248377344 + }, + { + "query": "What is the current production database password?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 417.2051, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 215678976, + "peak_working_set_bytes": 248377344 + } + ], + "id": "exact-code-evidence", + "dimension": "retrieval", + "tier": "easy", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.000 (n=2) positive-n=3 negative-n=2 (5 queries)" + }, + { + "observations": [ + { + "query": "Which port does the local development HTTP server use?", + "ranked": [ + "http-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZXJXCHCYTYP5V7AC1Z3CK", + "id": "01M1WZXKQHW92VNEMF8CQ3NKZ8", + "kind": "memory", + "score": 0.9998925924301147, + "summary": "project:fact - The local development HTTP server listens on port 4317." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 794.6872000000001, + "first_query": true, + "server_startup_ms": 95.6106, + "model_text_bytes": 444, + "mcp_result_bytes": 525, + "wire_bytes": 560, + "reported_used_tokens": 525, + "working_set_bytes": 213708800, + "peak_working_set_bytes": 248541184 + }, + { + "query": "What authentication password does the local development HTTP server require?", + "ranked": [ + "http-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZXJXCHCYTYP5V7AC1Z3CK", + "id": "01M1WZXM4SXRQNHNT9VMXJCPZJ", + "kind": "memory", + "score": 0.5794959664344788, + "summary": "project:fact - The local development HTTP server listens on port 4317." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 418.3049, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 443, + "mcp_result_bytes": 524, + "wire_bytes": 559, + "reported_used_tokens": 524, + "working_set_bytes": 213774336, + "peak_working_set_bytes": 248541184 + }, + { + "query": "Which encryption key protects the SQLite database?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 409.2965, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 213807104, + "peak_working_set_bytes": 248541184 + }, + { + "query": "What is the production request log retention duration?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 416.5783, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 214024192, + "peak_working_set_bytes": 248541184 + } + ], + "id": "related-but-unanswerable", + "dimension": "retrieval", + "tier": "hard", + "score": 0.75, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.333 (n=3) positive-n=1 negative-n=3 (4 queries)" + }, + { + "observations": [ + { + "query": "\u00bfD\u00f3nde deben escribirse los mensajes de diagn\u00f3stico MCP para no romper JSON-RPC?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 809.0994999999999, + "first_query": true, + "server_startup_ms": 98.2436, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 215072768, + "peak_working_set_bytes": 248320000 + }, + { + "query": "\u00bfC\u00f3mo se activan las claves for\u00e1neas de SQLite en cada conexi\u00f3n?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 417.1099, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 215126016, + "peak_working_set_bytes": 248320000 + }, + { + "query": "\u00bfC\u00f3mo ejecuto trabajo s\u00edncrono largo sin bloquear Tokio?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 454.1064, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 215142400, + "peak_working_set_bytes": 248320000 + }, + { + "query": "\u00bfCu\u00e1l es el precio actual de la electricidad?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 413.0191, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 215142400, + "peak_working_set_bytes": 248320000 + } + ], + "id": "cross-language-code", + "dimension": "retrieval", + "tier": "hard", + "score": 0.25, + "skipped": false, + "detail": "positive-recall@4=0.00 mrr=0.00 stale-hit=n/a resolution=n/a false-injection=0.000 (n=1) positive-n=3 negative-n=1 (4 queries)" + }, + { + "observations": [ + { + "query": "How do temporary memory tests establish a Git boundary and prevent user brain leakage?", + "ranked": [ + "git-boundary", + "user-brain" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZXT2Q3MFFH5FVMWP5JNFN", + "id": "01M1WZXTXE9GGPJQWJ5HYQYFSR", + "kind": "memory", + "score": 0.9979111552238464, + "summary": "project:fact - For safe temporary memory tests, run git init in the temporary workspace to establish a repository boundary." + }, + { + "expansion_handle": "memory:01M1WZXT39NJ97FR0WHZ71ZM00", + "id": "01M1WZXTXEQREFQ4AX3NHHA6Q3", + "kind": "memory", + "score": 0.9784100651741028, + "summary": "project:fact - For isolated memory tests, set KIMETSU_USER_BRAIN=0 to prevent cross-project memory leakage." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 796.2098, + "first_query": true, + "server_startup_ms": 93.95190000000001, + "model_text_bytes": 751, + "mcp_result_bytes": 850, + "wire_bytes": 885, + "reported_used_tokens": 850, + "working_set_bytes": 214228992, + "peak_working_set_bytes": 248516608 + }, + { + "query": "Which executable signing certificate is used for releases?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 403.74670000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 214421504, + "peak_working_set_bytes": 248516608 + } + ], + "id": "multi-fact-retrieval", + "dimension": "retrieval", + "tier": "medium", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.000 (n=1) positive-n=1 negative-n=1 (2 queries)" + }, + { + "observations": [ + { + "query": "Which port does the Atlas staging listener currently use?", + "ranked": [ + "current" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZXWJYA5NRWYNY3AM8CDTQ", + "id": "01M1WZXXCAYP1D6X69BGNPHJ98", + "kind": "memory", + "score": 0.9999620914459229, + "summary": "project:fact - The Atlas staging listener uses port 4002." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 788.4262, + "first_query": true, + "server_startup_ms": 95.9522, + "model_text_bytes": 430, + "mcp_result_bytes": 511, + "wire_bytes": 546, + "reported_used_tokens": 511, + "working_set_bytes": 212897792, + "peak_working_set_bytes": 248369152 + }, + { + "query": "What authentication password does the Atlas staging listener require?", + "ranked": [ + "current" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZXWJYA5NRWYNY3AM8CDTQ", + "id": "01M1WZXXSA2MMBHCHACTD18G5X", + "kind": "memory", + "score": 0.9969580173492432, + "summary": "project:fact - The Atlas staging listener uses port 4002." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": false, + "latency_ms": 407.4108, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 430, + "mcp_result_bytes": 511, + "wire_bytes": 546, + "reported_used_tokens": 511, + "working_set_bytes": 212992000, + "peak_working_set_bytes": 248369152 + } + ], + "id": "live-temporal-applicability", + "dimension": "retrieval", + "tier": "hard", + "score": 0.5, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=0.500 (n=2) false-injection=1.000 (n=1) positive-n=1 negative-n=1 (2 queries)" + }, + { + "observations": [ + { + "query": "Which port does the Zephyr local development HTTP server use?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 844.576, + "first_query": true, + "server_startup_ms": 99.4259, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 212320256, + "peak_working_set_bytes": 248340480 + }, + { + "query": "Which port does the Zephyr local development HTTP server use?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZY00BRVD5JSS5HBV19BR8", + "id": "01M1WZY0AXKJYN489BCGSWMA9B", + "kind": "memory", + "score": 0.9999316930770874, + "summary": "project:fact - The Zephyr local development HTTP server listens on port 5243." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 408.5315, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 451, + "mcp_result_bytes": 532, + "wire_bytes": 567, + "reported_used_tokens": 532, + "working_set_bytes": 213053440, + "peak_working_set_bytes": 248340480 + }, + { + "query": "What authentication password does the Zephyr HTTP server require?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 419.93370000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 213065728, + "peak_working_set_bytes": 248340480 + }, + { + "query": "Where should MCP diagnostic logs be written?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 409.5396, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 213065728, + "peak_working_set_bytes": 248340480 + }, + { + "query": "Where should MCP diagnostic logs be written?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZY1RW8M1TYT9HZ1EHKXK6", + "id": "01M1WZY23JS3JDDJWQM6DVBBDY", + "kind": "memory", + "score": 0.9979764819145204, + "summary": "project:fact - MCP diagnostic logs go to stderr; stdout is reserved for JSON-RPC messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 414.9638, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 463, + "mcp_result_bytes": 544, + "wire_bytes": 579, + "reported_used_tokens": 544, + "working_set_bytes": 213598208, + "peak_working_set_bytes": 248340480 + } + ], + "id": "persistent-mcp-observes-new-writes", + "dimension": "workflow", + "tier": "medium", + "score": 1.0, + "skipped": false, + "detail": "useful-hit=1.00 mrr=1.00 false-inj=0.00 trap-hit=n/a resolution=n/a curve=1.00\u21921.00 (5 episodes: 2 gold, 3 abstention; brain=2 mems)" + } + ], + "by_dimension_tier": { + "retrieval/easy": [ + 1.0, + 1 + ], + "retrieval/hard": [ + 1.5, + 3 + ], + "retrieval/medium": [ + 1.0, + 1 + ], + "workflow/medium": [ + 1.0, + 1 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 0.7, + "n": 5, + "ci95": 0.2857166428474197 + }, + "workflow": { + "mean": 1.0, + "n": 1, + "ci95": null + } + }, + "overall_index": 0.85, + "scenario_weighted_index": 0.75 +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-retrieval/contract/3-candidate.json b/docs/audits/2026-09-07-retrieval/contract/3-candidate.json new file mode 100644 index 0000000..07762d4 --- /dev/null +++ b/docs/audits/2026-09-07-retrieval/contract/3-candidate.json @@ -0,0 +1,630 @@ +{ + "generated_at": "2026-09-07T03:53:02.5417835Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\bench\\datasets\\brainbench\\agent-memory-contract.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "Where must MCP diagnostic logs be written?", + "ranked": [ + "stderr" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZY3KJWVRG15Z97NMFHW8E", + "id": "01M1WZY5PRKN0KGN3KA3HQBY2K", + "kind": "memory", + "score": 0.9986107349395752, + "summary": "project:fact - MCP diagnostic logs must go to stderr. Stdout is reserved for JSON-RPC messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2113.0788, + "first_query": true, + "server_startup_ms": 93.57360000000001, + "model_text_bytes": 468, + "mcp_result_bytes": 549, + "wire_bytes": 584, + "reported_used_tokens": 549, + "working_set_bytes": 630145024, + "peak_working_set_bytes": 684916736 + }, + { + "query": "How do I enable SQLite foreign key constraints on a new connection?", + "ranked": [ + "foreign-keys" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZY3M4NWNK216C75J1XP0Z", + "id": "01M1WZY654EQYCP4X9THKN4H8H", + "kind": "memory", + "score": 0.999981164932251, + "summary": "project:fact - Enable SQLite foreign key constraints explicitly on every new connection using PRAGMA foreign_keys = ON." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 467.2796, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 632422400, + "peak_working_set_bytes": 684916736 + }, + { + "query": "How should I handle a long synchronous operation in the Tokio executor?", + "ranked": [ + "blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZY3MHRHY4QYCZ7VY69XT1", + "id": "01M1WZY6KKJ818CBDGBRP830BN", + "kind": "memory", + "score": 0.9949350953102112, + "summary": "project:fact - Move long synchronous operations into tokio::task::spawn_blocking instead of blocking asynchronous executor threads." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 460.8614, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 504, + "mcp_result_bytes": 585, + "wire_bytes": 620, + "reported_used_tokens": 585, + "working_set_bytes": 632893440, + "peak_working_set_bytes": 684916736 + }, + { + "query": "What is the weather forecast for tomorrow?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 455.7844, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 632954880, + "peak_working_set_bytes": 684916736 + }, + { + "query": "What is the current production database password?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 448.2038, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 632979456, + "peak_working_set_bytes": 684916736 + } + ], + "id": "exact-code-evidence", + "dimension": "retrieval", + "tier": "easy", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.000 (n=2) positive-n=3 negative-n=2 (5 queries)" + }, + { + "observations": [ + { + "query": "Which port does the local development HTTP server use?", + "ranked": [ + "http-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZY8ZVQ5886CZF0FJRM5ED", + "id": "01M1WZYB3X0Z7H5JCRSH3FCSWF", + "kind": "memory", + "score": 0.9999812841415404, + "summary": "project:fact - The local development HTTP server listens on port 4317." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2158.4186, + "first_query": true, + "server_startup_ms": 96.8603, + "model_text_bytes": 444, + "mcp_result_bytes": 525, + "wire_bytes": 560, + "reported_used_tokens": 525, + "working_set_bytes": 630509568, + "peak_working_set_bytes": 684666880 + }, + { + "query": "What authentication password does the local development HTTP server require?", + "ranked": [ + "http-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZY8ZVQ5886CZF0FJRM5ED", + "id": "01M1WZYBHMDQBWFHE4PTENX0XN", + "kind": "memory", + "score": 0.7880221605300903, + "summary": "project:fact - The local development HTTP server listens on port 4317." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 427.48960000000005, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 443, + "mcp_result_bytes": 524, + "wire_bytes": 559, + "reported_used_tokens": 524, + "working_set_bytes": 632033280, + "peak_working_set_bytes": 684666880 + }, + { + "query": "Which encryption key protects the SQLite database?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 433.6764, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 632070144, + "peak_working_set_bytes": 684666880 + }, + { + "query": "What is the production request log retention duration?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 450.6363, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 632090624, + "peak_working_set_bytes": 684666880 + } + ], + "id": "related-but-unanswerable", + "dimension": "retrieval", + "tier": "hard", + "score": 0.75, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.333 (n=3) positive-n=1 negative-n=3 (4 queries)" + }, + { + "observations": [ + { + "query": "\u00bfD\u00f3nde deben escribirse los mensajes de diagn\u00f3stico MCP para no romper JSON-RPC?", + "ranked": [ + "stderr" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZYDYE8EPNK0V1FAVQTMTA", + "id": "01M1WZYG2KDR3XE0YV8FKX4JZ9", + "kind": "memory", + "score": 0.9584800601005554, + "summary": "project:fact - Write MCP diagnostic messages to stderr because stdout carries the JSON-RPC protocol." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2154.4088, + "first_query": true, + "server_startup_ms": 100.0977, + "model_text_bytes": 473, + "mcp_result_bytes": 554, + "wire_bytes": 589, + "reported_used_tokens": 554, + "working_set_bytes": 631681024, + "peak_working_set_bytes": 684924928 + }, + { + "query": "\u00bfC\u00f3mo se activan las claves for\u00e1neas de SQLite en cada conexi\u00f3n?", + "ranked": [ + "foreign-keys" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZYDZ10YA7Q4PXX2CNQ583", + "id": "01M1WZYGGWAMM6T20MMZ41CCNV", + "kind": "memory", + "score": 0.993928074836731, + "summary": "project:fact - SQLite foreign key enforcement must be enabled separately for every connection with PRAGMA foreign_keys = ON." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 441.4391, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 496, + "mcp_result_bytes": 577, + "wire_bytes": 612, + "reported_used_tokens": 577, + "working_set_bytes": 632561664, + "peak_working_set_bytes": 684924928 + }, + { + "query": "\u00bfC\u00f3mo ejecuto trabajo s\u00edncrono largo sin bloquear Tokio?", + "ranked": [ + "blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZYDZF3RS98VY89YV2CQR1", + "id": "01M1WZYGZ2FX3T0CYNDA1JH1ED", + "kind": "memory", + "score": 0.9620424509048462, + "summary": "project:fact - Use tokio::task::spawn_blocking for long synchronous work inside an asynchronous application." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 470.4418, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 481, + "mcp_result_bytes": 562, + "wire_bytes": 597, + "reported_used_tokens": 562, + "working_set_bytes": 632729600, + "peak_working_set_bytes": 684924928 + }, + { + "query": "\u00bfCu\u00e1l es el precio actual de la electricidad?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 428.77729999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 632741888, + "peak_working_set_bytes": 684924928 + } + ], + "id": "cross-language-code", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.000 (n=1) positive-n=3 negative-n=1 (4 queries)" + }, + { + "observations": [ + { + "query": "How do temporary memory tests establish a Git boundary and prevent user brain leakage?", + "ranked": [ + "git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZYK0P2GSYB8Y1EKRMN8AV", + "id": "01M1WZYN7FABZ2CA43FEM9A3BP", + "kind": "memory", + "score": 0.9905676245689392, + "summary": "project:fact - For safe temporary memory tests, run git init in the temporary workspace to establish a repository boundary." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2224.569, + "first_query": true, + "server_startup_ms": 114.1178, + "model_text_bytes": 497, + "mcp_result_bytes": 578, + "wire_bytes": 613, + "reported_used_tokens": 578, + "working_set_bytes": 631517184, + "peak_working_set_bytes": 684744704 + }, + { + "query": "Which executable signing certificate is used for releases?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 438.8273, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 631705600, + "peak_working_set_bytes": 684744704 + } + ], + "id": "multi-fact-retrieval", + "dimension": "retrieval", + "tier": "medium", + "score": 0.75, + "skipped": false, + "detail": "positive-recall@4=0.50 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.000 (n=1) positive-n=1 negative-n=1 (2 queries)" + }, + { + "observations": [ + { + "query": "Which port does the Atlas staging listener currently use?", + "ranked": [ + "current" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZYQ7BD0VA9RZSBBFVG47Z", + "id": "01M1WZYSA22MDV4YEK6FM8KBF5", + "kind": "memory", + "score": 0.999972939491272, + "summary": "project:fact - The Atlas staging listener uses port 4002." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 2123.0704, + "first_query": true, + "server_startup_ms": 98.5089, + "model_text_bytes": 429, + "mcp_result_bytes": 510, + "wire_bytes": 545, + "reported_used_tokens": 510, + "working_set_bytes": 621203456, + "peak_working_set_bytes": 684888064 + }, + { + "query": "What authentication password does the Atlas staging listener require?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": false, + "latency_ms": 420.1975, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 621887488, + "peak_working_set_bytes": 684888064 + } + ], + "id": "live-temporal-applicability", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=0.500 (n=2) false-injection=0.000 (n=1) positive-n=1 negative-n=1 (2 queries)" + }, + { + "observations": [ + { + "query": "Which port does the Zephyr local development HTTP server use?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 2164.7336, + "first_query": true, + "server_startup_ms": 104.9557, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 625434624, + "peak_working_set_bytes": 684732416 + }, + { + "query": "Which port does the Zephyr local development HTTP server use?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZYXE6XZ7B80SQA5E8KY6X", + "id": "01M1WZYXS6F73R74EYHPW6PQS0", + "kind": "memory", + "score": 0.99998140335083, + "summary": "project:fact - The Zephyr local development HTTP server listens on port 5243." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 441.1631, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 451, + "mcp_result_bytes": 532, + "wire_bytes": 567, + "reported_used_tokens": 532, + "working_set_bytes": 626962432, + "peak_working_set_bytes": 684732416 + }, + { + "query": "What authentication password does the Zephyr HTTP server require?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 434.3021, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 627220480, + "peak_working_set_bytes": 684732416 + }, + { + "query": "Where should MCP diagnostic logs be written?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 428.2713, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 324, + "reported_used_tokens": 289, + "working_set_bytes": 627761152, + "peak_working_set_bytes": 684732416 + }, + { + "query": "Where should MCP diagnostic logs be written?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZYZ8XD2MJBFDP5QCDDH4E", + "id": "01M1WZYZKT5WA8XAFF2NA1SYV0", + "kind": "memory", + "score": 0.9523543119430542, + "summary": "project:fact - MCP diagnostic logs go to stderr; stdout is reserved for JSON-RPC messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 443.5456, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 463, + "mcp_result_bytes": 544, + "wire_bytes": 579, + "reported_used_tokens": 544, + "working_set_bytes": 629104640, + "peak_working_set_bytes": 684732416 + } + ], + "id": "persistent-mcp-observes-new-writes", + "dimension": "workflow", + "tier": "medium", + "score": 1.0, + "skipped": false, + "detail": "useful-hit=1.00 mrr=1.00 false-inj=0.00 trap-hit=n/a resolution=n/a curve=1.00\u21921.00 (5 episodes: 2 gold, 3 abstention; brain=2 mems)" + } + ], + "by_dimension_tier": { + "retrieval/easy": [ + 1.0, + 1 + ], + "retrieval/hard": [ + 2.75, + 3 + ], + "retrieval/medium": [ + 0.75, + 1 + ], + "workflow/medium": [ + 1.0, + 1 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 0.9, + "n": 5, + "ci95": 0.12002499739637572 + }, + "workflow": { + "mean": 1.0, + "n": 1, + "ci95": null + } + }, + "overall_index": 0.95, + "scenario_weighted_index": 0.9166666666666666 +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-retrieval/contract/comparison.json b/docs/audits/2026-09-07-retrieval/contract/comparison.json new file mode 100644 index 0000000..05c9afb --- /dev/null +++ b/docs/audits/2026-09-07-retrieval/contract/comparison.json @@ -0,0 +1,237 @@ +{ + "schema_version": 1, + "status": "complete", + "harness": { + "path": "E:\\Kimetsu\\bench\\target\\release\\kbench.exe", + "sha256": "fcb3d2ee52aaa040a1f54eb26a8245fa833a4cd35bed51505ddce76d31e8df9c", + "bytes": 9348096 + }, + "runner": { + "path": "E:\\tmp\\kimetsu-brain-hardening\\bench\\scripts\\compare_brainbench.py", + "sha256": "9b2f79c38a34a13d4f95ef9e19972eb0ccdba9e0e585a8df7a637ad7d06332a8", + "bytes": 23909 + }, + "binaries": { + "baseline": { + "path": "E:\\tmp\\kimetsu-brain-hardening\\tmp-tests\\kimetsu-candidate.exe", + "sha256": "5c87e542907a47917f23fad50ddff1e46789eaae14328ccc662ca748b66b5477", + "bytes": 47080448 + }, + "candidate": { + "path": "E:\\tmp\\kimetsu-brain-hardening\\tmp-tests\\kimetsu-retrieval-candidate.exe", + "sha256": "2445e8adc06c4a6b59d5ee51be46a6f5aa6e777514dfbb60ac4b136b69ba0ba6", + "bytes": 47064064 + } + }, + "datasets": [ + { + "path": "E:\\tmp\\kimetsu-brain-hardening\\bench\\datasets\\brainbench\\agent-memory-contract.json", + "sha256": "ef30967945d2230f84539f2c81d275f00c48765b5f8850a44068948f6825ace4", + "bytes": 8300 + } + ], + "settings": { + "budget_tokens": 2048, + "dimensions": [ + "poisoning", + "render-contract", + "retrieval", + "workflow" + ], + "jobs": 1, + "warm_start": false, + "include_ambient": false, + "overrides": { + "KIMETSU_BRAIN_EMBEDDER": "bge-small-en-v1.5", + "KIMETSU_DETECT_CONFLICTS": "0", + "KIMETSU_RESOLVE_CONFLICTS": "0", + "FASTEMBED_CACHE_DIR": "E:/Kimetsu/.fastembed_cache", + "HF_HOME": "E:/tmp/kimetsu-brain-hardening/tmp-tests/hf-home" + }, + "baseline_threads": 0, + "candidate_threads": 0, + "baseline_reranker": "ms-marco-tinybert-l-2-v2", + "candidate_reranker": "mmarco-minilm-l12-v2-int8", + "baseline_rerank_floor": null, + "candidate_rerank_floor": 0.55 + }, + "runs": [ + { + "label": "baseline", + "repeat": 1, + "intra_threads_override": null, + "rerank_floor_override": null, + "reranker_override": "ms-marco-tinybert-l-2-v2", + "wall_seconds": 167.34423210000386, + "report_file": "1-baseline.json" + }, + { + "label": "candidate", + "repeat": 1, + "intra_threads_override": null, + "rerank_floor_override": "0.55", + "reranker_override": "mmarco-minilm-l12-v2-int8", + "wall_seconds": 30.990422799950466, + "report_file": "1-candidate.json" + }, + { + "label": "candidate", + "repeat": 2, + "intra_threads_override": null, + "rerank_floor_override": "0.55", + "reranker_override": "mmarco-minilm-l12-v2-int8", + "wall_seconds": 30.323271200002637, + "report_file": "2-candidate.json" + }, + { + "label": "baseline", + "repeat": 2, + "intra_threads_override": null, + "rerank_floor_override": null, + "reranker_override": "ms-marco-tinybert-l-2-v2", + "wall_seconds": 25.16442939999979, + "report_file": "2-baseline.json" + }, + { + "label": "baseline", + "repeat": 3, + "intra_threads_override": null, + "rerank_floor_override": null, + "reranker_override": "ms-marco-tinybert-l-2-v2", + "wall_seconds": 20.796417599951383, + "report_file": "3-baseline.json" + }, + { + "label": "candidate", + "repeat": 3, + "intra_threads_override": null, + "rerank_floor_override": "0.55", + "reranker_override": "mmarco-minilm-l12-v2-int8", + "wall_seconds": 30.27207519998774, + "report_file": "3-candidate.json" + } + ], + "comparison": { + "measurement_summary": { + "baseline": { + "unique_queries": 22, + "query_observations": 66, + "positive_queries": 11, + "negative_queries": 11, + "stale_queries": 2, + "positive_recall_at_4": 0.7272727272727273, + "positive_hit_at_4": 0.7272727272727273, + "positive_mrr": 0.7272727272727273, + "negative_injection_rate": 0.18181818181818182, + "stale_injection_rate": 0, + "first_query_mean_ms": 1018.891488888889, + "subsequent_query_p50_ms": 428.7726, + "subsequent_query_p95_ms": 801.7253, + "subsequent_observations": 48, + "mean_model_text_bytes": 344.90909090909093, + "mean_mcp_result_bytes": 416.90909090909093, + "memory_observations": 66, + "mean_mcp_working_set_bytes": 214227812.84848484, + "max_mcp_peak_working_set_bytes": 248709120, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + }, + "candidate": { + "unique_queries": 22, + "query_observations": 66, + "positive_queries": 11, + "negative_queries": 11, + "stale_queries": 2, + "positive_recall_at_4": 0.9545454545454546, + "positive_hit_at_4": 1, + "positive_mrr": 1.0, + "negative_injection_rate": 0.09090909090909091, + "stale_injection_rate": 0, + "first_query_mean_ms": 2163.8191444444446, + "subsequent_query_p50_ms": 444.8203, + "subsequent_query_p95_ms": 470.4541, + "subsequent_observations": 48, + "mean_model_text_bytes": 359.1363636363636, + "mean_mcp_result_bytes": 431.95454545454544, + "memory_observations": 66, + "mean_mcp_working_set_bytes": 629898457.2121212, + "max_mcp_peak_working_set_bytes": 685191168, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + } + }, + "by_dimension": { + "retrieval": { + "n_scenarios": 5, + "baseline": 0.7, + "candidate": 0.9, + "mean_delta": 0.2, + "ci95": [ + -0.1, + 0.55 + ], + "wins": 2, + "ties": 2, + "losses": 1 + }, + "workflow": { + "n_scenarios": 1, + "baseline": 1.0, + "candidate": 1.0, + "mean_delta": 0.0, + "ci95": null, + "wins": 0, + "ties": 1, + "losses": 0 + } + }, + "scenarios": [ + { + "identity": "retrieval/cross-language-code", + "dimension": "retrieval", + "baseline": 0.25, + "candidate": 1.0, + "delta": 0.75 + }, + { + "identity": "retrieval/exact-code-evidence", + "dimension": "retrieval", + "baseline": 1.0, + "candidate": 1.0, + "delta": 0.0 + }, + { + "identity": "retrieval/live-temporal-applicability", + "dimension": "retrieval", + "baseline": 0.5, + "candidate": 1.0, + "delta": 0.5 + }, + { + "identity": "retrieval/multi-fact-retrieval", + "dimension": "retrieval", + "baseline": 1.0, + "candidate": 0.75, + "delta": -0.25 + }, + { + "identity": "retrieval/related-but-unanswerable", + "dimension": "retrieval", + "baseline": 0.75, + "candidate": 0.75, + "delta": 0.0 + }, + { + "identity": "workflow/persistent-mcp-observes-new-writes", + "dimension": "workflow", + "baseline": 1.0, + "candidate": 1.0, + "delta": 0.0 + } + ], + "unpaired_scenarios": [], + "unpaired_details": [], + "baseline_errors": 0, + "candidate_errors": 0, + "repeats": 3, + "uncertainty_note": "Exploratory paired bootstrap over scenario IDs after averaging repeats; correlated task families require a separate grouped holdout." + } +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-retrieval/development-100.json b/docs/audits/2026-09-07-retrieval/development-100.json new file mode 100644 index 0000000..c316519 --- /dev/null +++ b/docs/audits/2026-09-07-retrieval/development-100.json @@ -0,0 +1,1678 @@ +{ + "scenarios": [ + { + "id": "existing-development-100", + "dimension": "retrieval", + "tier": "hard", + "description": "Existing development corpus from the model audit, now measured through persistent production MCP. Not held-out data.", + "memories": [ + { + "key": "mutex-deadlock-user-brain-disabled", + "text": "[tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure — `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation. (context: New tests for Tier-1 perf work called test_env_lock().lock() inside with_user_brain_disabled closure, deadlocking all project::tests that ran after them in the same test binary.)" + }, + { + "key": "remote-ingest-split-roots", + "text": "[tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races. Re-enable kimetsu_brain_ingest_repo in the tool allowlist only when ingest is configured, and INTERCEPT that tools/call in the remote handler (clone+ingest_repo_at_root) before the normal dispatch (which would walk the wrong dir). Hermetic test: git init a temp repo, register url=local path, ingest, then context retrieves the file capsule via FTS (noop embedder). (context: R3c: server-side ingest for kimetsu-remote — cloning repos so file-capsule retrieval works without a local checkout.)" + }, + { + "key": "remote-mcp-host-wiring", + "text": "[tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal. Derive a stable repo id from the git remote: strip `.git`, scheme (`://`), and `user@`, then map non-alphanumerics to '-' and collapse — so both https://github.com/org/repo.git and git@github.com:org/repo.git -> `github-com-org-repo`. Remote install writes ONLY the MCP entry + instructions (no local hooks — the brain is on the server). Codex/Pi don't get --remote (no remote-MCP / no MCP). (context: R2: implementing `kimetsu plugin install --remote` to wire a host at a kimetsu-remote HTTP MCP server.)" + }, + { + "key": "cargo-feature-unification-embeddings", + "text": "[tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli). Diagnostic tell: a test that passes alone but fails only under `cargo test --workspace` AND a brand-new crate was just added = suspect feature unification flipping a sibling crate's behavior. (context: Building the kimetsu-remote crate (HTTP MCP server); its default embeddings feature broke 3 kimetsu-chat retrieval tests only under the full workspace test.)" + }, + { + "key": "bedrock-kimetsu-provider", + "text": "[tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env. Wire \"bedrock\" into BOTH pipeline.rs provider matches AND the distiller (normalize_distiller_provider + instantiation); the distiller is configured independently so agent-on-Bedrock + harvester-on-direct-Claude works for free. Sign and send the SAME payload bytes; test signing determinism with a fixed SystemTime. (context: Workstream A: adding AWS Bedrock as a provider for the agent + auto-harvester in v1.0.0.)" + }, + { + "key": "bridge-target-enum-seams", + "text": "[tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors. (context: Adding BridgeTarget::OpenClaw host to Kimetsu bridge.rs and main.rs in Workstream C)" + }, + { + "key": "pi-openclaw-extension-api", + "text": "[tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`. External commands use `pi.exec()` but `node:child_process` spawn also works. Pi has NO MCP so Kimetsu integrates via TS extension + SKILL.md only. (context: Implementing Pi host target for Kimetsu plugin install/status/uninstall system.)\n\nAlso: [tags: kimetsu host-integration pi openclaw bridge] When integrating Kimetsu with an external host agent (Pi, OpenClaw, etc.), VERIFY the host's real plugin/extension API against its actual repo before writing embedded assets — docs-from-memory are frequently wrong. Concretely corrected during v1.0: Pi uses a default-export factory `export default function(pi)` (not `defineExtension`) with lifecycle events `session_start`/`agent_end`/`session_shutdown`; OpenClaw plugin entry is `index.ts` via `definePluginEntry` from `openclaw/plugin-sdk/plugin-entry` + an `openclaw.plugin.json` manifest, with snake_case hook events `agent_turn_prepare`/`agent_end`/`session_end` (NOT colon-delimited). Always make the embedded hook shell-out a silent no-op if the `kimetsu` binary isn't on PATH so a wrong guess never breaks the host. (context: Adding Pi + OpenClaw as BridgeTarget hosts in v1.0.0; the inferred extension/plugin APIs from docs were wrong and had to be corrected against the real repos.)" + }, + { + "key": "aws-sigv4-bedrock-blocking", + "text": "[tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed. aws-smithy-runtime-api required as a companion to supply Identity. (context: Implementing BedrockProvider for Kimetsu with blocking reqwest + SigV4 signing, no tokio/aws-sdk)" + }, + { + "key": "gc-trace-env-guard-placement", + "text": "[tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site. (context: QQ4 — runs auto-GC on run creation. Env guard placement decision when wiring opportunistic GC into TraceWriter::create.)" + }, + { + "key": "init-project-git-boundary", + "text": "[tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain. (context: QQ3 — kimetsu setup integration test failed because init_project climbed git tree to real ~/.kimetsu instead of temp workspace)" + }, + { + "key": "clap-version-build-flavor", + "text": "[tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds. (context: QQ2: --version build flavor + plugin install self-check)" + }, + { + "key": "harbor-terminal-bench-subprocess-isolation", + "text": "[tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd). Worker re-derives auth internally from .env so the OAuth token never lands in argv; it writes {run,grade} JSON the parent reads back. One Harbor invocation per process always works (baseline-alone passed). (context: kbench multi-trial sweeps crashed on every trial after the 1st; diagnosed as Harbor/pyiceberg os.getcwd staleness on WSL2.)" + }, + { + "key": "sqlite-vacuum-wal-checkpoint", + "text": "[tags: rust sqlite vacuum rusqlite windows] When implementing SQLite VACUUM in rusqlite: VACUUM cannot run inside a transaction. rusqlite's Connection does not hold an implicit transaction, so `conn.execute_batch(\"VACUUM;\")` works directly. After VACUUM, run `PRAGMA wal_checkpoint(TRUNCATE);` before measuring file size — on Windows the WAL file can hold significant space that isn't reflected in the main db file until the checkpoint runs. (context: Implementing kimetsu brain compact (Q8) — SQLite VACUUM + WAL checkpoint for accurate post-compact file size.)" + }, + { + "key": "import-dedup-seen-ids", + "text": "[tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount — both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise. (context: Implementing brain export/import (Q5). First naive approach used a single `seen_ids` set local to the function; the dedup test caught it on the second-import assertion.)" + }, + { + "key": "toml-value-parse", + "text": "[tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table. (context: Implementing config get/set with toml::Value navigation; str.parse() failed with 'unexpected content' error on document strings.)" + }, + { + "key": "process-start-time-cross-platform", + "text": "[tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path). Keep the skew decision logic in a pure function `assess_mcp_skew(servers, binary_mtime, binary_path) -> Outcome` so it can be unit-tested without any live OS state. (context: Q3 — kimetsu doctor version-skew check for stale MCP server processes)" + }, + { + "key": "windows-update-process-locking", + "text": "[tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics — mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code. (context: Q2 — kimetsu update preflight for locked binary on Windows)" + }, + { + "key": "cfg-cross-platform-dead-code", + "text": "[tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform. (context: Adding parse_unix_ps to kimetsu-cli/src/process.rs — used only on Unix at runtime but needed on Windows for cross-platform unit tests.)" + }, + { + "key": "sqlite-busy-timeout-wal", + "text": "[tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch. Set the timeout before any transaction, not inside one — it is a connection-level property. (context: Kimetsu brain writer and reader processes sharing the same SQLite brain database.)" + }, + { + "key": "sqlite-wal-network-drive", + "text": "[tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db. Fallback: `PRAGMA journal_mode=DELETE;` is safe over SMB at the cost of lower concurrency. Detect network drives at startup with `GetFileAttributes` checking FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS or using `PathIsNetworkPath`. (context: Users running kimetsu with the brain database on a mapped network drive.)" + }, + { + "key": "sqlite-fts5-tokenizer", + "text": "[tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon. If you switch tokenizers on an existing FTS5 table, you MUST rebuild the shadow tables: `INSERT INTO tbl(tbl) VALUES('rebuild');` — a schema-only change leaves the inverted index unusable. The `porter` stemmer is available as `tokenize='porter unicode61'` but aggressively strips suffixes and hurts precision on technical terms. (context: Kimetsu brain FTS5 index tuning for Rust identifier retrieval.)" + }, + { + "key": "sqlite-page-size", + "text": "[tags: sqlite page_size performance rusqlite] SQLite's default page_size is 4096 bytes. For a write-heavy brain database with large BLOB payloads (embedding vectors), raising page_size to 16384 reduces fragmentation and improves sequential scan throughput. `PRAGMA page_size = 16384;` must be set BEFORE the first table is created — changing it on an existing database requires a VACUUM afterward to rebuild all pages. Verify it took effect with `PRAGMA page_size;` after VACUUM. rusqlite's `Connection::open` runs no implicit PRAGMA, so set this in the connection init path. (context: Tuning the kimetsu brain SQLite schema for embedding vector storage.)" + }, + { + "key": "sqlite-foreign-keys-default-off", + "text": "[tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting — every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing. Check your schema with `PRAGMA foreign_key_list(table_name);` and your current setting with `PRAGMA foreign_keys;`. rusqlite does not enable foreign keys automatically. (context: Kimetsu brain schema — memory_tags table has FK to memories table, discovered ON DELETE CASCADE wasn't firing.)" + }, + { + "key": "sqlite-json1-extract", + "text": "[tags: sqlite json1 json_extract rusqlite] SQLite's json1 extension (built in since 3.38.0) lets you index and query JSONB columns with `json_extract(col, '$.field')`. To create a partial index over a JSON field: `CREATE INDEX idx ON memories (json_extract(metadata, '$.scope')) WHERE json_extract(metadata, '$.scope') IS NOT NULL;`. Use `json_each` for array fields. On older SQLite builds (rusqlite links whatever the system provides), check for json1 with `SELECT json('{}');` — an error means it's absent. Always prefer column storage over JSON blobs for frequently queried fields. (context: Kimetsu brain querying metadata scopes without migrating a separate column.)" + }, + { + "key": "sqlite-prepared-stmt-cache", + "text": "[tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8). The cache key is the SQL string verbatim, so template strings with interpolated values defeat caching — use `?1, ?2` placeholders instead. Calling `prepare_cached` in a tight loop is effectively free after warmup. (context: Kimetsu brain high-throughput ingest path — replacing prepare() with prepare_cached() cut ingest time by ~30%.)" + }, + { + "key": "sqlite-partial-index", + "text": "[tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query — the planner uses the partial index only when the WHERE clause matches. Verify index usage with `EXPLAIN QUERY PLAN SELECT ...`. Partial indexes are not supported before SQLite 3.8.0; rusqlite's bundled SQLite is always current, but system SQLite on old Debian/Ubuntu may not be. (context: Optimizing kimetsu brain retrieval query over the active-memories subset.)" + }, + { + "key": "cargo-lockfile-drift", + "text": "[tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this — it errors on any lockfile diff. For library crates, `Cargo.lock` is normally gitignored, but for workspace roots with binary crates it should be committed. Use `cargo update --precise ` to pin a specific dep version without touching unrelated entries. (context: Kimetsu workspace lockfile drift after adding kimetsu-remote crate.)" + }, + { + "key": "cargo-build-script-rerun", + "text": "[tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory. If the build script generates code from a schema file, emit `rerun-if-changed=schema.json`. If there are NO inputs (e.g. the script only inspects env vars), emit `cargo:rerun-if-changed=` with an empty string to suppress re-runs entirely. Missing this directive is the most common cause of unexpectedly slow incremental builds. (context: kimetsu-cli build.rs for embedding version stamps.)" + }, + { + "key": "cargo-dev-dep-leak", + "text": "[tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates. Run `cargo tree --features ` to trace which crate activated an unexpected feature. (context: Kimetsu testing infra — a dev-dep was activating the embeddings feature in non-test builds.)" + }, + { + "key": "cargo-target-dir-sharing", + "text": "[tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps — use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination. (context: Kimetsu development on Windows with Windows Defender causing intermittent link failures.)" + }, + { + "key": "cargo-incremental-cache-corruption", + "text": "[tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase. Fix: `cargo clean` then rebuild. Adding `CARGO_INCREMENTAL=0` to CI matrices prevents this class of false failures. (context: Kimetsu development — spurious type mismatch errors after branch switches.)" + }, + { + "key": "cargo-profile-override", + "text": "[tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug. The downside: rebuild time increases for that crate. For overflow-checks, `overflow-checks = false` per package speeds up hot loops. Never disable overflow-checks in release for business-critical data-mutating code. `[profile.release] strip = \"debuginfo\"` reduces binary size with minimal impact on stack traces. (context: Kimetsu dev experience — embedding inference was 10x slower in debug builds.)" + }, + { + "key": "cargo-patch-section", + "text": "[tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace — including transitive deps — that depend on `my-crate`. Remove the patch before publishing. Using `[replace]` is deprecated since Cargo 0.47; always use `[patch]`. When patching a crate pinned via an exact version specifier, the patch must satisfy that exact version. Use `cargo tree` to confirm the patch is applied. (context: Kimetsu patching upstream rusqlite for a Windows-specific WAL fix.)" + }, + { + "key": "cargo-msrv", + "text": "[tags: cargo rust msrv edition compatibility] Set `rust-version` in each `Cargo.toml` to declare the minimum supported Rust version (MSRV). Cargo enforces this with `--check`: `cargo check` fails if the toolchain is older than `rust-version`. Keep MSRV as old as your oldest supported deployment target. When bumping MSRV, update the CI matrix and the workspace root. Common trap: a transitive dep bumps its MSRV, pulling yours up silently — check with `cargo msrv` (cargo-msrv crate) or `cargo tree -e features | grep msrv`. Edition 2021 requires Rust >= 1.56.0. (context: Kimetsu workspace MSRV policy — ensuring it runs on the LTS toolchain.)" + }, + { + "key": "windows-long-paths", + "text": "[tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe. On Windows 10 1607+ the LongPathsEnabled key is sufficient for most tools. `cargo build` itself works after the registry change; MSI installers may still fail on paths > 260 in the installer runtime. (context: Kimetsu CI on Windows Server 2019 — build failed with OS error 3 on deeply nested proc-macro paths.)" + }, + { + "key": "windows-file-locking-av", + "text": "[tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine. For CI, use GitHub-hosted Windows runners which don't have real-time AV. Alternatively, build to a different directory with `CARGO_TARGET_DIR=C:\\tmp\\target`. The error is non-deterministic — it only appears when AV scanning races with the link step. (context: Kimetsu development on Windows — intermittent linker errors.)" + }, + { + "key": "windows-unc-paths", + "text": "[tags: windows unc-paths rust std::fs] Windows UNC paths (`\\\\server\\share\\...`) are not supported by most Rust `std::fs` operations unless passed through the extended-length prefix `\\\\?\\UNC\\server\\share\\...`. `std::path::Path::new(\"\\\\\\\\server\\\\share\")` works for basic operations but breaks with `canonicalize()` which returns the verbatim prefix form. When walking directory trees that may start on UNC paths, use the `dunce` crate to strip the verbatim prefix before comparing or displaying paths. Never `cd` into a UNC path in a subprocess started with `std::process::Command` — the subprocess may not inherit it correctly on older Windows. (context: Kimetsu ingest walking paths on network-mounted project directories.)" + }, + { + "key": "windows-console-encoding", + "text": "[tags: windows console encoding utf8 rust] Windows console code page defaults to the system ANSI code page (usually CP1252 or CP932), not UTF-8. Rust's `println!` writes UTF-8 bytes which display as mojibake in a non-UTF-8 console. Fix at process startup: call `SetConsoleOutputCP(65001)` via `winapi` or `windows-sys`, or set `PYTHONUTF8=1`/`RUST_LOG` before launch. In PowerShell, `[Console]::OutputEncoding = [System.Text.Encoding]::UTF8` fixes the session. For binary piped output (MCP stdio protocol), write raw bytes — don't use the console code page. (context: Kimetsu MCP server — Unicode memory text was garbled on non-UTF8 Windows terminals.)" + }, + { + "key": "windows-junctions-vs-symlinks", + "text": "[tags: windows junctions symlinks rust std::fs] On Windows, directory junctions (NTFS reparse points) behave like symlinks for directory traversal but `std::fs::symlink_metadata` returns `FileType::is_symlink() = false` for junctions (only true for regular symlinks). Use `std::fs::read_link` — it succeeds for both junction and symlink. `walkdir` crate's `follow_links` follows both, but its `is_symlink()` method correctly reports only actual symlinks. Creating symlinks requires SeCreateSymbolicLinkPrivilege (admin or Developer Mode). Creating junctions requires no special privilege. Use junctions for internal tooling that doesn't need to cross volumes. (context: Kimetsu path handling for brain symlink detection on Windows.)" + }, + { + "key": "windows-exit-codes", + "text": "[tags: windows exit-codes rust process child] On Windows, process exit codes are 32-bit unsigned integers (DWORD). Rust's `ExitStatus::code()` returns `Option` — it's `None` if the process was killed by a signal (which Windows doesn't use; instead, TerminateProcess with a code). Conventional codes: 0=success, 1=generic error, 0xC0000005=access violation. Programs that call `std::process::exit(-1)` on Windows produce exit code 0xFFFFFFFF (4294967295), not -1. When checking for success in a subprocess chain, always check `status.success()` rather than `status.code() == Some(0)` to handle this portably. (context: Kimetsu update binary replacement — exit code handling.)" + }, + { + "key": "windows-registry-rust", + "text": "[tags: windows registry rust winreg read write] Reading and writing the Windows registry from Rust requires the `winreg` crate. Open a key with `RegKey::predef(HKEY_LOCAL_MACHINE).open_subkey_with_flags(path, KEY_READ)` — use `KEY_READ` for reads and `KEY_READ | KEY_WRITE` for writes (NOT `KEY_ALL_ACCESS`, which requires admin). To set a DWORD value: `key.set_value(\"LongPathsEnabled\", &1u32)`. Registry paths use backslash separators and are case-insensitive. Prefer reading env vars over registry for runtime config — registry reads are expensive (kernel transition) and inappropriate for hot paths. For kimetsu, registry access is limited to the `kimetsu doctor` check for long-path enablement. (context: Kimetsu doctor — checking LongPathsEnabled registry value on Windows.)" + }, + { + "key": "onnx-tokenizer-mismatch", + "text": "[tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly — specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings — cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo. Validate by checking a reference embedding against the HuggingFace Python output. (context: Kimetsu custom ONNX reranker loading — wrong tokenizer produced degraded retrieval.)" + }, + { + "key": "onnx-quantization-drift", + "text": "[tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals — cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case. (context: Kimetsu embedding model selection — evaluating jina-v2 int8 vs fp32.)" + }, + { + "key": "onnx-batch-padding", + "text": "[tags: onnx batch padding attention-mask embeddings] When running batch inference with an ONNX model, all inputs in the batch must be padded to the same sequence length. The `attention_mask` tensor marks which tokens are real (1) and which are padding (0). Failing to pass `attention_mask` causes the model to average-pool over padding tokens, producing systematically lower-norm embeddings. With ORT (ort crate), construct the mask as a 2-D i64 tensor `[batch, seq_len]` with 1s for real tokens and 0s for padding. For variable-length batches, pad to `max(lengths)` in the batch, not to `model.max_length`. (context: Kimetsu embedding batch inference with ORT — missing attention mask caused MRR degradation.)" + }, + { + "key": "onnx-model-cache-paths", + "text": "[tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use. In kimetsu, `KIMETSU_EMBEDDER_CACHE` overrides the path and is forwarded when spawning child bench processes — without forwarding it, each child re-downloads the model. (context: Kimetsu brain bench on CI — model cache path handling in child processes.)" + }, + { + "key": "onnx-cosine-vs-dot", + "text": "[tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing — double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g. E5, GTE with separate query/passage prefixes), the query and document encoders must use different prefix strings. Check the model card's `Similarity function` field. usearch/qdrant: prefer `MetricKind::Cos` over `Dot` for passage vectors that may not be perfectly normalized. (context: Kimetsu embedding storage — similarity metric selection.)" + }, + { + "key": "onnx-dim-mismatch", + "text": "[tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results — the ANN index shape mismatch isn't always caught at runtime. kimetsu detects this by storing `embedder_id` in the brain schema and refusing to query if the configured embedder differs from what was used at ingest time. Mitigation: re-ingest all memories with the new model, or keep per-memory vector dim metadata. (context: Kimetsu embedder migration — detecting dimension mismatch at startup.)" + }, + { + "key": "onnx-prefix-instructions", + "text": "[tags: onnx embeddings prefix instruction e5 query passage] E5 and Instructor family models require a text prefix on BOTH query and passage sides to produce meaningful similarities: query prefix `\"query: \"`, passage prefix `\"passage: \"`. Omitting the prefix can drop MRR by 10-15 percentage points on out-of-domain datasets. Check the model's README for the exact prefix string — it varies by model family. In kimetsu, the embedder abstraction has `query_prefix` and `passage_prefix` fields; FallbackEmbedder uses `\"\"` for both. jina-v2-base-code and bge-small use `\"\"` prefixes. (context: Kimetsu embedder trait design — prefix handling for E5/Instructor models.)" + }, + { + "key": "onnx-ort-threading", + "text": "[tags: onnx ort thread-pool parallelism cpu] ORT (ONNX Runtime) creates its own inter-op and intra-op thread pools. In a multi-process bench setup, each child inherits these pools and they compete for CPU cores. Set `SessionOptionsBuilder::with_intra_threads(1).with_inter_threads(1)` if you're running many parallel bench processes — this sacrifices per-inference throughput for lower contention. In a single-threaded embedding pipeline, 2-4 intra-op threads are better. For benchmarking, set `ORT_NUM_THREADS=1` via env var to get deterministic single-threaded latency numbers. (context: Kimetsu brain bench multi-process parallelism — ORT thread contention causing inconsistent latency.)" + }, + { + "key": "git-worktree-brain-isolation", + "text": "[tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root — if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain. Always set `KIMETSU_BRAIN_DIR` or use `git_init_boundary` in tests to prevent this. (context: Kimetsu development with git worktrees — test isolation.)" + }, + { + "key": "git-hooks-bypass", + "text": "[tags: git hooks bypass pre-commit skip] `git commit --no-verify` skips ALL hooks (pre-commit and commit-msg). Never use this in shared team repos where hooks enforce quality gates (lint, tests, memory harvest). Instead, fix the failing hook. If the hook itself is broken, fix the hook script. For emergency commits where hooks aren't relevant (e.g. updating a gitignore to untrack already-committed files), document the `--no-verify` use in the commit message. In CI, hooks run only if explicitly invoked — `git commit` in a CI pipeline with no hooks configured does nothing for quality enforcement. (context: Kimetsu pre-commit hook enforcing memory harvest.)" + }, + { + "key": "git-sparse-checkout", + "text": "[tags: git sparse-checkout partial-clone bandwidth] `git sparse-checkout init --cone` combined with `git clone --filter=blob:none` (partial clone) fetches only the commit graph and tree objects, not blobs. Individual blobs are fetched on demand when accessed. This cuts clone time for large repos from minutes to seconds. For kimetsu server-side ingest, use `git clone --depth 1 --filter=blob:none` for the initial checkout, then `git sparse-checkout set ` to limit the working tree to indexed directories. On `git fetch --depth 1 origin main` for refresh, blobs in the sparse set are updated lazily. (context: Kimetsu remote ingest — reducing bandwidth and disk usage for large repo checkouts.)" + }, + { + "key": "git-line-endings-windows", + "text": "[tags: git line-endings windows crlf autocrlf] On Windows, `core.autocrlf=true` (git's default for Windows installs) converts LF to CRLF on checkout and CRLF to LF on commit. This causes spurious diffs when files are edited on Windows then committed — the content is identical but the line endings differ in the index vs the working tree. Fix: set `core.autocrlf=false` and `.gitattributes` with `* text=auto eol=lf` for the repo. For Rust projects, all source files should be LF; only Windows batch scripts need CRLF. Warn: AV scanners that modify newly written files can re-introduce CRLF in files Rust writes. (context: Kimetsu CI — spurious diffs from Windows CRLF conversion.)" + }, + { + "key": "git-submodule-pinning", + "text": "[tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip — this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version. If a submodule is the kimetsu-bench repo inside the main repo, pin the bench SHA after validating the dataset change. Use `git diff HEAD -- bench` to see the pinned SHA change before committing. (context: Kimetsu bench as a git submodule of the main repo.)" + }, + { + "key": "git-reflog-rescue", + "text": "[tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone — they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only — remote reflog is not accessible via normal git commands. If you need the remote version, use `git fetch origin +refs/heads/main:refs/heads/main-backup` before a force push. In kimetsu bench development, always create a branch before destructive rebases. (context: Kimetsu bench dataset recovery after accidental hard reset.)" + }, + { + "key": "tokio-blocking-in-async", + "text": "[tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking — never call rusqlite directly from an async fn without spawn_blocking. fastembed inference is also blocking (ONNX Runtime is synchronous). The threshold: any operation taking more than 100 microseconds that can't be made async belongs in spawn_blocking. Ignoring this causes tail-latency spikes and request timeouts under load in kimetsu-remote. (context: Kimetsu remote server — SQLite and embedding calls from async handlers.)" + }, + { + "key": "tokio-runtime-in-tests", + "text": "[tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests. For sync test code that calls async, use `tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async { ... })`. Never call `block_on` from inside an async function. (context: Kimetsu remote integration tests — nested runtime panic.)" + }, + { + "key": "tokio-select-cancellation", + "text": "[tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded. For correctness, cancelled futures must be cancellation-safe: holding no partially committed state. `tokio::sync::watch::Receiver::changed()` is cancellation-safe; `tokio::sync::mpsc::Sender::send()` is NOT (the item is lost). In kimetsu shutdown, use a `CancellationToken` and `select!` branches that are all cancellation-safe. (context: Kimetsu remote graceful shutdown — race between incoming requests and shutdown signal.)" + }, + { + "key": "tokio-channel-backpressure", + "text": "[tags: tokio mpsc channel backpressure async rust] `tokio::sync::mpsc::channel(N)` with a bounded buffer provides backpressure: senders block when the buffer is full. This prevents unbounded memory growth but can cause sender tasks to stall. Choosing N: too small causes frequent backpressure (throughput drops); too large defeats the purpose. For kimetsu's harvest pipeline, N=16 was a good balance — the harvester is I/O bound (LLM call), producers are fast (hook callbacks). Prefer bounded channels over unbounded in production code. `tokio::sync::mpsc::unbounded_channel()` is a footgun for bursty producers. (context: Kimetsu auto-harvester pipeline — bounded vs unbounded channel selection.)" + }, + { + "key": "tokio-spawn-blocking", + "text": "[tags: tokio spawn_blocking thread-pool rust blocking] `tokio::task::spawn_blocking` places work on a dedicated blocking thread pool (default up to 512 threads, configurable via `Builder::max_blocking_threads`). Each call creates or reuses a thread — there's no true pooling, threads may be created on demand. For many short-duration blocking calls (e.g. per-query SQLite reads), thread creation overhead may dominate. Prefer batching: collect N queries, then one `spawn_blocking` to run them all. Alternatively, keep a persistent blocking task that reads from an mpsc channel. Profile with `tokio-console` if you suspect spawn_blocking overhead. (context: Kimetsu retrieval server — per-query spawn_blocking was adding ~0.3ms overhead.)" + }, + { + "key": "tokio-shutdown-ordering", + "text": "[tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries — the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks. `axum::Server::with_graceful_shutdown` handles steps 1-2; you must handle 3-5 manually. (context: Kimetsu remote server graceful shutdown implementation.)" + }, + { + "key": "http-connection-pooling", + "text": "[tags: http reqwest connection-pool keep-alive rust] reqwest's `Client` holds a connection pool; always create ONE `Client` instance and clone it for each handler — cloning is cheap (Arc under the hood). Creating a `Client::new()` per request defeats connection pooling and causes TCP connection exhaustion under load. The default pool settings: max_idle_per_host=usize::MAX (unbounded), idle_timeout=90s. For a kimetsu outbound client (LLM provider), set `pool_max_idle_per_host(5)` to limit idle connections. On Windows, the underlying hyper+winapi stack may not reuse connections as aggressively as on Linux — set `connection_verbose(true)` on the builder to confirm reuse. (context: Kimetsu provider HTTP client — connection pooling best practices.)" + }, + { + "key": "http-timeout-layering", + "text": "[tags: http reqwest timeout connect read total rust] reqwest has three distinct timeout knobs: `connect_timeout`, `read_timeout`, and `timeout` (total). They compose: if all three are set, the request fails at whichever fires first. For LLM API calls with streaming responses, `read_timeout` must be larger than the slowest expected token (often 30-60s) while `connect_timeout` can be tight (3-5s). `timeout` should be your SLA ceiling. If you set only `timeout`, a slow connect eats into the overall budget. For kimetsu-remote, set both `connect_timeout(5s)` and `timeout(120s)` — the LLM call is the bottleneck. (context: Kimetsu provider timeouts — request timing out during streaming.)" + }, + { + "key": "http-retry-idempotency", + "text": "[tags: http retry idempotency post put reqwest] Only retry idempotent requests automatically. GET, HEAD, PUT, DELETE are idempotent. POST is NOT — retrying a POST may create duplicate resources. For LLM API calls (POST), implement retry with idempotency keys: include a stable `X-Idempotency-Key: ` header; the provider deduplicates. For transient 429 (rate limit) responses, back off with jitter: `min(base * 2^attempt, cap) + rand(0, base)`. For 5xx, retry at most 3 times. Never retry on 4xx (except 429). In kimetsu, retry logic lives in the provider layer, not the distiller. (context: Kimetsu LLM provider retry strategy.)" + }, + { + "key": "http-tls-roots", + "text": "[tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle — the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle. Alternatively, add the custom root with `add_root_certificate`. On Linux, the system CA bundle is at `/etc/ssl/certs/ca-certificates.crt`; on Windows it's in the Windows Certificate Store. (context: Kimetsu on a corporate Windows machine with a custom proxy CA.)" + }, + { + "key": "http-streaming-bodies", + "text": "[tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding — a chunk may split across frame boundaries. In kimetsu's proxy path, accumulate bytes until `\n\n` (SSE frame delimiter) before parsing the JSON data field. Never assume one `.chunk()` call = one SSE event. (context: Kimetsu remote proxy — streaming LLM responses to the client.)" + }, + { + "key": "http-proxy-env", + "text": "[tags: http proxy environment reqwest rust corporate] reqwest respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` environment variables by default (with `default-tls` or `rustls-tls`). In a corporate network, these may redirect traffic through an intercepting proxy that breaks mTLS or adds latency. To disable proxy usage entirely: `reqwest::ClientBuilder::no_proxy()`. On Windows, reqwest does NOT use the system proxy settings (IE/WinInet) — you must set env vars explicitly. `NO_PROXY=127.0.0.1,localhost` prevents proxying loopback traffic (important for kimetsu-remote local dev). (context: Kimetsu provider calls failing behind corporate proxy on Windows.)" + }, + { + "key": "testing-snapshot-churn", + "text": "[tags: testing snapshot insta assert churn rust] Snapshot tests (e.g. with the `insta` crate) fail whenever the output changes, even for intended changes. In CI, they fail loudly; locally, `cargo insta review` walks you through accepting or rejecting changes. Snapshot churn becomes a problem when output includes timestamps, process IDs, or randomly-ordered maps. Redact these before snapshotting: use `insta::with_settings!({redactions: [\".timestamp\" => \"[TIMESTAMP]\"]})`. For JSON output, sort maps and arrays before comparing. Keep snapshot files in `src/snapshots/` and always commit them — an untracked snapshot file causes the next CI run to fail with a different error than expected. (context: Kimetsu CLI output snapshot tests — reducing churn.)" + }, + { + "key": "testing-temp-dirs-ci", + "text": "[tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure. On Windows, `env::temp_dir()` returns `C:\\Users\\\\AppData\\Local\\Temp` — ensure the test binary has write permissions there. Avoid using the workspace root as a temp dir — tests should never write to the source tree. (context: Kimetsu test infrastructure — temp directory discipline.)" + }, + { + "key": "testing-time-dependent-flakes", + "text": "[tags: testing time flaky clock mock rust] Tests that depend on wall-clock time are inherently flaky under load (slow CI runners, GC pauses). Abstract time behind a trait (`Clock: Fn() -> SystemTime`) injected at construction, and supply a fake in tests. For tests checking that something happened \"within N seconds\", use a generous multiple of the expected duration (10x is not unreasonable for CI). `std::thread::sleep` in tests is a smell — prefer channel synchronization or a condvar instead of timing-based waits. If you must use sleep, set `KIMETSU_TEST_TIMEOUT_SCALE` to stretch timeouts in slow environments. (context: Kimetsu GC and TTL tests — time-dependent flakes on loaded CI.)" + }, + { + "key": "testing-property-tests", + "text": "[tags: testing property-based proptest quickcheck rust] Property-based tests (proptest, quickcheck) find edge cases that example-based tests miss. For kimetsu's memory text normalization, proptest found that zero-width joiner characters and right-to-left marks caused hash collisions. Run proptest with `PROPTEST_CASES=10000` in CI for thorough coverage. Shrinking: when proptest finds a failure, it automatically shrinks the input to the minimal failing case — read the `Minimized failure` output, not the original random input. Use `prop_assume!` to skip inputs that violate preconditions rather than `if/return`. (context: Kimetsu brain text normalization — property test for dedup hash stability.)" + }, + { + "key": "testing-serial-vs-parallel", + "text": "[tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`). `cargo nextest` runs each test in a separate process by default, avoiding the problem entirely at the cost of longer startup time. For kimetsu, prefer nextest in CI and accept that `test_env_lock` exists only for `cargo test` compatibility. (context: Kimetsu test suite — env-var mutation in parallel tests.)" + }, + { + "key": "testing-fixture-drift", + "text": "[tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code. For kimetsu, `EvalFixture::from_memories(memories)` constructs a dataset from the exported format — use it in tests instead of hardcoded JSON. Tag fixture files with the schema version they were generated against in a comment. (context: Kimetsu eval fixture drift after schema migration.)" + }, + { + "key": "mcp-stdout-protocol", + "text": "[tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr. `println!` in the request handler is forbidden. Use `eprintln!` or `tracing` with a stderr subscriber. In tests of the MCP server, capture stdout as bytes and validate it parses as JSON-Lines. When debugging, set `KIMETSU_LOG=debug` which writes to stderr only. (context: Kimetsu MCP server stdout protocol hygiene.)" + }, + { + "key": "mcp-tool-timeouts", + "text": "[tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking — in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize — keep it in a process-global `OnceLock`). The reranker adds another 200-800ms; jina-tiny is the fastest. If tool calls are still slow, log the per-stage latency with `tracing::info!` at DEBUG level and profile under load. (context: Kimetsu MCP tool latency optimization.)" + }, + { + "key": "mcp-env-propagation", + "text": "[tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment — changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate. For kimetsu hooks (pre-commit, post-commit), the hook script inherits the shell's env at hook invocation time, not the server's. If `KIMETSU_BRAIN_DIR` needs to vary per project, set it in the project's `.env` file and source it in the hook script. (context: Kimetsu env propagation from hooks to MCP server.)" + }, + { + "key": "mcp-schema-validation", + "text": "[tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array — omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error. Use `serde(default)` for optional fields. When adding a new tool, check the schema in the tools/list response manually by piping a JSON-RPC tools/list request to `./kimetsu mcp`. (context: Kimetsu MCP tool schema — required field validation.)" + }, + { + "key": "mcp-tool-naming", + "text": "[tags: mcp tool naming convention kimetsu] MCP tool names must be valid identifiers for all host agents. Claude Code restricts tool names to `[a-zA-Z0-9_-]` and max 64 chars. Use `snake_case` (kimetsu_brain_context, kimetsu_brain_record) — hyphen is technically allowed but some hosts reject it. Avoid dots (not allowed). Namespace with a prefix (`kimetsu_brain_`) to prevent collisions with other MCP servers. When a tool name changes, update ALL host config files (`.mcp.json`, `openclaw.json`, skill markdown) — mismatched names cause silent failures where the host skips the tool. (context: Kimetsu MCP tool naming convention enforcement.)" + }, + { + "key": "mcp-transcript-paths", + "text": "[tags: mcp transcript paths kimetsu hooks runs] kimetsu writes run transcripts to `/.kimetsu/runs//`. The post-session hook reads the latest run's transcript to trigger memory harvest. On Windows, the path uses backslashes internally but the MCP JSON must use forward slashes or the host may reject path-type arguments. `std::path::Path::display()` produces backslashes on Windows — use `.to_string_lossy().replace('\\\\', \"/\")` when serializing paths for MCP protocol. The transcript path is included in the `kimetsu_brain_context` response under the `run_dir` field for the distiller's reference. (context: Kimetsu transcript path handling in MCP responses on Windows.)" + }, + { + "key": "aws-credentials-chain", + "text": "[tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually. On Windows, `~/.aws` is `%USERPROFILE%\\.aws` — `std::env::var(\"USERPROFILE\")` to get the path since `~` expansion is shell-level. (context: Kimetsu Bedrock provider credential resolution.)" + }, + { + "key": "aws-region-resolution", + "text": "[tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time. For cross-region inference (e.g. us-west-2 for Claude Opus), set `AWS_REGION=us-west-2`; do NOT rely on the Bedrock endpoint prefix being region-agnostic. (context: Kimetsu Bedrock provider region configuration.)" + }, + { + "key": "aws-retry-throttling", + "text": "[tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with ±25% jitter. Do NOT retry `ValidationException` or `AccessDeniedException` — these are permanent errors. `ModelStreamErrorException` during streaming may be retryable. Log the `x-amzn-requestid` header from failed responses for AWS support debugging. (context: Kimetsu Bedrock provider retry logic.)" + }, + { + "key": "aws-presigned-urls", + "text": "[tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time — clock skew > 15 minutes causes `RequestTimeTooSkewed`. kimetsu could use presigned URLs to serve brain exports from S3 without exposing credentials to the client. (context: Kimetsu potential S3 export feature — presigned URL generation.)" + }, + { + "key": "aws-instance-metadata", + "text": "[tags: aws imds instance-metadata ec2 token] The AWS Instance Metadata Service v2 (IMDSv2) requires a session token: PUT `http://169.254.169.254/latest/api/token` with `X-aws-ec2-metadata-token-ttl-seconds: 21600` to get a token, then GET metadata with `X-aws-ec2-metadata-token: `. IMDSv1 (no token) is disabled on hardened instances. The metadata endpoint is only reachable from within EC2 — a connection timeout means you're not on EC2. Set a short connect timeout (200ms) when probing for the metadata service to avoid slow startup on non-EC2 hosts. (context: Kimetsu Bedrock provider — EC2 instance role credential fallback.)" + }, + { + "key": "ci-cache-keys", + "text": "[tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key — macOS and Windows have incompatible artifact formats. Separate the registry cache from the build cache: the registry (downloaded crates) changes rarely, the build cache changes every push. Bust the build cache on major dependency changes by adding a manual cache version suffix to the key. (context: Kimetsu CI — cache invalidation strategy.)" + }, + { + "key": "ci-matrix-explosion", + "text": "[tags: ci github-actions matrix jobs resources] A CI matrix combining OS (3) x Rust toolchain (3) x features (2) = 18 jobs. Each spawns a runner; at $0.008/min for Ubuntu and $0.016/min for Windows, a 10-minute build costs $2.40 per push. Reduce: test the full matrix only on PRs to main; on feature branches, test only Linux+stable. Use `fail-fast: false` to see all failures, not just the first. Combine related checks (clippy + test) in one job when they share build artifacts. For Windows-specific tests, run only the OS-specific job to reduce cost. (context: Kimetsu CI matrix cost optimization.)" + }, + { + "key": "ci-secrets-masking", + "text": "[tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output — but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable. Never reconstruct secrets from parts in step output. For kimetsu bench `--remote` CI runs, `KIMETSU_REMOTE_TOKEN` must be in the repository secrets, not in the workflow YAML. Use `${{ secrets.KIMETSU_REMOTE_TOKEN }}` in env — never `echo ${{ secrets.KIMETSU_REMOTE_TOKEN }}` in a run step. (context: Kimetsu CI remote benchmark — token handling.)" + }, + { + "key": "ci-artifact-retention", + "text": "[tags: ci github-actions artifacts retention benchmark] GitHub Actions artifacts are retained for 90 days (default). For benchmark results, use `actions/upload-artifact` with `retention-days: 365` for long-term tracking. The free tier has 500MB storage — per-combo JSON files from kimetsu bench (each ~60KB) add up fast if you upload them on every push. Upload only the summary.md. For regression detection, compare the current run's MRR against the artifact from the last green main build — fetch it with the `actions/download-artifact` action. (context: Kimetsu CI benchmark result tracking.)" + }, + { + "key": "ci-flaky-quarantine", + "text": "[tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal — a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output. NEVER let a flaky test gate the merge queue. For kimetsu timing-based tests (`test_gc_old_runs_deletes_ancient`), apply `#[cfg_attr(ci, ignore)]` and run only in a dedicated slow-CI job. (context: Kimetsu CI flaky test policy.)" + }, + { + "key": "kimetsu-daemon-lifecycle", + "text": "[tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required. The server PID is not stored anywhere; use `kimetsu doctor` to enumerate running MCP server processes via OS APIs. On Windows, the server binary may be locked by AV after first launch — `kimetsu update` must stop all running server processes before replacing the binary. (context: Kimetsu daemon lifecycle — process management for updates.)" + }, + { + "key": "kimetsu-capsule-budgets", + "text": "[tags: kimetsu capsule tokens budget retrieval] kimetsu retrieval enforces a token budget per capsule type: memory capsules are capped at 6000 tokens total (across all retrieved memories), file capsules at 3000 tokens. When a memory is large and would exceed the budget, it is truncated at a sentence boundary. The budget is enforced AFTER reranking — reranking may reorder results so that a truncated high-ranked memory displaces a full lower-ranked one. `noise_caps` in the bench output counts capsules that scored below the noise floor — they consume budget without contributing signal. Lower noise_caps = tighter retrieval. (context: Kimetsu capsule budget enforcement and noise floor interaction.)" + }, + { + "key": "kimetsu-memory-scopes", + "text": "[tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available — if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope. The `kimetsu_brain_record` MCP tool inherits the scope from the server's launch context. When running kimetsu-remote, all memories are project-scoped to the registered repo-id. (context: Kimetsu memory scope system — project vs user isolation.)" + }, + { + "key": "kimetsu-distiller-config", + "text": "[tags: kimetsu distiller harvest config provider] The kimetsu distiller (auto-harvester) uses a SEPARATE provider configuration from the main agent: `distiller.provider`, `distiller.model`, `distiller.api_key`. This allows running the agent on an expensive model (Claude Opus) while harvesting with a cheap model (Claude Haiku). If `distiller.provider` is not set, it inherits `provider`. The distiller runs as a background task triggered by the post-session hook; it reads the session transcript and emits `kimetsu_brain_record` calls. Distiller timeouts are longer (300s) than normal tool calls (60s) because transcript processing can be slow. (context: Kimetsu distiller provider configuration — agent vs harvester model separation.)" + }, + { + "key": "kimetsu-proactive-hooks", + "text": "[tags: kimetsu proactive hooks context injection] kimetsu's proactive context injection runs before each agent turn (pre-turn hook) and injects relevant memories into the system prompt prefix. The hook invocation adds latency to the first token: embedding inference + vector search + reranking + context formatting. On a cold start, this can be 1-3 seconds. The hook is optional — disable with `KIMETSU_PROACTIVE=0`. The semantic floor (min cosine similarity) filters noise capsules before injection; setting the floor too low injects irrelevant memories and wastes context window tokens. The proactive hook does NOT trigger the distiller — that runs post-session only. (context: Kimetsu proactive context injection — latency and floor tuning.)" + }, + { + "key": "kimetsu-write-tools-gate", + "text": "[tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level — disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients. The feature was introduced to prevent malicious prompts from poisoning the brain. (context: Kimetsu write-tools gate — config-driven security for remote deployments.)" + }, + { + "key": "kimetsu-query-stemming", + "text": "[tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression. The lexical floor (`min_lexical_coverage`) requires at least N stemmed query tokens to match in any retrieved document — this prevents high-semantic-score but lexically-unrelated documents from dominating. Stemming is applied only when the query has >= 3 tokens; short queries skip it. (context: Kimetsu retrieval — query-side stemming implementation.)" + }, + { + "key": "kimetsu-rerank-pool", + "text": "[tags: kimetsu reranker pool size ann retrieval] kimetsu's retrieval pipeline: ANN (approximate nearest neighbor) retrieves a pool of candidates, then the reranker reorders them, then the top-K are returned. The pool size (default 6 for production, 12 in bench) controls the recall-latency tradeoff: larger pool = higher recall = more reranker calls = more latency. For the jina-tiny reranker, pool 12 adds ~80ms vs pool 6. The bench uses pool 12 to maximize measurable recall differences between rerankers; production uses pool 6 for latency. Increasing pool size beyond 20 has diminishing recall returns on corpora < 1000 memories. (context: Kimetsu ANN pool size tuning for the retrieval benchmark.)" + }, + { + "key": "kimetsu-bench-remote-embedder-singleton", + "text": "[tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval. Workaround: run ONE `--embedders` value per invocation and kill the remote process between runs. The local bench path is not affected (each combo is process-isolated via `--single` child spawn). (context: Kimetsu brain bench --remote known issue — multi-embedder contamination.)" + }, + { + "key": "kimetsu-eval-fixture-shape", + "text": "[tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` — a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases). Keys must be unique across the dataset. The bench currently does not validate keys at load — it fails later with an `unwrap()` on a missing HashMap entry. (context: Kimetsu bench dataset shape and validation.)" + }, + { + "key": "kimetsu-mrr-metric", + "text": "[tags: kimetsu bench mrr recall metrics evaluation] kimetsu bench reports MRR (Mean Reciprocal Rank) and Recall@K. MRR is 1/rank_of_first_relevant_result, averaged across cases; it penalizes models that rank the correct answer 2nd or 3rd. Recall@K is the fraction of cases where at least one relevant answer appears in the top K. For multi-answer cases, recall@K considers a case satisfied if ANY relevant key appears in top K. MRR is the primary metric for knowledge retrieval because users read the first result first. A 0.01 MRR difference on a 100-case dataset corresponds to about 1 case changing from rank-2 to rank-1. Noise of ~2-3 cases is expected run-to-run. (context: Kimetsu benchmark metric interpretation.)" + } + ], + "queries": [ + { + "query": "test_env_lock inside with_user_brain_disabled deadlock", + "relevant": [ + "mutex-deadlock-user-brain-disabled" + ] + }, + { + "query": "why does my test hang after calling with_user_brain_disabled when I also lock test_env_lock?", + "relevant": [ + "mutex-deadlock-user-brain-disabled" + ] + }, + { + "query": "ingest_repo_at_root brain_root files_root kimetsu remote", + "relevant": [ + "remote-ingest-split-roots" + ] + }, + { + "query": "why does the remote server index the wrong directory when I run kimetsu brain ingest?", + "relevant": [ + "remote-ingest-split-roots" + ] + }, + { + "query": "kimetsu plugin install --remote mcp.json authorization bearer token", + "relevant": [ + "remote-mcp-host-wiring" + ] + }, + { + "query": "how do I wire a remote kimetsu brain into Claude Code without storing the token in the config file?", + "relevant": [ + "remote-mcp-host-wiring" + ] + }, + { + "query": "cargo feature unification kimetsu-brain embeddings fastembed test failure", + "relevant": [ + "cargo-feature-unification-embeddings" + ] + }, + { + "query": "my integration tests pass in isolation but break when I run cargo test --workspace — embedder changed?", + "relevant": [ + "cargo-feature-unification-embeddings" + ] + }, + { + "query": "build_anthropic_body bedrock-2023-05-31 InvokeModel blocking reqwest", + "relevant": [ + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ] + }, + { + "query": "how do I add AWS Bedrock as a model provider in Kimetsu without pulling in the aws-sdk?", + "relevant": [ + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ] + }, + { + "query": "BridgeTarget enum seams plugin_install_inner plugin_status_inner resolve_setup_hosts", + "relevant": [ + "bridge-target-enum-seams" + ] + }, + { + "query": "I added a new host to the bridge enum but cargo gives me compile errors in five different match arms — what did I miss?", + "relevant": [ + "bridge-target-enum-seams" + ] + }, + { + "query": "Pi extension factory defineExtension agent_end session_shutdown kimetsu.ts", + "relevant": [ + "pi-openclaw-extension-api" + ] + }, + { + "query": "how does Pi (earendil-works/pi) load plugins and what lifecycle hooks does it expose?", + "relevant": [ + "pi-openclaw-extension-api" + ] + }, + { + "query": "aws-sigv4 SigningParams apply_to_request_http1x reqwest sign-http", + "relevant": [ + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider" + ] + }, + { + "query": "how do I sign a Bedrock InvokeModel request with aws-sigv4 in blocking Rust?", + "relevant": [ + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider" + ] + }, + { + "query": "KIMETSU_RUNS_GC env opt-out TraceWriter create gc_old_runs caller", + "relevant": [ + "gc-trace-env-guard-placement" + ] + }, + { + "query": "where should I put the KIMETSU_RUNS_GC=0 guard — inside the GC function or at the call site?", + "relevant": [ + "gc-trace-env-guard-placement" + ] + }, + { + "query": "git_init_boundary ProjectPaths::discover temp dir user brain isolation", + "relevant": [ + "init-project-git-boundary" + ] + }, + { + "query": "my test calls init_project but it writes to the real ~/.kimetsu instead of the temp folder — why?", + "relevant": [ + "init-project-git-boundary" + ] + }, + { + "query": "clap command version KIMETSU_VERSION_DISPLAY cfg feature embeddings", + "relevant": [ + "clap-version-build-flavor" + ] + }, + { + "query": "how do I show the build flavor (lean vs embeddings) in the kimetsu --version output?", + "relevant": [ + "clap-version-build-flavor" + ] + }, + { + "query": "Harbor pyiceberg os.getcwd stale WSL2 DrvFs worker-result subprocess re-exec", + "relevant": [ + "harbor-terminal-bench-subprocess-isolation" + ] + }, + { + "query": "why does my kbench sweep crash after the first trial with 'result.json missing' on WSL2?", + "relevant": [ + "harbor-terminal-bench-subprocess-isolation" + ] + }, + { + "query": "rusqlite VACUUM transaction WAL checkpoint wal_checkpoint TRUNCATE", + "relevant": [ + "sqlite-vacuum-wal-checkpoint" + ] + }, + { + "query": "my SQLite VACUUM reports the file shrank but the disk usage stayed the same — Windows WAL?", + "relevant": [ + "sqlite-vacuum-wal-checkpoint" + ] + }, + { + "query": "add_memory import dedup seen_ids snapshot pre-existing active memory IDs", + "relevant": [ + "import-dedup-seen-ids" + ] + }, + { + "query": "brain import re-imports the same JSON file but the deduplication counter is wrong — why?", + "relevant": [ + "import-dedup-seen-ids" + ] + }, + { + "query": "toml::from_str Value parse document unexpected content str.parse", + "relevant": [ + "toml-value-parse" + ] + }, + { + "query": "how do I parse a TOML configuration file into a toml::Value in toml 0.9?", + "relevant": [ + "toml-value-parse" + ] + }, + { + "query": "CIM CreationDate DMTF WMI ps etimes started_at assess_mcp_skew", + "relevant": [ + "process-start-time-cross-platform" + ] + }, + { + "query": "how do I read a process start time on both Windows and Linux in pure Rust?", + "relevant": [ + "process-start-time-cross-platform" + ] + }, + { + "query": "processes_locking_target decide_preflight_action BufRead Write update.rs", + "relevant": [ + "windows-update-process-locking" + ] + }, + { + "query": "how should I reuse the existing process enumerator in the update preflight check to avoid a second PowerShell query?", + "relevant": [ + "windows-update-process-locking" + ] + }, + { + "query": "cfg_attr windows allow dead_code parse_unix_ps cross-platform tests", + "relevant": [ + "cfg-cross-platform-dead-code" + ] + }, + { + "query": "how do I keep a function that is only called on Unix from triggering dead_code warnings on Windows?", + "relevant": [ + "cfg-cross-platform-dead-code" + ] + }, + { + "query": "deadlocking a Rust mutex in integration tests", + "relevant": [ + "mutex-deadlock-user-brain-disabled" + ] + }, + { + "query": "benchmarking retrieval quality across embedders", + "relevant": [] + }, + { + "query": "process memory working set RSS peak measurement Windows", + "relevant": [ + "process-start-time-cross-platform", + "windows-update-process-locking" + ] + }, + { + "query": "cloning a git repository server-side into a managed checkout", + "relevant": [ + "remote-ingest-split-roots" + ] + }, + { + "query": "SigV4 signing HTTP requests in Rust", + "relevant": [ + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider" + ] + }, + { + "query": "cargo test --workspace feature flag changes broke my unit tests", + "relevant": [ + "cargo-feature-unification-embeddings" + ] + }, + { + "query": "how do I make pasta carbonara?", + "relevant": [] + }, + { + "query": "what is the offside rule in football?", + "relevant": [] + }, + { + "query": "best way to train for a half marathon", + "relevant": [] + }, + { + "query": "my test passes when I run it alone but fails under cargo test --workspace", + "relevant": [ + "cargo-feature-unification-embeddings" + ] + }, + { + "query": "all the project tests started hanging forever after I added my new test", + "relevant": [ + "mutex-deadlock-user-brain-disabled" + ] + }, + { + "query": "my integration test silently wrote memories into my real home brain instead of the temp workspace", + "relevant": [ + "init-project-git-boundary" + ] + }, + { + "query": "where should the env-var opt-out check live for a cleanup feature triggered from a hot code path", + "relevant": [ + "gc-trace-env-guard-placement" + ] + }, + { + "query": "the brain database file stays huge on Windows even after deleting most rows", + "relevant": [ + "sqlite-vacuum-wal-checkpoint" + ] + }, + { + "query": "re-importing the same exported memories file counts them as new instead of deduplicated", + "relevant": [ + "import-dedup-seen-ids" + ] + }, + { + "query": "a helper function only called on Unix at runtime fails the dead-code lint on the Windows build", + "relevant": [ + "cfg-cross-platform-dead-code" + ] + }, + { + "query": "the second Terminal-Bench trial always crashes even though the first one passes", + "relevant": [ + "harbor-terminal-bench-subprocess-isolation" + ] + }, + { + "query": "how does doctor tell a running MCP server process is older than the kimetsu binary on disk", + "relevant": [ + "process-start-time-cross-platform" + ] + }, + { + "query": "the self-update preflight needs the list of running kimetsu processes without re-running the OS query", + "relevant": [ + "windows-update-process-locking" + ] + }, + { + "query": "parsing the WMI DMTF CreationDate timestamp into epoch seconds without extra crates", + "relevant": [ + "process-start-time-cross-platform" + ] + }, + { + "query": "calling Bedrock InvokeModel from blocking reqwest without the aws sdk", + "relevant": [ + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider" + ] + }, + { + "query": "how do I rotate the encryption key protecting the kimetsu brain database", + "relevant": [] + }, + { + "query": "which tokio runtime worker-thread settings does the kimetsu MCP server use", + "relevant": [] + }, + { + "query": "how does kimetsu sync memories between two machines over the network", + "relevant": [] + }, + { + "query": "recovering a corrupted usearch ANN index after a power loss", + "relevant": [] + }, + { + "query": "what postgres schema should I use to store kimetsu memories", + "relevant": [] + }, + { + "query": "the whole CI job just froze forever with no failure output after my latest test PR", + "relevant": [ + "mutex-deadlock-user-brain-disabled" + ] + }, + { + "query": "running the test suite left junk state in my home directory", + "relevant": [ + "init-project-git-boundary" + ] + }, + { + "query": "I deleted a bunch of old rows but the file on disk is still the same size", + "relevant": [ + "sqlite-vacuum-wal-checkpoint" + ] + }, + { + "query": "adding one new crate quietly changed how the whole workspace builds", + "relevant": [ + "cargo-feature-unification-embeddings" + ] + }, + { + "query": "we cannot pull an async runtime into the agent just to talk to AWS", + "relevant": [ + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider" + ] + }, + { + "query": "users should be able to tell which build variant they installed from the version output", + "relevant": [ + "clap-version-build-flavor" + ] + }, + { + "query": "what gotchas should I expect writing process-inspection code that works on both Windows and Unix?", + "relevant": [ + "process-start-time-cross-platform", + "cfg-cross-platform-dead-code", + "windows-update-process-locking" + ] + }, + { + "query": "why might tests behave differently on my machine than in the full CI run?", + "relevant": [ + "cargo-feature-unification-embeddings", + "mutex-deadlock-user-brain-disabled", + "init-project-git-boundary" + ] + }, + { + "query": "what do I need to know before wiring kimetsu into a brand new host agent?", + "relevant": [ + "bridge-target-enum-seams", + "pi-openclaw-extension-api", + "remote-mcp-host-wiring" + ] + }, + { + "query": "tell me everything relevant to running kimetsu against AWS", + "relevant": [ + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ] + }, + { + "query": "ingesting a cloned repo when the brain lives under a different root", + "relevant": [ + "remote-ingest-split-roots" + ] + }, + { + "query": "streamable-http transport entry for openclaw.json with a bearer token", + "relevant": [ + "remote-mcp-host-wiring" + ] + }, + { + "query": "serializing ingests with a tokio mutex to avoid checkout races", + "relevant": [ + "remote-ingest-split-roots" + ] + }, + { + "query": "percent-encoding the colon in the bedrock model id for the invoke URL", + "relevant": [ + "bedrock-kimetsu-provider" + ] + }, + { + "query": "deduplicating re-imported memories against pre-existing ids", + "relevant": [ + "import-dedup-seen-ids" + ] + }, + { + "query": "parsing DMTF datetimes", + "relevant": [ + "process-start-time-cross-platform" + ] + }, + { + "query": "how should install derive a stable identifier from the git remote URL?", + "relevant": [ + "remote-mcp-host-wiring" + ] + }, + { + "query": "the secret token must not end up written into the host config file", + "relevant": [ + "remote-mcp-host-wiring" + ] + }, + { + "query": "keep the cleanup logic unit-testable without touching environment variables", + "relevant": [ + "gc-trace-env-guard-placement" + ] + }, + { + "query": "how do we stop the server from cloning arbitrary repos clients request?", + "relevant": [ + "remote-ingest-split-roots" + ] + }, + { + "query": "make sure a wrong guess about a host plugin API never breaks that host", + "relevant": [ + "pi-openclaw-extension-api" + ] + }, + { + "query": "which wire-format trick lets us reuse the existing Anthropic request builder for AWS?", + "relevant": [ + "bedrock-kimetsu-provider" + ] + }, + { + "query": "the self-update froze because something was still holding the executable", + "relevant": [ + "windows-update-process-locking" + ] + }, + { + "query": "our notes about the extension API turned out wrong once we read the actual repo", + "relevant": [ + "pi-openclaw-extension-api" + ] + }, + { + "query": "half the benchmark trials die right after the first one finishes", + "relevant": [ + "harbor-terminal-bench-subprocess-isolation" + ] + }, + { + "query": "I need this parser visible to tests on every OS even though only one OS calls it", + "relevant": [ + "cfg-cross-platform-dead-code" + ] + }, + { + "query": "the config file content refuses to parse even though the TOML looks valid", + "relevant": [ + "toml-value-parse" + ] + }, + { + "query": "the remote server must refresh its checkout before answering file queries", + "relevant": [ + "remote-ingest-split-roots" + ] + }, + { + "query": "tests must not climb to a parent git repository when resolving project paths", + "relevant": [ + "init-project-git-boundary" + ] + }, + { + "query": "how do I test request signing deterministically when timestamps change every run?", + "relevant": [ + "bedrock-kimetsu-provider" + ] + }, + { + "query": "adding a new variant to the host target enum - which places will I forget to update?", + "relevant": [ + "bridge-target-enum-seams" + ] + }, + { + "query": "how do I enable GPU acceleration for kimetsu embedding inference", + "relevant": [] + }, + { + "query": "how do I throttle kimetsu API spend per month", + "relevant": [] + }, + { + "query": "can the kimetsu brain database be stored in S3 instead of on disk", + "relevant": [] + }, + { + "query": "how do I plug a custom tokenizer into the FTS index", + "relevant": [] + }, + { + "query": "what should I check when kimetsu behaves differently on Windows than on Linux?", + "relevant": [ + "process-start-time-cross-platform", + "cfg-cross-platform-dead-code", + "sqlite-vacuum-wal-checkpoint", + "windows-update-process-locking" + ] + }, + { + "query": "what are the moving parts of the kimetsu remote deployment story?", + "relevant": [ + "remote-ingest-split-roots", + "remote-mcp-host-wiring" + ] + }, + { + "query": "which lessons cover guarding behavior behind environment variables?", + "relevant": [ + "gc-trace-env-guard-placement", + "mutex-deadlock-user-brain-disabled" + ] + }, + { + "query": "SQLite BUSY error under concurrent writes", + "relevant": [ + "sqlite-busy-timeout-wal", + "sqlite-vacuum-wal-checkpoint" + ] + }, + { + "query": "SQLite WAL mode breaks when the database is on a network share", + "relevant": [ + "sqlite-wal-network-drive" + ] + }, + { + "query": "my SQLite WAL database causes SQLITE_IOERR_LOCK on a mapped drive", + "relevant": [ + "sqlite-wal-network-drive" + ] + }, + { + "query": "FTS5 tokenizer configuration for Rust identifiers with underscores", + "relevant": [ + "sqlite-fts5-tokenizer" + ] + }, + { + "query": "I switched the FTS5 tokenizer but search stopped returning results", + "relevant": [ + "sqlite-fts5-tokenizer" + ] + }, + { + "query": "optimal SQLite page size for storing embedding vectors", + "relevant": [ + "sqlite-page-size" + ] + }, + { + "query": "ON DELETE CASCADE in SQLite does nothing — foreign keys not enforced", + "relevant": [ + "sqlite-foreign-keys-default-off" + ] + }, + { + "query": "indexing a JSON metadata column in SQLite without a schema migration", + "relevant": [ + "sqlite-json1-extract" + ] + }, + { + "query": "prepare() vs prepare_cached() in rusqlite hot insert loop", + "relevant": [ + "sqlite-prepared-stmt-cache" + ] + }, + { + "query": "speed up bulk memory ingest by caching SQL statements", + "relevant": [ + "sqlite-prepared-stmt-cache" + ] + }, + { + "query": "partial index on deleted_at IS NULL for faster active memory queries", + "relevant": [ + "sqlite-partial-index" + ] + }, + { + "query": "the brain query is slow because it scans all rows including soft-deleted ones", + "relevant": [ + "sqlite-partial-index" + ] + }, + { + "query": "Cargo.lock changed unexpectedly after adding a new workspace crate", + "relevant": [ + "cargo-lockfile-drift" + ] + }, + { + "query": "how do I prevent CI from accepting a modified lockfile silently?", + "relevant": [ + "cargo-lockfile-drift" + ] + }, + { + "query": "build.rs reruns on every incremental build even when nothing changed", + "relevant": [ + "cargo-build-script-rerun" + ] + }, + { + "query": "incremental cargo build is slow because build script runs every time", + "relevant": [ + "cargo-build-script-rerun" + ] + }, + { + "query": "a dev-dependency is activating an embeddings feature in my production build", + "relevant": [ + "cargo-dev-dep-leak" + ] + }, + { + "query": "how do I prevent a test-only feature from bleeding into the non-test compilation?", + "relevant": [ + "cargo-dev-dep-leak" + ] + }, + { + "query": "linker errors in target/ caused by antivirus holding the exe file", + "relevant": [ + "cargo-target-dir-sharing", + "windows-file-locking-av" + ] + }, + { + "query": "Access is denied (os error 5) when linking on Windows — how do I fix this?", + "relevant": [ + "windows-file-locking-av" + ] + }, + { + "query": "incremental build broke with a type mismatch after switching branches", + "relevant": [ + "cargo-incremental-cache-corruption" + ] + }, + { + "query": "cargo reports a type error that references a type not in the codebase", + "relevant": [ + "cargo-incremental-cache-corruption" + ] + }, + { + "query": "compile fastembed at O2 in debug builds to avoid slow embedding inference", + "relevant": [ + "cargo-profile-override" + ] + }, + { + "query": "override compilation profile for a single crate in a Cargo workspace", + "relevant": [ + "cargo-profile-override" + ] + }, + { + "query": "[patch.crates-io] workspace dependency override", + "relevant": [ + "cargo-patch-section" + ] + }, + { + "query": "pin minimum supported Rust version in Cargo.toml", + "relevant": [ + "cargo-msrv" + ] + }, + { + "query": "Windows path over 260 characters causes OS error 3 during Cargo build", + "relevant": [ + "windows-long-paths" + ] + }, + { + "query": "how do I enable long file paths for Cargo on Windows?", + "relevant": [ + "windows-long-paths" + ] + }, + { + "query": "intermittent sharing violation errors when Rust linker writes the exe on Windows", + "relevant": [ + "windows-file-locking-av" + ] + }, + { + "query": "Rust walkdir follows junctions differently from symlinks on Windows", + "relevant": [ + "windows-junctions-vs-symlinks" + ] + }, + { + "query": "UNC path canonicalize returns verbatim prefix — how do I strip it?", + "relevant": [ + "windows-unc-paths" + ] + }, + { + "query": "UTF-8 memory text prints as mojibake in the Windows console", + "relevant": [ + "windows-console-encoding" + ] + }, + { + "query": "process exit code is 4294967295 instead of -1 on Windows", + "relevant": [ + "windows-exit-codes" + ] + }, + { + "query": "tokenizer.json must match the ONNX model — what breaks if it doesn't?", + "relevant": [ + "onnx-tokenizer-mismatch" + ] + }, + { + "query": "embedding quality degraded after I swapped in the INT8 quantized model", + "relevant": [ + "onnx-quantization-drift" + ] + }, + { + "query": "missing attention mask causes low-norm embeddings in batch inference", + "relevant": [ + "onnx-batch-padding" + ] + }, + { + "query": "ONNX model download fails in a Docker container with no home directory", + "relevant": [ + "onnx-model-cache-paths" + ] + }, + { + "query": "fastembed cache path environment variable for CI", + "relevant": [ + "onnx-model-cache-paths" + ] + }, + { + "query": "cosine similarity vs dot product for L2-normalized embedding vectors", + "relevant": [ + "onnx-cosine-vs-dot" + ] + }, + { + "query": "stored vectors have wrong dimension after switching embedding models", + "relevant": [ + "onnx-dim-mismatch" + ] + }, + { + "query": "E5 and Instructor models need a query prefix — what happens without it?", + "relevant": [ + "onnx-prefix-instructions" + ] + }, + { + "query": "ORT thread pool contention when running multiple bench processes in parallel", + "relevant": [ + "onnx-ort-threading" + ] + }, + { + "query": "git worktrees share the .kimetsu brain — how do I isolate test runs?", + "relevant": [ + "git-worktree-brain-isolation" + ] + }, + { + "query": "when is it safe to use --no-verify on git commit?", + "relevant": [ + "git-hooks-bypass" + ] + }, + { + "query": "reduce clone size and bandwidth for server-side repo ingest", + "relevant": [ + "git-sparse-checkout" + ] + }, + { + "query": "spurious diffs from Windows CRLF line ending conversion in git", + "relevant": [ + "git-line-endings-windows" + ] + }, + { + "query": "git submodule always gets the wrong commit in CI", + "relevant": [ + "git-submodule-pinning" + ] + }, + { + "query": "accidentally ran git reset --hard and lost commits — can I recover?", + "relevant": [ + "git-reflog-rescue" + ] + }, + { + "query": "blocking SQLite call from an async tokio handler causes latency spikes", + "relevant": [ + "tokio-blocking-in-async" + ] + }, + { + "query": "Cannot start a runtime from within a runtime in a tokio test", + "relevant": [ + "tokio-runtime-in-tests" + ] + }, + { + "query": "tokio select cancels the other branch and loses the value in the channel", + "relevant": [ + "tokio-select-cancellation" + ] + }, + { + "query": "mpsc channel backpressure causing senders to stall", + "relevant": [ + "tokio-channel-backpressure" + ] + }, + { + "query": "overhead from calling spawn_blocking on every single query request", + "relevant": [ + "tokio-spawn-blocking" + ] + }, + { + "query": "axum server panics during shutdown because the DB pool is already closed", + "relevant": [ + "tokio-shutdown-ordering" + ] + }, + { + "query": "reqwest Client created per-request defeats connection pooling", + "relevant": [ + "http-connection-pooling" + ] + }, + { + "query": "LLM request times out during streaming — which timeout setting applies?", + "relevant": [ + "http-timeout-layering" + ] + }, + { + "query": "how do I safely retry a POST to the LLM API without creating duplicates?", + "relevant": [ + "http-retry-idempotency" + ] + }, + { + "query": "custom enterprise root CA not trusted by rustls on Windows", + "relevant": [ + "http-tls-roots" + ] + }, + { + "query": "parsing server-sent events when a single TCP chunk contains a partial SSE frame", + "relevant": [ + "http-streaming-bodies" + ] + }, + { + "query": "reqwest does not use the system proxy settings on Windows", + "relevant": [ + "http-proxy-env" + ] + }, + { + "query": "insta snapshot tests fail in CI because output includes a timestamp", + "relevant": [ + "testing-snapshot-churn" + ] + }, + { + "query": "two test workers writing to the same temp directory path race each other", + "relevant": [ + "testing-temp-dirs-ci" + ] + }, + { + "query": "test passes locally but fails on a slow CI runner due to a 100ms sleep", + "relevant": [ + "testing-time-dependent-flakes" + ] + }, + { + "query": "proptest found a hash collision in text normalization that example tests missed", + "relevant": [ + "testing-property-tests" + ] + }, + { + "query": "set_var in tests races when cargo test runs them in parallel", + "relevant": [ + "testing-serial-vs-parallel" + ] + }, + { + "query": "hardcoded JSON fixtures broke after a schema migration", + "relevant": [ + "testing-fixture-drift" + ] + }, + { + "query": "debug print in the MCP handler corrupts the JSON-Lines protocol stream", + "relevant": [ + "mcp-stdout-protocol" + ] + }, + { + "query": "kimetsu MCP tool call times out because embedding model is re-initialized every call", + "relevant": [ + "mcp-tool-timeouts" + ] + }, + { + "query": "env var set after host launch is not visible to the MCP server process", + "relevant": [ + "mcp-env-propagation" + ] + }, + { + "query": "MCP tool call fails because a required field is missing from the JSON input", + "relevant": [ + "mcp-schema-validation" + ] + }, + { + "query": "Claude Code rejects the tool name with a hyphen in it", + "relevant": [ + "mcp-tool-naming" + ] + }, + { + "query": "MCP response path uses backslashes and the host rejects it", + "relevant": [ + "mcp-transcript-paths" + ] + }, + { + "query": "AWS credentials not found — which env var does kimetsu read for Bedrock?", + "relevant": [ + "aws-credentials-chain" + ] + }, + { + "query": "Bedrock InvokeModel fails because the region is not configured", + "relevant": [ + "aws-region-resolution" + ] + }, + { + "query": "how do I handle ThrottlingException from Bedrock with exponential backoff?", + "relevant": [ + "aws-retry-throttling" + ] + }, + { + "query": "generating a presigned S3 URL for brain export without exposing credentials", + "relevant": [ + "aws-presigned-urls" + ] + }, + { + "query": "IMDSv2 token required for instance metadata — PUT before GET", + "relevant": [ + "aws-instance-metadata" + ] + }, + { + "query": "Cargo cache key strategy for GitHub Actions to avoid toolchain version collisions", + "relevant": [ + "ci-cache-keys" + ] + }, + { + "query": "CI matrix has 18 jobs and costs too much — how do I reduce it?", + "relevant": [ + "ci-matrix-explosion" + ] + }, + { + "query": "GitHub Actions secret accidentally printed in build logs", + "relevant": [ + "ci-secrets-masking" + ] + }, + { + "query": "how long do GitHub Actions artifacts persist and what's the storage limit?", + "relevant": [ + "ci-artifact-retention" + ] + }, + { + "query": "timing-based test flake in CI — quarantine or fix?", + "relevant": [ + "ci-flaky-quarantine" + ] + }, + { + "query": "kimetsu doctor says the MCP server is running — how do I stop it before an update?", + "relevant": [ + "kimetsu-daemon-lifecycle" + ] + }, + { + "query": "noise capsules consuming token budget without contributing retrieval signal", + "relevant": [ + "kimetsu-capsule-budgets" + ] + }, + { + "query": "kimetsu_brain_record writes to the wrong brain location — user vs project scope", + "relevant": [ + "kimetsu-memory-scopes" + ] + }, + { + "query": "how do I configure kimetsu to use Claude Haiku for harvesting but Opus for the agent?", + "relevant": [ + "kimetsu-distiller-config" + ] + }, + { + "query": "first agent turn is slow because kimetsu proactive hook runs embedding inference", + "relevant": [ + "kimetsu-proactive-hooks" + ] + }, + { + "query": "make the kimetsu brain read-only for certain repos on a shared remote server", + "relevant": [ + "kimetsu-write-tools-gate" + ] + }, + { + "query": "kimetsu FTS search misses 'deadlocking' when memory says 'deadlock'", + "relevant": [ + "kimetsu-query-stemming" + ] + }, + { + "query": "how does pool size affect retrieval recall and latency in the bench?", + "relevant": [ + "kimetsu-rerank-pool" + ] + }, + { + "query": "second embedder in a remote bench run gets worse results than the first", + "relevant": [ + "kimetsu-bench-remote-embedder-singleton" + ] + }, + { + "query": "what is the expected JSON schema for kimetsu brain bench dataset files?", + "relevant": [ + "kimetsu-eval-fixture-shape" + ] + }, + { + "query": "what does MRR mean and how do I interpret a 0.01 difference between combos?", + "relevant": [ + "kimetsu-mrr-metric" + ] + }, + { + "query": "SQLITE_BUSY keeps appearing even with WAL mode enabled", + "relevant": [ + "sqlite-busy-timeout-wal" + ] + }, + { + "query": "my brain file got huge again right after I compacted it", + "relevant": [ + "sqlite-vacuum-wal-checkpoint", + "sqlite-page-size" + ] + }, + { + "query": "all my FTS queries stopped returning results after I changed the tokenizer config", + "relevant": [ + "sqlite-fts5-tokenizer" + ] + }, + { + "query": "something is preventing the kimetsu binary from being replaced during update", + "relevant": [ + "kimetsu-daemon-lifecycle", + "windows-file-locking-av", + "windows-update-process-locking" + ] + }, + { + "query": "tool call results not appearing in the context — is the semantic floor too high?", + "relevant": [ + "kimetsu-proactive-hooks", + "kimetsu-rerank-pool" + ] + }, + { + "query": "CARGO_INCREMENTAL=0 in CI prevents a class of spurious compilation errors", + "relevant": [ + "cargo-incremental-cache-corruption" + ] + }, + { + "query": "how do I check whether my Cargo workspace respects the MSRV constraint?", + "relevant": [ + "cargo-msrv" + ] + }, + { + "query": "rusqlite connection opened but ON DELETE CASCADE cascade never fires", + "relevant": [ + "sqlite-foreign-keys-default-off" + ] + }, + { + "query": "I cannot connect to kimetsu-remote — something about TLS cert validation failed", + "relevant": [ + "http-tls-roots" + ] + }, + { + "query": "graceful shutdown fails because in-flight SQLite queries are still running when pool closes", + "relevant": [ + "tokio-shutdown-ordering", + "tokio-blocking-in-async" + ] + }, + { + "query": "kimetsu-remote response takes 8 seconds — which stage is slow?", + "relevant": [ + "mcp-tool-timeouts", + "kimetsu-proactive-hooks" + ] + }, + { + "query": "git reflog to rescue accidentally deleted branch", + "relevant": [ + "git-reflog-rescue" + ] + }, + { + "query": "git submodule --remote advances the pinned SHA unexpectedly", + "relevant": [ + "git-submodule-pinning" + ] + }, + { + "query": "axum SSE streaming drops the last event when client disconnects", + "relevant": [ + "http-streaming-bodies", + "tokio-select-cancellation" + ] + }, + { + "query": "how do I detect that I am running inside a git worktree vs the main checkout?", + "relevant": [ + "git-worktree-brain-isolation" + ] + }, + { + "query": "ONNX Runtime intra-op threads causing CPU contention during parallel bench", + "relevant": [ + "onnx-ort-threading" + ] + }, + { + "query": "what is the right way to supply AWS session token alongside access key and secret?", + "relevant": [ + "aws-credentials-chain", + "aws-sigv4-bedrock-blocking" + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-retrieval/development-scores.json b/docs/audits/2026-09-07-retrieval/development-scores.json new file mode 100644 index 0000000..4623c01 --- /dev/null +++ b/docs/audits/2026-09-07-retrieval/development-scores.json @@ -0,0 +1,12804 @@ +[ + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "test_env_lock inside with_user_brain_disabled deadlock", + "relevant": [ + "mutex-deadlock-user-brain-disabled" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999797344207764, + "key": "mutex-deadlock-user-brain-disabled", + "rank_score": 0.9549996256828308, + "text": "project:fact - [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure — `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation. (context: New tests for Tier-1 perf work called test_env_lock().lock() inside with_user_brain_disabled closure, deadlocking all project::tests that ran after them in the same test binary.)" + }, + { + "ce": 0.0012594320578500628, + "key": "kimetsu-query-stemming", + "rank_score": 0.434656023979187, + "text": "project:fact - [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression. The lexical floor (`min_lexical_coverage`) requires at least N stemmed query tokens to match in any retrieved document — this prevents high-semantic-score but lexically-unrelated documents from dominating. Stemming is applied only when the query has >= 3 tokens; short queries skip it. (context: Kimetsu retrieval — query-side stemming implementation.)" + }, + { + "ce": 0.05156223475933075, + "key": "testing-serial-vs-parallel", + "rank_score": 0.4325084090232849, + "text": "project:fact - [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`). `cargo nextest` runs each test in a separate process by default, avoiding the problem entirely at the cost of longer startup time. For kimetsu, prefer nextest in CI and accept that `test_env_lock` exists only for `cargo test` compatibility. (context: Kimetsu test suite — env-var mutation in parallel tests.)" + }, + { + "ce": 0.00007744869071757421, + "key": "git-submodule-pinning", + "rank_score": 0.35421955585479736, + "text": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip — this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version. If a submodule is the kimetsu-bench repo inside the main repo, pin the bench SHA after validating the dataset change. Use `git diff HEAD -- bench` to see the pinned SHA change before committing. (context: Kimetsu bench as a git submodule of the main repo.)" + }, + { + "ce": 0.019477659836411476, + "key": "tokio-runtime-in-tests", + "rank_score": 0.3812733292579651, + "text": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests. For sync test code that calls async, use `tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async { ... })`. Never call `block_on` from inside an async function. (context: Kimetsu remote integration tests — nested runtime panic.)" + }, + { + "ce": 0.12273282557725906, + "key": "gc-trace-env-guard-placement", + "rank_score": 0.3611459732055664, + "text": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site. (context: QQ4 — runs auto-GC on run creation. Env guard placement decision when wiring opportunistic GC into TraceWriter::create.)" + } + ], + "delivered": [ + "mutex-deadlock-user-brain-disabled" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8449746370315552 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "why does my test hang after calling with_user_brain_disabled when I also lock test_env_lock?", + "relevant": [ + "mutex-deadlock-user-brain-disabled" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9994394183158875, + "key": "mutex-deadlock-user-brain-disabled", + "rank_score": 0.9549996256828308, + "text": "project:fact - [2026-09-05] [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure — `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation. (context: New tests for Tier-1 perf work called test_env_lock().lock() inside with_user_brain_disabled closure, deadlocking all project::tests that ran after them in the same test binary.)" + }, + { + "ce": 0.0025282741989940405, + "key": "windows-update-process-locking", + "rank_score": 0.4526326060295105, + "text": "project:fact - [2026-09-05] [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics — mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code. (context: Q2 — kimetsu update preflight for locked binary on Windows)" + }, + { + "ce": 0.00003514017953420989, + "key": "cargo-patch-section", + "rank_score": 0.4690268337726593, + "text": "project:fact - [2026-09-05] [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace — including transitive deps — that depend on `my-crate`. Remove the patch before publishing. Using `[replace]` is deprecated since Cargo 0.47; always use `[patch]`. When patching a crate pinned via an exact version specifier, the patch must satisfy that exact version. Use `cargo tree` to confirm the patch is applied. (context: Kimetsu patching upstream rusqlite for a Windows-specific WAL fix.)" + }, + { + "ce": 0.00017368352564517409, + "key": "tokio-blocking-in-async", + "rank_score": 0.4244077801704407, + "text": "project:fact - [2026-09-05] [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking — never call rusqlite directly from an async fn without spawn_blocking. fastembed inference is also blocking (ONNX Runtime is synchronous). The threshold: any operation taking more than 100 microseconds that can't be made async belongs in spawn_blocking. Ignoring this causes tail-latency spikes and request timeouts under load in kimetsu-remote. (context: Kimetsu remote server — SQLite and embedding calls from async handlers.)" + }, + { + "ce": 0.0278056301176548, + "key": "testing-serial-vs-parallel", + "rank_score": 0.5881108045578003, + "text": "project:fact - [2026-09-05] [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`). `cargo nextest` runs each test in a separate process by default, avoiding the problem entirely at the cost of longer startup time. For kimetsu, prefer nextest in CI and accept that `test_env_lock` exists only for `cargo test` compatibility. (context: Kimetsu test suite — env-var mutation in parallel tests.)" + }, + { + "ce": 0.00011049437307519838, + "key": "ci-secrets-masking", + "rank_score": 0.48994845151901245, + "text": "project:fact - [2026-09-05] [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output — but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable. Never reconstruct secrets from parts in step output. For kimetsu bench `--remote` CI runs, `KIMETSU_REMOTE_TOKEN` must be in the repository secrets, not in the workflow YAML. Use `${{ secrets.KIMETSU_REMOTE_TOKEN }}` in env — never `echo ${{ secrets.KIMETSU_REMOTE_TOKEN }}` in a run step. (context: Kimetsu CI remote benchmark — token handling.)" + } + ], + "delivered": [ + "mutex-deadlock-user-brain-disabled" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8332277536392212 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "ingest_repo_at_root brain_root files_root kimetsu remote", + "relevant": [ + "remote-ingest-split-roots" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999620914459229, + "key": "remote-ingest-split-roots", + "rank_score": 0.954999566078186, + "text": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races. Re-enable kimetsu_brain_ingest_repo in the tool allowlist only when ingest is configured, and INTERCEPT that tools/call in the remote handler (clone+ingest_repo_at_root) before the normal dispatch (which would walk the wrong dir). Hermetic test: git init a temp repo, register url=local path, ingest, then context retrieves the file capsule via FTS (noop embedder). (context: R3c: server-side ingest for kimetsu-remote — cloning repos so file-capsule retrieval works without a local checkout.)" + }, + { + "ce": 0.07324008643627167, + "key": "ci-secrets-masking", + "rank_score": 0.386308491230011, + "text": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output — but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable. Never reconstruct secrets from parts in step output. For kimetsu bench `--remote` CI runs, `KIMETSU_REMOTE_TOKEN` must be in the repository secrets, not in the workflow YAML. Use `${{ secrets.KIMETSU_REMOTE_TOKEN }}` in env — never `echo ${{ secrets.KIMETSU_REMOTE_TOKEN }}` in a run step. (context: Kimetsu CI remote benchmark — token handling.)" + }, + { + "ce": 0.8598380088806152, + "key": "remote-mcp-host-wiring", + "rank_score": 0.39448752999305725, + "text": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal. Derive a stable repo id from the git remote: strip `.git`, scheme (`://`), and `user@`, then map non-alphanumerics to '-' and collapse — so both https://github.com/org/repo.git and git@github.com:org/repo.git -> `github-com-org-repo`. Remote install writes ONLY the MCP entry + instructions (no local hooks — the brain is on the server). Codex/Pi don't get --remote (no remote-MCP / no MCP). (context: R2: implementing `kimetsu plugin install --remote` to wire a host at a kimetsu-remote HTTP MCP server.)" + }, + { + "ce": 0.6798340678215027, + "key": "kimetsu-bench-remote-embedder-singleton", + "rank_score": 0.38900500535964966, + "text": "project:fact - [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval. Workaround: run ONE `--embedders` value per invocation and kill the remote process between runs. The local bench path is not affected (each combo is process-isolated via `--single` child spawn). (context: Kimetsu brain bench --remote known issue — multi-embedder contamination.)" + }, + { + "ce": 0.9088719487190247, + "key": "kimetsu-write-tools-gate", + "rank_score": 0.37572383880615234, + "text": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level — disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients. The feature was introduced to prevent malicious prompts from poisoning the brain. (context: Kimetsu write-tools gate — config-driven security for remote deployments.)" + }, + { + "ce": 0.026341591030359268, + "key": "kimetsu-daemon-lifecycle", + "rank_score": 0.36410775780677795, + "text": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required. The server PID is not stored anywhere; use `kimetsu doctor` to enumerate running MCP server processes via OS APIs. On Windows, the server binary may be locked by AV after first launch — `kimetsu update` must stop all running server processes before replacing the binary. (context: Kimetsu daemon lifecycle — process management for updates.)" + } + ], + "delivered": [ + "remote-ingest-split-roots", + "kimetsu-write-tools-gate", + "remote-mcp-host-wiring", + "kimetsu-bench-remote-embedder-singleton" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8406606316566467 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "why does the remote server index the wrong directory when I run kimetsu brain ingest?", + "relevant": [ + "remote-ingest-split-roots" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9677385091781616, + "key": "remote-ingest-split-roots", + "rank_score": 0.8641229867935181, + "text": "project:fact - [2026-09-05] [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races. Re-enable kimetsu_brain_ingest_repo in the tool allowlist only when ingest is configured, and INTERCEPT that tools/call in the remote handler (clone+ingest_repo_at_root) before the normal dispatch (which would walk the wrong dir). Hermetic test: git init a temp repo, register url=local path, ingest, then context retrieves the file capsule via FTS (noop embedder). (context: R3c: server-side ingest for kimetsu-remote — cloning repos so file-capsule retrieval works without a local checkout.)" + }, + { + "ce": 0.05468548834323883, + "key": "pi-openclaw-extension-api", + "rank_score": 0.7571325302124023, + "text": "project:fact - [2026-09-05] [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`. External commands use `pi.exec()` but `node:child_process` spawn also works. Pi has NO MCP so Kimetsu integrates via TS extension + SKILL.md only. (context: Implementing Pi host target for Kimetsu plugin install/status/uninstall system.)\n\nAlso: [tags: kimetsu host-integration pi openclaw bridge] When integrating Kimetsu with an external host agent (Pi, OpenClaw, etc.), VERIFY the host's real plugin/extension API against its actual repo before writing embedded assets — docs-from-memory are frequently wrong. Concretely corrected during v1.0: Pi uses a default-export factory `export default function(pi)` (not `defineExtension`) with lifecycle events `session_start`/`agent_end`/`session_shutdown`; OpenClaw plugin entry is `index.ts` via `definePluginEntry` from `openclaw/plugin-sdk/plugin-entry` + an `openclaw.plugin.json` manifest, with snake_case hook events `agent_turn_prepare`/`agent_end`/`session_end` (NOT colon-delimited). Always make the embedded hook shell-out a silent no-op if the `kimetsu` binary isn't on PATH so a wrong guess never breaks the host. (context: Adding Pi + OpenClaw as BridgeTarget hosts in v1.0.0; the inferred extension/plugin APIs from docs were wrong and had to be corrected against the real repos.)" + }, + { + "ce": 0.03457397595047951, + "key": "windows-unc-paths", + "rank_score": 0.7921637296676636, + "text": "project:fact - [2026-09-05] [tags: windows unc-paths rust std::fs] Windows UNC paths (`\\\\server\\share\\...`) are not supported by most Rust `std::fs` operations unless passed through the extended-length prefix `\\\\?\\UNC\\server\\share\\...`. `std::path::Path::new(\"\\\\\\\\server\\\\share\")` works for basic operations but breaks with `canonicalize()` which returns the verbatim prefix form. When walking directory trees that may start on UNC paths, use the `dunce` crate to strip the verbatim prefix before comparing or displaying paths. Never `cd` into a UNC path in a subprocess started with `std::process::Command` — the subprocess may not inherit it correctly on older Windows. (context: Kimetsu ingest walking paths on network-mounted project directories.)" + }, + { + "ce": 0.0014343964867293835, + "key": "windows-junctions-vs-symlinks", + "rank_score": 0.7848407626152039, + "text": "project:fact - [2026-09-05] [tags: windows junctions symlinks rust std::fs] On Windows, directory junctions (NTFS reparse points) behave like symlinks for directory traversal but `std::fs::symlink_metadata` returns `FileType::is_symlink() = false` for junctions (only true for regular symlinks). Use `std::fs::read_link` — it succeeds for both junction and symlink. `walkdir` crate's `follow_links` follows both, but its `is_symlink()` method correctly reports only actual symlinks. Creating symlinks requires SeCreateSymbolicLinkPrivilege (admin or Developer Mode). Creating junctions requires no special privilege. Use junctions for internal tooling that doesn't need to cross volumes. (context: Kimetsu path handling for brain symlink detection on Windows.)" + }, + { + "ce": 0.5734329223632812, + "key": "onnx-dim-mismatch", + "rank_score": 0.7595473527908325, + "text": "project:fact - [2026-09-05] [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results — the ANN index shape mismatch isn't always caught at runtime. kimetsu detects this by storing `embedder_id` in the brain schema and refusing to query if the configured embedder differs from what was used at ingest time. Mitigation: re-ingest all memories with the new model, or keep per-memory vector dim metadata. (context: Kimetsu embedder migration — detecting dimension mismatch at startup.)" + }, + { + "ce": 0.7162853479385376, + "key": "git-sparse-checkout", + "rank_score": 0.9549997448921204, + "text": "project:fact - [2026-09-05] [tags: git sparse-checkout partial-clone bandwidth] `git sparse-checkout init --cone` combined with `git clone --filter=blob:none` (partial clone) fetches only the commit graph and tree objects, not blobs. Individual blobs are fetched on demand when accessed. This cuts clone time for large repos from minutes to seconds. For kimetsu server-side ingest, use `git clone --depth 1 --filter=blob:none` for the initial checkout, then `git sparse-checkout set ` to limit the working tree to indexed directories. On `git fetch --depth 1 origin main` for refresh, blobs in the sparse set are updated lazily. (context: Kimetsu remote ingest — reducing bandwidth and disk usage for large repo checkouts.)" + } + ], + "delivered": [ + "remote-ingest-split-roots", + "git-sparse-checkout", + "onnx-dim-mismatch" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7687626481056213 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "kimetsu plugin install --remote mcp.json authorization bearer token", + "relevant": [ + "remote-mcp-host-wiring" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999654293060303, + "key": "remote-mcp-host-wiring", + "rank_score": 0.9549995064735413, + "text": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal. Derive a stable repo id from the git remote: strip `.git`, scheme (`://`), and `user@`, then map non-alphanumerics to '-' and collapse — so both https://github.com/org/repo.git and git@github.com:org/repo.git -> `github-com-org-repo`. Remote install writes ONLY the MCP entry + instructions (no local hooks — the brain is on the server). Codex/Pi don't get --remote (no remote-MCP / no MCP). (context: R2: implementing `kimetsu plugin install --remote` to wire a host at a kimetsu-remote HTTP MCP server.)" + }, + { + "ce": 0.9509484171867371, + "key": "pi-openclaw-extension-api", + "rank_score": 0.5225779414176941, + "text": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`. External commands use `pi.exec()` but `node:child_process` spawn also works. Pi has NO MCP so Kimetsu integrates via TS extension + SKILL.md only. (context: Implementing Pi host target for Kimetsu plugin install/status/uninstall system.)\n\nAlso: [tags: kimetsu host-integration pi openclaw bridge] When integrating Kimetsu with an external host agent (Pi, OpenClaw, etc.), VERIFY the host's real plugin/extension API against its actual repo before writing embedded assets — docs-from-memory are frequently wrong. Concretely corrected during v1.0: Pi uses a default-export factory `export default function(pi)` (not `defineExtension`) with lifecycle events `session_start`/`agent_end`/`session_shutdown`; OpenClaw plugin entry is `index.ts` via `definePluginEntry` from `openclaw/plugin-sdk/plugin-entry` + an `openclaw.plugin.json` manifest, with snake_case hook events `agent_turn_prepare`/`agent_end`/`session_end` (NOT colon-delimited). Always make the embedded hook shell-out a silent no-op if the `kimetsu` binary isn't on PATH so a wrong guess never breaks the host. (context: Adding Pi + OpenClaw as BridgeTarget hosts in v1.0.0; the inferred extension/plugin APIs from docs were wrong and had to be corrected against the real repos.)" + }, + { + "ce": 0.24699412286281586, + "key": "bridge-target-enum-seams", + "rank_score": 0.4640461504459381, + "text": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors. (context: Adding BridgeTarget::OpenClaw host to Kimetsu bridge.rs and main.rs in Workstream C)" + }, + { + "ce": 0.08096175640821457, + "key": "clap-version-build-flavor", + "rank_score": 0.4292328357696533, + "text": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds. (context: QQ2: --version build flavor + plugin install self-check)" + }, + { + "ce": 0.20349422097206116, + "key": "onnx-tokenizer-mismatch", + "rank_score": 0.41309261322021484, + "text": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly — specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings — cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo. Validate by checking a reference embedding against the HuggingFace Python output. (context: Kimetsu custom ONNX reranker loading — wrong tokenizer produced degraded retrieval.)" + }, + { + "ce": 0.2866811752319336, + "key": "mcp-stdout-protocol", + "rank_score": 0.4212198853492737, + "text": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr. `println!` in the request handler is forbidden. Use `eprintln!` or `tracing` with a stderr subscriber. In tests of the MCP server, capture stdout as bytes and validate it parses as JSON-Lines. When debugging, set `KIMETSU_LOG=debug` which writes to stderr only. (context: Kimetsu MCP server stdout protocol hygiene.)" + } + ], + "delivered": [ + "remote-mcp-host-wiring", + "pi-openclaw-extension-api" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8215996623039246 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "how do I wire a remote kimetsu brain into Claude Code without storing the token in the config file?", + "relevant": [ + "remote-mcp-host-wiring" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9997368454933167, + "key": "remote-mcp-host-wiring", + "rank_score": 0.9549995064735413, + "text": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal. Derive a stable repo id from the git remote: strip `.git`, scheme (`://`), and `user@`, then map non-alphanumerics to '-' and collapse — so both https://github.com/org/repo.git and git@github.com:org/repo.git -> `github-com-org-repo`. Remote install writes ONLY the MCP entry + instructions (no local hooks — the brain is on the server). Codex/Pi don't get --remote (no remote-MCP / no MCP). (context: R2: implementing `kimetsu plugin install --remote` to wire a host at a kimetsu-remote HTTP MCP server.)" + }, + { + "ce": 0.9267048239707947, + "key": "bedrock-kimetsu-provider", + "rank_score": 0.8291690945625305, + "text": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env. Wire \"bedrock\" into BOTH pipeline.rs provider matches AND the distiller (normalize_distiller_provider + instantiation); the distiller is configured independently so agent-on-Bedrock + harvester-on-direct-Claude works for free. Sign and send the SAME payload bytes; test signing determinism with a fixed SystemTime. (context: Workstream A: adding AWS Bedrock as a provider for the agent + auto-harvester in v1.0.0.)" + }, + { + "ce": 0.0003483921173028648, + "key": "cargo-dev-dep-leak", + "rank_score": 0.6639547944068909, + "text": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates. Run `cargo tree --features ` to trace which crate activated an unexpected feature. (context: Kimetsu testing infra — a dev-dep was activating the embeddings feature in non-test builds.)" + }, + { + "ce": 0.6635663509368896, + "key": "mcp-tool-naming", + "rank_score": 0.6748912334442139, + "text": "project:fact - [tags: mcp tool naming convention kimetsu] MCP tool names must be valid identifiers for all host agents. Claude Code restricts tool names to `[a-zA-Z0-9_-]` and max 64 chars. Use `snake_case` (kimetsu_brain_context, kimetsu_brain_record) — hyphen is technically allowed but some hosts reject it. Avoid dots (not allowed). Namespace with a prefix (`kimetsu_brain_`) to prevent collisions with other MCP servers. When a tool name changes, update ALL host config files (`.mcp.json`, `openclaw.json`, skill markdown) — mismatched names cause silent failures where the host skips the tool. (context: Kimetsu MCP tool naming convention enforcement.)" + }, + { + "ce": 0.07523805648088455, + "key": "gc-trace-env-guard-placement", + "rank_score": 0.6421265602111816, + "text": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site. (context: QQ4 — runs auto-GC on run creation. Env guard placement decision when wiring opportunistic GC into TraceWriter::create.)" + }, + { + "ce": 0.012190239503979683, + "key": "kimetsu-memory-scopes", + "rank_score": 0.6410476565361023, + "text": "project:fact - [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available — if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope. The `kimetsu_brain_record` MCP tool inherits the scope from the server's launch context. When running kimetsu-remote, all memories are project-scoped to the registered repo-id. (context: Kimetsu memory scope system — project vs user isolation.)" + } + ], + "delivered": [ + "remote-mcp-host-wiring", + "bedrock-kimetsu-provider", + "mcp-tool-naming" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7947775721549988 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "cargo feature unification kimetsu-brain embeddings fastembed test failure", + "relevant": [ + "cargo-feature-unification-embeddings" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999688863754272, + "key": "cargo-feature-unification-embeddings", + "rank_score": 0.9549995064735413, + "text": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli). Diagnostic tell: a test that passes alone but fails only under `cargo test --workspace` AND a brand-new crate was just added = suspect feature unification flipping a sibling crate's behavior. (context: Building the kimetsu-remote crate (HTTP MCP server); its default embeddings feature broke 3 kimetsu-chat retrieval tests only under the full workspace test.)" + }, + { + "ce": 0.6766564249992371, + "key": "cargo-dev-dep-leak", + "rank_score": 0.6311139464378357, + "text": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates. Run `cargo tree --features ` to trace which crate activated an unexpected feature. (context: Kimetsu testing infra — a dev-dep was activating the embeddings feature in non-test builds.)" + }, + { + "ce": 0.6029285192489624, + "key": "clap-version-build-flavor", + "rank_score": 0.5744269490242004, + "text": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds. (context: QQ2: --version build flavor + plugin install self-check)" + }, + { + "ce": 0.03332360461354256, + "key": "ci-matrix-explosion", + "rank_score": 0.5249122381210327, + "text": "project:fact - [tags: ci github-actions matrix jobs resources] A CI matrix combining OS (3) x Rust toolchain (3) x features (2) = 18 jobs. Each spawns a runner; at $0.008/min for Ubuntu and $0.016/min for Windows, a 10-minute build costs $2.40 per push. Reduce: test the full matrix only on PRs to main; on feature branches, test only Linux+stable. Use `fail-fast: false` to see all failures, not just the first. Combine related checks (clippy + test) in one job when they share build artifacts. For Windows-specific tests, run only the OS-specific job to reduce cost. (context: Kimetsu CI matrix cost optimization.)" + }, + { + "ce": 0.667348325252533, + "key": "cargo-profile-override", + "rank_score": 0.5246867537498474, + "text": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug. The downside: rebuild time increases for that crate. For overflow-checks, `overflow-checks = false` per package speeds up hot loops. Never disable overflow-checks in release for business-critical data-mutating code. `[profile.release] strip = \"debuginfo\"` reduces binary size with minimal impact on stack traces. (context: Kimetsu dev experience — embedding inference was 10x slower in debug builds.)" + }, + { + "ce": 0.45561033487319946, + "key": "onnx-tokenizer-mismatch", + "rank_score": 0.48954513669013977, + "text": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly — specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings — cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo. Validate by checking a reference embedding against the HuggingFace Python output. (context: Kimetsu custom ONNX reranker loading — wrong tokenizer produced degraded retrieval.)" + } + ], + "delivered": [ + "cargo-feature-unification-embeddings", + "cargo-dev-dep-leak", + "cargo-profile-override", + "clap-version-build-flavor" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7502140998840332 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "my integration tests pass in isolation but break when I run cargo test --workspace — embedder changed?", + "relevant": [ + "cargo-feature-unification-embeddings" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9980612397193909, + "key": "cargo-feature-unification-embeddings", + "rank_score": 0.8994753360748291, + "text": "project:fact - [2026-09-05] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli). Diagnostic tell: a test that passes alone but fails only under `cargo test --workspace` AND a brand-new crate was just added = suspect feature unification flipping a sibling crate's behavior. (context: Building the kimetsu-remote crate (HTTP MCP server); its default embeddings feature broke 3 kimetsu-chat retrieval tests only under the full workspace test.)" + }, + { + "ce": 0.2377571314573288, + "key": "init-project-git-boundary", + "rank_score": 0.9549995064735413, + "text": "project:fact - [2026-09-05] [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain. (context: QQ3 — kimetsu setup integration test failed because init_project climbed git tree to real ~/.kimetsu instead of temp workspace)" + }, + { + "ce": 0.006763927638530731, + "key": "cargo-patch-section", + "rank_score": 0.707301139831543, + "text": "project:fact - [2026-09-05] [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace — including transitive deps — that depend on `my-crate`. Remove the patch before publishing. Using `[replace]` is deprecated since Cargo 0.47; always use `[patch]`. When patching a crate pinned via an exact version specifier, the patch must satisfy that exact version. Use `cargo tree` to confirm the patch is applied. (context: Kimetsu patching upstream rusqlite for a Windows-specific WAL fix.)" + }, + { + "ce": 0.0003770666371565312, + "key": "git-worktree-brain-isolation", + "rank_score": 0.7147300243377686, + "text": "project:fact - [2026-09-05] [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root — if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain. Always set `KIMETSU_BRAIN_DIR` or use `git_init_boundary` in tests to prevent this. (context: Kimetsu development with git worktrees — test isolation.)" + }, + { + "ce": 0.011705971322953701, + "key": "tokio-runtime-in-tests", + "rank_score": 0.7210437059402466, + "text": "project:fact - [2026-09-05] [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests. For sync test code that calls async, use `tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async { ... })`. Never call `block_on` from inside an async function. (context: Kimetsu remote integration tests — nested runtime panic.)" + }, + { + "ce": 0.001660109031945467, + "key": "testing-serial-vs-parallel", + "rank_score": 0.6186650395393372, + "text": "project:fact - [2026-09-05] [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`). `cargo nextest` runs each test in a separate process by default, avoiding the problem entirely at the cost of longer startup time. For kimetsu, prefer nextest in CI and accept that `test_env_lock` exists only for `cargo test` compatibility. (context: Kimetsu test suite — env-var mutation in parallel tests.)" + } + ], + "delivered": [ + "cargo-feature-unification-embeddings" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7686222791671753 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "build_anthropic_body bedrock-2023-05-31 InvokeModel blocking reqwest", + "relevant": [ + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999423027038574, + "key": "bedrock-kimetsu-provider", + "rank_score": 0.9549994468688965, + "text": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env. Wire \"bedrock\" into BOTH pipeline.rs provider matches AND the distiller (normalize_distiller_provider + instantiation); the distiller is configured independently so agent-on-Bedrock + harvester-on-direct-Claude works for free. Sign and send the SAME payload bytes; test signing determinism with a fixed SystemTime. (context: Workstream A: adding AWS Bedrock as a provider for the agent + auto-harvester in v1.0.0.)" + }, + { + "ce": 0.9967412352561951, + "key": "aws-sigv4-bedrock-blocking", + "rank_score": 0.7320441603660583, + "text": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed. aws-smithy-runtime-api required as a companion to supply Identity. (context: Implementing BedrockProvider for Kimetsu with blocking reqwest + SigV4 signing, no tokio/aws-sdk)" + }, + { + "ce": 0.04677147418260574, + "key": "aws-region-resolution", + "rank_score": 0.5447235703468323, + "text": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time. For cross-region inference (e.g. us-west-2 for Claude Opus), set `AWS_REGION=us-west-2`; do NOT rely on the Bedrock endpoint prefix being region-agnostic. (context: Kimetsu Bedrock provider region configuration.)" + }, + { + "ce": 0.0000826321920612827, + "key": "tokio-spawn-blocking", + "rank_score": 0.43329864740371704, + "text": "project:fact - [tags: tokio spawn_blocking thread-pool rust blocking] `tokio::task::spawn_blocking` places work on a dedicated blocking thread pool (default up to 512 threads, configurable via `Builder::max_blocking_threads`). Each call creates or reuses a thread — there's no true pooling, threads may be created on demand. For many short-duration blocking calls (e.g. per-query SQLite reads), thread creation overhead may dominate. Prefer batching: collect N queries, then one `spawn_blocking` to run them all. Alternatively, keep a persistent blocking task that reads from an mpsc channel. Profile with `tokio-console` if you suspect spawn_blocking overhead. (context: Kimetsu retrieval server — per-query spawn_blocking was adding ~0.3ms overhead.)" + }, + { + "ce": 0.023912858217954636, + "key": "aws-retry-throttling", + "rank_score": 0.4265473186969757, + "text": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with ±25% jitter. Do NOT retry `ValidationException` or `AccessDeniedException` — these are permanent errors. `ModelStreamErrorException` during streaming may be retryable. Log the `x-amzn-requestid` header from failed responses for AWS support debugging. (context: Kimetsu Bedrock provider retry logic.)" + }, + { + "ce": 0.0004045580280944705, + "key": "http-proxy-env", + "rank_score": 0.40795642137527466, + "text": "project:fact - [tags: http proxy environment reqwest rust corporate] reqwest respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` environment variables by default (with `default-tls` or `rustls-tls`). In a corporate network, these may redirect traffic through an intercepting proxy that breaks mTLS or adds latency. To disable proxy usage entirely: `reqwest::ClientBuilder::no_proxy()`. On Windows, reqwest does NOT use the system proxy settings (IE/WinInet) — you must set env vars explicitly. `NO_PROXY=127.0.0.1,localhost` prevents proxying loopback traffic (important for kimetsu-remote local dev). (context: Kimetsu provider calls failing behind corporate proxy on Windows.)" + } + ], + "delivered": [ + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8114692568778992 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "how do I add AWS Bedrock as a model provider in Kimetsu without pulling in the aws-sdk?", + "relevant": [ + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999690055847168, + "key": "bedrock-kimetsu-provider", + "rank_score": 0.9549994468688965, + "text": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env. Wire \"bedrock\" into BOTH pipeline.rs provider matches AND the distiller (normalize_distiller_provider + instantiation); the distiller is configured independently so agent-on-Bedrock + harvester-on-direct-Claude works for free. Sign and send the SAME payload bytes; test signing determinism with a fixed SystemTime. (context: Workstream A: adding AWS Bedrock as a provider for the agent + auto-harvester in v1.0.0.)" + }, + { + "ce": 0.9369313716888428, + "key": "aws-retry-throttling", + "rank_score": 0.7062099575996399, + "text": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with ±25% jitter. Do NOT retry `ValidationException` or `AccessDeniedException` — these are permanent errors. `ModelStreamErrorException` during streaming may be retryable. Log the `x-amzn-requestid` header from failed responses for AWS support debugging. (context: Kimetsu Bedrock provider retry logic.)" + }, + { + "ce": 0.9984136819839478, + "key": "aws-region-resolution", + "rank_score": 0.6946679353713989, + "text": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time. For cross-region inference (e.g. us-west-2 for Claude Opus), set `AWS_REGION=us-west-2`; do NOT rely on the Bedrock endpoint prefix being region-agnostic. (context: Kimetsu Bedrock provider region configuration.)" + }, + { + "ce": 0.9353601336479187, + "key": "aws-credentials-chain", + "rank_score": 0.5768946409225464, + "text": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually. On Windows, `~/.aws` is `%USERPROFILE%\\.aws` — `std::env::var(\"USERPROFILE\")` to get the path since `~` expansion is shell-level. (context: Kimetsu Bedrock provider credential resolution.)" + }, + { + "ce": 0.9974077343940735, + "key": "aws-sigv4-bedrock-blocking", + "rank_score": 0.562364935874939, + "text": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed. aws-smithy-runtime-api required as a companion to supply Identity. (context: Implementing BedrockProvider for Kimetsu with blocking reqwest + SigV4 signing, no tokio/aws-sdk)" + }, + { + "ce": 0.32741013169288635, + "key": "kimetsu-distiller-config", + "rank_score": 0.5180878639221191, + "text": "project:fact - [tags: kimetsu distiller harvest config provider] The kimetsu distiller (auto-harvester) uses a SEPARATE provider configuration from the main agent: `distiller.provider`, `distiller.model`, `distiller.api_key`. This allows running the agent on an expensive model (Claude Opus) while harvesting with a cheap model (Claude Haiku). If `distiller.provider` is not set, it inherits `provider`. The distiller runs as a background task triggered by the post-session hook; it reads the session transcript and emits `kimetsu_brain_record` calls. Distiller timeouts are longer (300s) than normal tool calls (60s) because transcript processing can be slow. (context: Kimetsu distiller provider configuration — agent vs harvester model separation.)" + } + ], + "delivered": [ + "bedrock-kimetsu-provider", + "aws-region-resolution", + "aws-sigv4-bedrock-blocking", + "aws-retry-throttling" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8971834182739258 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "BridgeTarget enum seams plugin_install_inner plugin_status_inner resolve_setup_hosts", + "relevant": [ + "bridge-target-enum-seams" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999773502349854, + "key": "bridge-target-enum-seams", + "rank_score": 0.9549993872642517, + "text": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors. (context: Adding BridgeTarget::OpenClaw host to Kimetsu bridge.rs and main.rs in Workstream C)" + }, + { + "ce": 0.019532261416316032, + "key": "windows-update-process-locking", + "rank_score": 0.46850162744522095, + "text": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics — mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code. (context: Q2 — kimetsu update preflight for locked binary on Windows)" + }, + { + "ce": 0.00004985520718037151, + "key": "kimetsu-daemon-lifecycle", + "rank_score": 0.45895230770111084, + "text": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required. The server PID is not stored anywhere; use `kimetsu doctor` to enumerate running MCP server processes via OS APIs. On Windows, the server binary may be locked by AV after first launch — `kimetsu update` must stop all running server processes before replacing the binary. (context: Kimetsu daemon lifecycle — process management for updates.)" + }, + { + "ce": 0.7292461395263672, + "key": "pi-openclaw-extension-api", + "rank_score": 0.33482903242111206, + "text": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`. External commands use `pi.exec()` but `node:child_process` spawn also works. Pi has NO MCP so Kimetsu integrates via TS extension + SKILL.md only. (context: Implementing Pi host target for Kimetsu plugin install/status/uninstall system.)\n\nAlso: [tags: kimetsu host-integration pi openclaw bridge] When integrating Kimetsu with an external host agent (Pi, OpenClaw, etc.), VERIFY the host's real plugin/extension API against its actual repo before writing embedded assets — docs-from-memory are frequently wrong. Concretely corrected during v1.0: Pi uses a default-export factory `export default function(pi)` (not `defineExtension`) with lifecycle events `session_start`/`agent_end`/`session_shutdown`; OpenClaw plugin entry is `index.ts` via `definePluginEntry` from `openclaw/plugin-sdk/plugin-entry` + an `openclaw.plugin.json` manifest, with snake_case hook events `agent_turn_prepare`/`agent_end`/`session_end` (NOT colon-delimited). Always make the embedded hook shell-out a silent no-op if the `kimetsu` binary isn't on PATH so a wrong guess never breaks the host. (context: Adding Pi + OpenClaw as BridgeTarget hosts in v1.0.0; the inferred extension/plugin APIs from docs were wrong and had to be corrected against the real repos.)" + }, + { + "ce": 0.0008126430329866707, + "key": "harbor-terminal-bench-subprocess-isolation", + "rank_score": 0.274738609790802, + "text": "project:fact - [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd). Worker re-derives auth internally from .env so the OAuth token never lands in argv; it writes {run,grade} JSON the parent reads back. One Harbor invocation per process always works (baseline-alone passed). (context: kbench multi-trial sweeps crashed on every trial after the 1st; diagnosed as Harbor/pyiceberg os.getcwd staleness on WSL2.)" + }, + { + "ce": 0.00017747485253494233, + "key": "sqlite-fts5-tokenizer", + "rank_score": 0.2745037078857422, + "text": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon. If you switch tokenizers on an existing FTS5 table, you MUST rebuild the shadow tables: `INSERT INTO tbl(tbl) VALUES('rebuild');` — a schema-only change leaves the inverted index unusable. The `porter` stemmer is available as `tokenize='porter unicode61'` but aggressively strips suffixes and hurts precision on technical terms. (context: Kimetsu brain FTS5 index tuning for Rust identifier retrieval.)" + } + ], + "delivered": [ + "bridge-target-enum-seams", + "pi-openclaw-extension-api" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8089725375175476 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "I added a new host to the bridge enum but cargo gives me compile errors in five different match arms — what did I miss?", + "relevant": [ + "bridge-target-enum-seams" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9977103471755981, + "key": "bridge-target-enum-seams", + "rank_score": 0.9549993872642517, + "text": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors. (context: Adding BridgeTarget::OpenClaw host to Kimetsu bridge.rs and main.rs in Workstream C)" + }, + { + "ce": 0.011918189004063606, + "key": "cargo-feature-unification-embeddings", + "rank_score": 0.6092767715454102, + "text": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli). Diagnostic tell: a test that passes alone but fails only under `cargo test --workspace` AND a brand-new crate was just added = suspect feature unification flipping a sibling crate's behavior. (context: Building the kimetsu-remote crate (HTTP MCP server); its default embeddings feature broke 3 kimetsu-chat retrieval tests only under the full workspace test.)" + }, + { + "ce": 0.005956617183983326, + "key": "pi-openclaw-extension-api", + "rank_score": 0.5642949938774109, + "text": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`. External commands use `pi.exec()` but `node:child_process` spawn also works. Pi has NO MCP so Kimetsu integrates via TS extension + SKILL.md only. (context: Implementing Pi host target for Kimetsu plugin install/status/uninstall system.)\n\nAlso: [tags: kimetsu host-integration pi openclaw bridge] When integrating Kimetsu with an external host agent (Pi, OpenClaw, etc.), VERIFY the host's real plugin/extension API against its actual repo before writing embedded assets — docs-from-memory are frequently wrong. Concretely corrected during v1.0: Pi uses a default-export factory `export default function(pi)` (not `defineExtension`) with lifecycle events `session_start`/`agent_end`/`session_shutdown`; OpenClaw plugin entry is `index.ts` via `definePluginEntry` from `openclaw/plugin-sdk/plugin-entry` + an `openclaw.plugin.json` manifest, with snake_case hook events `agent_turn_prepare`/`agent_end`/`session_end` (NOT colon-delimited). Always make the embedded hook shell-out a silent no-op if the `kimetsu` binary isn't on PATH so a wrong guess never breaks the host. (context: Adding Pi + OpenClaw as BridgeTarget hosts in v1.0.0; the inferred extension/plugin APIs from docs were wrong and had to be corrected against the real repos.)" + }, + { + "ce": 0.00004475489186006598, + "key": "windows-update-process-locking", + "rank_score": 0.5448954701423645, + "text": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics — mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code. (context: Q2 — kimetsu update preflight for locked binary on Windows)" + }, + { + "ce": 0.00004005066875834018, + "key": "onnx-cosine-vs-dot", + "rank_score": 0.5013427138328552, + "text": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing — double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g. E5, GTE with separate query/passage prefixes), the query and document encoders must use different prefix strings. Check the model card's `Similarity function` field. usearch/qdrant: prefer `MetricKind::Cos` over `Dot` for passage vectors that may not be perfectly normalized. (context: Kimetsu embedding storage — similarity metric selection.)" + }, + { + "ce": 0.00010352722165407613, + "key": "mcp-schema-validation", + "rank_score": 0.4870358109474182, + "text": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array — omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error. Use `serde(default)` for optional fields. When adding a new tool, check the schema in the tools/list response manually by piping a JSON-RPC tools/list request to `./kimetsu mcp`. (context: Kimetsu MCP tool schema — required field validation.)" + } + ], + "delivered": [ + "bridge-target-enum-seams" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8239090442657471 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "Pi extension factory defineExtension agent_end session_shutdown kimetsu.ts", + "relevant": [ + "pi-openclaw-extension-api" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9995530247688293, + "key": "pi-openclaw-extension-api", + "rank_score": 0.9549993276596069, + "text": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`. External commands use `pi.exec()` but `node:child_process` spawn also works. Pi has NO MCP so Kimetsu integrates via TS extension + SKILL.md only. (context: Implementing Pi host target for Kimetsu plugin install/status/uninstall system.)\n\nAlso: [tags: kimetsu host-integration pi openclaw bridge] When integrating Kimetsu with an external host agent (Pi, OpenClaw, etc.), VERIFY the host's real plugin/extension API against its actual repo before writing embedded assets — docs-from-memory are frequently wrong. Concretely corrected during v1.0: Pi uses a default-export factory `export default function(pi)` (not `defineExtension`) with lifecycle events `session_start`/`agent_end`/`session_shutdown`; OpenClaw plugin entry is `index.ts` via `definePluginEntry` from `openclaw/plugin-sdk/plugin-entry` + an `openclaw.plugin.json` manifest, with snake_case hook events `agent_turn_prepare`/`agent_end`/`session_end` (NOT colon-delimited). Always make the embedded hook shell-out a silent no-op if the `kimetsu` binary isn't on PATH so a wrong guess never breaks the host. (context: Adding Pi + OpenClaw as BridgeTarget hosts in v1.0.0; the inferred extension/plugin APIs from docs were wrong and had to be corrected against the real repos.)" + }, + { + "ce": 0.09174210578203201, + "key": "bedrock-kimetsu-provider", + "rank_score": 0.3790968060493469, + "text": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env. Wire \"bedrock\" into BOTH pipeline.rs provider matches AND the distiller (normalize_distiller_provider + instantiation); the distiller is configured independently so agent-on-Bedrock + harvester-on-direct-Claude works for free. Sign and send the SAME payload bytes; test signing determinism with a fixed SystemTime. (context: Workstream A: adding AWS Bedrock as a provider for the agent + auto-harvester in v1.0.0.)" + }, + { + "ce": 0.0007624977151863277, + "key": "git-submodule-pinning", + "rank_score": 0.3443452715873718, + "text": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip — this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version. If a submodule is the kimetsu-bench repo inside the main repo, pin the bench SHA after validating the dataset change. Use `git diff HEAD -- bench` to see the pinned SHA change before committing. (context: Kimetsu bench as a git submodule of the main repo.)" + }, + { + "ce": 0.00026442710077390075, + "key": "sqlite-json1-extract", + "rank_score": 0.3536471128463745, + "text": "project:fact - [tags: sqlite json1 json_extract rusqlite] SQLite's json1 extension (built in since 3.38.0) lets you index and query JSONB columns with `json_extract(col, '$.field')`. To create a partial index over a JSON field: `CREATE INDEX idx ON memories (json_extract(metadata, '$.scope')) WHERE json_extract(metadata, '$.scope') IS NOT NULL;`. Use `json_each` for array fields. On older SQLite builds (rusqlite links whatever the system provides), check for json1 with `SELECT json('{}');` — an error means it's absent. Always prefer column storage over JSON blobs for frequently queried fields. (context: Kimetsu brain querying metadata scopes without migrating a separate column.)" + }, + { + "ce": 0.0020951488986611366, + "key": "tokio-channel-backpressure", + "rank_score": 0.3268624544143677, + "text": "project:fact - [tags: tokio mpsc channel backpressure async rust] `tokio::sync::mpsc::channel(N)` with a bounded buffer provides backpressure: senders block when the buffer is full. This prevents unbounded memory growth but can cause sender tasks to stall. Choosing N: too small causes frequent backpressure (throughput drops); too large defeats the purpose. For kimetsu's harvest pipeline, N=16 was a good balance — the harvester is I/O bound (LLM call), producers are fast (hook callbacks). Prefer bounded channels over unbounded in production code. `tokio::sync::mpsc::unbounded_channel()` is a footgun for bursty producers. (context: Kimetsu auto-harvester pipeline — bounded vs unbounded channel selection.)" + }, + { + "ce": 0.0001601348485564813, + "key": "process-start-time-cross-platform", + "rank_score": 0.32361119985580444, + "text": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path). Keep the skew decision logic in a pure function `assess_mcp_skew(servers, binary_mtime, binary_path) -> Outcome` so it can be unit-tested without any live OS state. (context: Q3 — kimetsu doctor version-skew check for stale MCP server processes)" + } + ], + "delivered": [ + "pi-openclaw-extension-api" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8331579566001892 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "how does Pi (earendil-works/pi) load plugins and what lifecycle hooks does it expose?", + "relevant": [ + "pi-openclaw-extension-api" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9957007765769958, + "key": "pi-openclaw-extension-api", + "rank_score": 0.9549992680549622, + "text": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`. External commands use `pi.exec()` but `node:child_process` spawn also works. Pi has NO MCP so Kimetsu integrates via TS extension + SKILL.md only. (context: Implementing Pi host target for Kimetsu plugin install/status/uninstall system.)\n\nAlso: [tags: kimetsu host-integration pi openclaw bridge] When integrating Kimetsu with an external host agent (Pi, OpenClaw, etc.), VERIFY the host's real plugin/extension API against its actual repo before writing embedded assets — docs-from-memory are frequently wrong. Concretely corrected during v1.0: Pi uses a default-export factory `export default function(pi)` (not `defineExtension`) with lifecycle events `session_start`/`agent_end`/`session_shutdown`; OpenClaw plugin entry is `index.ts` via `definePluginEntry` from `openclaw/plugin-sdk/plugin-entry` + an `openclaw.plugin.json` manifest, with snake_case hook events `agent_turn_prepare`/`agent_end`/`session_end` (NOT colon-delimited). Always make the embedded hook shell-out a silent no-op if the `kimetsu` binary isn't on PATH so a wrong guess never breaks the host. (context: Adding Pi + OpenClaw as BridgeTarget hosts in v1.0.0; the inferred extension/plugin APIs from docs were wrong and had to be corrected against the real repos.)" + }, + { + "ce": 0.00009015409887069836, + "key": "git-hooks-bypass", + "rank_score": 0.5862308144569397, + "text": "project:fact - [tags: git hooks bypass pre-commit skip] `git commit --no-verify` skips ALL hooks (pre-commit and commit-msg). Never use this in shared team repos where hooks enforce quality gates (lint, tests, memory harvest). Instead, fix the failing hook. If the hook itself is broken, fix the hook script. For emergency commits where hooks aren't relevant (e.g. updating a gitignore to untrack already-committed files), document the `--no-verify` use in the commit message. In CI, hooks run only if explicitly invoked — `git commit` in a CI pipeline with no hooks configured does nothing for quality enforcement. (context: Kimetsu pre-commit hook enforcing memory harvest.)" + }, + { + "ce": 0.000041975097701651976, + "key": "cargo-lockfile-drift", + "rank_score": 0.5629675388336182, + "text": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this — it errors on any lockfile diff. For library crates, `Cargo.lock` is normally gitignored, but for workspace roots with binary crates it should be committed. Use `cargo update --precise ` to pin a specific dep version without touching unrelated entries. (context: Kimetsu workspace lockfile drift after adding kimetsu-remote crate.)" + }, + { + "ce": 0.0007190185133367777, + "key": "kimetsu-daemon-lifecycle", + "rank_score": 0.5281153321266174, + "text": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required. The server PID is not stored anywhere; use `kimetsu doctor` to enumerate running MCP server processes via OS APIs. On Windows, the server binary may be locked by AV after first launch — `kimetsu update` must stop all running server processes before replacing the binary. (context: Kimetsu daemon lifecycle — process management for updates.)" + }, + { + "ce": 0.0028578205965459347, + "key": "remote-mcp-host-wiring", + "rank_score": 0.5280227661132812, + "text": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal. Derive a stable repo id from the git remote: strip `.git`, scheme (`://`), and `user@`, then map non-alphanumerics to '-' and collapse — so both https://github.com/org/repo.git and git@github.com:org/repo.git -> `github-com-org-repo`. Remote install writes ONLY the MCP entry + instructions (no local hooks — the brain is on the server). Codex/Pi don't get --remote (no remote-MCP / no MCP). (context: R2: implementing `kimetsu plugin install --remote` to wire a host at a kimetsu-remote HTTP MCP server.)" + }, + { + "ce": 0.0000638943602098152, + "key": "tokio-channel-backpressure", + "rank_score": 0.49195030331611633, + "text": "project:fact - [tags: tokio mpsc channel backpressure async rust] `tokio::sync::mpsc::channel(N)` with a bounded buffer provides backpressure: senders block when the buffer is full. This prevents unbounded memory growth but can cause sender tasks to stall. Choosing N: too small causes frequent backpressure (throughput drops); too large defeats the purpose. For kimetsu's harvest pipeline, N=16 was a good balance — the harvester is I/O bound (LLM call), producers are fast (hook callbacks). Prefer bounded channels over unbounded in production code. `tokio::sync::mpsc::unbounded_channel()` is a footgun for bursty producers. (context: Kimetsu auto-harvester pipeline — bounded vs unbounded channel selection.)" + } + ], + "delivered": [ + "pi-openclaw-extension-api" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7763376235961914 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "aws-sigv4 SigningParams apply_to_request_http1x reqwest sign-http", + "relevant": [ + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.999910831451416, + "key": "aws-sigv4-bedrock-blocking", + "rank_score": 0.9549992680549622, + "text": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed. aws-smithy-runtime-api required as a companion to supply Identity. (context: Implementing BedrockProvider for Kimetsu with blocking reqwest + SigV4 signing, no tokio/aws-sdk)" + }, + { + "ce": 0.9909924268722534, + "key": "bedrock-kimetsu-provider", + "rank_score": 0.6150126457214355, + "text": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env. Wire \"bedrock\" into BOTH pipeline.rs provider matches AND the distiller (normalize_distiller_provider + instantiation); the distiller is configured independently so agent-on-Bedrock + harvester-on-direct-Claude works for free. Sign and send the SAME payload bytes; test signing determinism with a fixed SystemTime. (context: Workstream A: adding AWS Bedrock as a provider for the agent + auto-harvester in v1.0.0.)" + }, + { + "ce": 0.9447544813156128, + "key": "aws-presigned-urls", + "rank_score": 0.541763186454773, + "text": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time — clock skew > 15 minutes causes `RequestTimeTooSkewed`. kimetsu could use presigned URLs to serve brain exports from S3 without exposing credentials to the client. (context: Kimetsu potential S3 export feature — presigned URL generation.)" + }, + { + "ce": 0.23717589676380157, + "key": "aws-credentials-chain", + "rank_score": 0.4577735662460327, + "text": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually. On Windows, `~/.aws` is `%USERPROFILE%\\.aws` — `std::env::var(\"USERPROFILE\")` to get the path since `~` expansion is shell-level. (context: Kimetsu Bedrock provider credential resolution.)" + }, + { + "ce": 0.004325655288994312, + "key": "http-proxy-env", + "rank_score": 0.43296730518341064, + "text": "project:fact - [tags: http proxy environment reqwest rust corporate] reqwest respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` environment variables by default (with `default-tls` or `rustls-tls`). In a corporate network, these may redirect traffic through an intercepting proxy that breaks mTLS or adds latency. To disable proxy usage entirely: `reqwest::ClientBuilder::no_proxy()`. On Windows, reqwest does NOT use the system proxy settings (IE/WinInet) — you must set env vars explicitly. `NO_PROXY=127.0.0.1,localhost` prevents proxying loopback traffic (important for kimetsu-remote local dev). (context: Kimetsu provider calls failing behind corporate proxy on Windows.)" + }, + { + "ce": 0.005192347802221775, + "key": "aws-retry-throttling", + "rank_score": 0.4164358675479889, + "text": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with ±25% jitter. Do NOT retry `ValidationException` or `AccessDeniedException` — these are permanent errors. `ModelStreamErrorException` during streaming may be retryable. Log the `x-amzn-requestid` header from failed responses for AWS support debugging. (context: Kimetsu Bedrock provider retry logic.)" + } + ], + "delivered": [ + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider", + "aws-presigned-urls" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7797333002090454 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "how do I sign a Bedrock InvokeModel request with aws-sigv4 in blocking Rust?", + "relevant": [ + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.99993896484375, + "key": "aws-sigv4-bedrock-blocking", + "rank_score": 0.9549992680549622, + "text": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed. aws-smithy-runtime-api required as a companion to supply Identity. (context: Implementing BedrockProvider for Kimetsu with blocking reqwest + SigV4 signing, no tokio/aws-sdk)" + }, + { + "ce": 0.9999256134033203, + "key": "bedrock-kimetsu-provider", + "rank_score": 0.7227458357810974, + "text": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env. Wire \"bedrock\" into BOTH pipeline.rs provider matches AND the distiller (normalize_distiller_provider + instantiation); the distiller is configured independently so agent-on-Bedrock + harvester-on-direct-Claude works for free. Sign and send the SAME payload bytes; test signing determinism with a fixed SystemTime. (context: Workstream A: adding AWS Bedrock as a provider for the agent + auto-harvester in v1.0.0.)" + }, + { + "ce": 0.39203885197639465, + "key": "aws-region-resolution", + "rank_score": 0.6626273989677429, + "text": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time. For cross-region inference (e.g. us-west-2 for Claude Opus), set `AWS_REGION=us-west-2`; do NOT rely on the Bedrock endpoint prefix being region-agnostic. (context: Kimetsu Bedrock provider region configuration.)" + }, + { + "ce": 0.9729819893836975, + "key": "aws-presigned-urls", + "rank_score": 0.614642858505249, + "text": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time — clock skew > 15 minutes causes `RequestTimeTooSkewed`. kimetsu could use presigned URLs to serve brain exports from S3 without exposing credentials to the client. (context: Kimetsu potential S3 export feature — presigned URL generation.)" + }, + { + "ce": 0.2369483858346939, + "key": "aws-retry-throttling", + "rank_score": 0.5518304109573364, + "text": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with ±25% jitter. Do NOT retry `ValidationException` or `AccessDeniedException` — these are permanent errors. `ModelStreamErrorException` during streaming may be retryable. Log the `x-amzn-requestid` header from failed responses for AWS support debugging. (context: Kimetsu Bedrock provider retry logic.)" + }, + { + "ce": 0.5573575496673584, + "key": "aws-credentials-chain", + "rank_score": 0.5419862866401672, + "text": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually. On Windows, `~/.aws` is `%USERPROFILE%\\.aws` — `std::env::var(\"USERPROFILE\")` to get the path since `~` expansion is shell-level. (context: Kimetsu Bedrock provider credential resolution.)" + } + ], + "delivered": [ + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider", + "aws-presigned-urls", + "aws-credentials-chain" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.9141742587089539 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "KIMETSU_RUNS_GC env opt-out TraceWriter create gc_old_runs caller", + "relevant": [ + "gc-trace-env-guard-placement" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999783039093018, + "key": "gc-trace-env-guard-placement", + "rank_score": 0.9549992680549622, + "text": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site. (context: QQ4 — runs auto-GC on run creation. Env guard placement decision when wiring opportunistic GC into TraceWriter::create.)" + }, + { + "ce": 0.4401592016220093, + "key": "ci-flaky-quarantine", + "rank_score": 0.388689786195755, + "text": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal — a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output. NEVER let a flaky test gate the merge queue. For kimetsu timing-based tests (`test_gc_old_runs_deletes_ancient`), apply `#[cfg_attr(ci, ignore)]` and run only in a dedicated slow-CI job. (context: Kimetsu CI flaky test policy.)" + }, + { + "ce": 0.0057434565387666225, + "key": "sqlite-partial-index", + "rank_score": 0.3785693347454071, + "text": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query — the planner uses the partial index only when the WHERE clause matches. Verify index usage with `EXPLAIN QUERY PLAN SELECT ...`. Partial indexes are not supported before SQLite 3.8.0; rusqlite's bundled SQLite is always current, but system SQLite on old Debian/Ubuntu may not be. (context: Optimizing kimetsu brain retrieval query over the active-memories subset.)" + }, + { + "ce": 0.0955595001578331, + "key": "ci-secrets-masking", + "rank_score": 0.3744296431541443, + "text": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output — but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable. Never reconstruct secrets from parts in step output. For kimetsu bench `--remote` CI runs, `KIMETSU_REMOTE_TOKEN` must be in the repository secrets, not in the workflow YAML. Use `${{ secrets.KIMETSU_REMOTE_TOKEN }}` in env — never `echo ${{ secrets.KIMETSU_REMOTE_TOKEN }}` in a run step. (context: Kimetsu CI remote benchmark — token handling.)" + }, + { + "ce": 0.21514949202537537, + "key": "onnx-ort-threading", + "rank_score": 0.35279354453086853, + "text": "project:fact - [tags: onnx ort thread-pool parallelism cpu] ORT (ONNX Runtime) creates its own inter-op and intra-op thread pools. In a multi-process bench setup, each child inherits these pools and they compete for CPU cores. Set `SessionOptionsBuilder::with_intra_threads(1).with_inter_threads(1)` if you're running many parallel bench processes — this sacrifices per-inference throughput for lower contention. In a single-threaded embedding pipeline, 2-4 intra-op threads are better. For benchmarking, set `ORT_NUM_THREADS=1` via env var to get deterministic single-threaded latency numbers. (context: Kimetsu brain bench multi-process parallelism — ORT thread contention causing inconsistent latency.)" + }, + { + "ce": 0.24473801255226135, + "key": "mutex-deadlock-user-brain-disabled", + "rank_score": 0.3688562512397766, + "text": "project:fact - [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure — `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation. (context: New tests for Tier-1 perf work called test_env_lock().lock() inside with_user_brain_disabled closure, deadlocking all project::tests that ran after them in the same test binary.)" + } + ], + "delivered": [ + "gc-trace-env-guard-placement", + "ci-flaky-quarantine" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8781748414039612 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "where should I put the KIMETSU_RUNS_GC=0 guard — inside the GC function or at the call site?", + "relevant": [ + "gc-trace-env-guard-placement" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999699592590332, + "key": "gc-trace-env-guard-placement", + "rank_score": 0.9549992084503174, + "text": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site. (context: QQ4 — runs auto-GC on run creation. Env guard placement decision when wiring opportunistic GC into TraceWriter::create.)" + }, + { + "ce": 0.0060012745670974255, + "key": "http-retry-idempotency", + "rank_score": 0.475990355014801, + "text": "project:fact - [tags: http retry idempotency post put reqwest] Only retry idempotent requests automatically. GET, HEAD, PUT, DELETE are idempotent. POST is NOT — retrying a POST may create duplicate resources. For LLM API calls (POST), implement retry with idempotency keys: include a stable `X-Idempotency-Key: ` header; the provider deduplicates. For transient 429 (rate limit) responses, back off with jitter: `min(base * 2^attempt, cap) + rand(0, base)`. For 5xx, retry at most 3 times. Never retry on 4xx (except 429). In kimetsu, retry logic lives in the provider layer, not the distiller. (context: Kimetsu LLM provider retry strategy.)" + }, + { + "ce": 0.015108779072761536, + "key": "testing-temp-dirs-ci", + "rank_score": 0.4878496527671814, + "text": "project:fact - [tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure. On Windows, `env::temp_dir()` returns `C:\\Users\\\\AppData\\Local\\Temp` — ensure the test binary has write permissions there. Avoid using the workspace root as a temp dir — tests should never write to the source tree. (context: Kimetsu test infrastructure — temp directory discipline.)" + }, + { + "ce": 0.10865218937397003, + "key": "tokio-runtime-in-tests", + "rank_score": 0.4717335104942322, + "text": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests. For sync test code that calls async, use `tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async { ... })`. Never call `block_on` from inside an async function. (context: Kimetsu remote integration tests — nested runtime panic.)" + }, + { + "ce": 0.001162411062978208, + "key": "onnx-cosine-vs-dot", + "rank_score": 0.4470091164112091, + "text": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing — double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g. E5, GTE with separate query/passage prefixes), the query and document encoders must use different prefix strings. Check the model card's `Similarity function` field. usearch/qdrant: prefer `MetricKind::Cos` over `Dot` for passage vectors that may not be perfectly normalized. (context: Kimetsu embedding storage — similarity metric selection.)" + }, + { + "ce": 0.009344962425529957, + "key": "testing-time-dependent-flakes", + "rank_score": 0.44397956132888794, + "text": "project:fact - [tags: testing time flaky clock mock rust] Tests that depend on wall-clock time are inherently flaky under load (slow CI runners, GC pauses). Abstract time behind a trait (`Clock: Fn() -> SystemTime`) injected at construction, and supply a fake in tests. For tests checking that something happened \"within N seconds\", use a generous multiple of the expected duration (10x is not unreasonable for CI). `std::thread::sleep` in tests is a smell — prefer channel synchronization or a condvar instead of timing-based waits. If you must use sleep, set `KIMETSU_TEST_TIMEOUT_SCALE` to stretch timeouts in slow environments. (context: Kimetsu GC and TTL tests — time-dependent flakes on loaded CI.)" + } + ], + "delivered": [ + "gc-trace-env-guard-placement" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7750881910324097 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "git_init_boundary ProjectPaths::discover temp dir user brain isolation", + "relevant": [ + "init-project-git-boundary" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999781847000122, + "key": "init-project-git-boundary", + "rank_score": 0.9549992084503174, + "text": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain. (context: QQ3 — kimetsu setup integration test failed because init_project climbed git tree to real ~/.kimetsu instead of temp workspace)" + }, + { + "ce": 0.998727023601532, + "key": "git-worktree-brain-isolation", + "rank_score": 0.7565751075744629, + "text": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root — if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain. Always set `KIMETSU_BRAIN_DIR` or use `git_init_boundary` in tests to prevent this. (context: Kimetsu development with git worktrees — test isolation.)" + }, + { + "ce": 0.8577821850776672, + "key": "testing-temp-dirs-ci", + "rank_score": 0.6813378930091858, + "text": "project:fact - [tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure. On Windows, `env::temp_dir()` returns `C:\\Users\\\\AppData\\Local\\Temp` — ensure the test binary has write permissions there. Avoid using the workspace root as a temp dir — tests should never write to the source tree. (context: Kimetsu test infrastructure — temp directory discipline.)" + }, + { + "ce": 0.2842428386211395, + "key": "kimetsu-memory-scopes", + "rank_score": 0.6249890923500061, + "text": "project:fact - [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available — if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope. The `kimetsu_brain_record` MCP tool inherits the scope from the server's launch context. When running kimetsu-remote, all memories are project-scoped to the registered repo-id. (context: Kimetsu memory scope system — project vs user isolation.)" + }, + { + "ce": 0.034479882568120956, + "key": "mutex-deadlock-user-brain-disabled", + "rank_score": 0.45741742849349976, + "text": "project:fact - [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure — `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation. (context: New tests for Tier-1 perf work called test_env_lock().lock() inside with_user_brain_disabled closure, deadlocking all project::tests that ran after them in the same test binary.)" + }, + { + "ce": 0.028108032420277596, + "key": "harbor-terminal-bench-subprocess-isolation", + "rank_score": 0.41305962204933167, + "text": "project:fact - [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd). Worker re-derives auth internally from .env so the OAuth token never lands in argv; it writes {run,grade} JSON the parent reads back. One Harbor invocation per process always works (baseline-alone passed). (context: kbench multi-trial sweeps crashed on every trial after the 1st; diagnosed as Harbor/pyiceberg os.getcwd staleness on WSL2.)" + } + ], + "delivered": [ + "init-project-git-boundary", + "git-worktree-brain-isolation", + "testing-temp-dirs-ci" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7941226959228516 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "my test calls init_project but it writes to the real ~/.kimetsu instead of the temp folder — why?", + "relevant": [ + "init-project-git-boundary" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999724626541138, + "key": "init-project-git-boundary", + "rank_score": 0.9549991488456726, + "text": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain. (context: QQ3 — kimetsu setup integration test failed because init_project climbed git tree to real ~/.kimetsu instead of temp workspace)" + }, + { + "ce": 0.5625059604644775, + "key": "cargo-feature-unification-embeddings", + "rank_score": 0.48359671235084534, + "text": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli). Diagnostic tell: a test that passes alone but fails only under `cargo test --workspace` AND a brand-new crate was just added = suspect feature unification flipping a sibling crate's behavior. (context: Building the kimetsu-remote crate (HTTP MCP server); its default embeddings feature broke 3 kimetsu-chat retrieval tests only under the full workspace test.)" + }, + { + "ce": 0.0001039560665958561, + "key": "cargo-patch-section", + "rank_score": 0.4590173363685608, + "text": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace — including transitive deps — that depend on `my-crate`. Remove the patch before publishing. Using `[replace]` is deprecated since Cargo 0.47; always use `[patch]`. When patching a crate pinned via an exact version specifier, the patch must satisfy that exact version. Use `cargo tree` to confirm the patch is applied. (context: Kimetsu patching upstream rusqlite for a Windows-specific WAL fix.)" + }, + { + "ce": 0.00547712342813611, + "key": "testing-fixture-drift", + "rank_score": 0.4278464913368225, + "text": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code. For kimetsu, `EvalFixture::from_memories(memories)` constructs a dataset from the exported format — use it in tests instead of hardcoded JSON. Tag fixture files with the schema version they were generated against in a comment. (context: Kimetsu eval fixture drift after schema migration.)" + }, + { + "ce": 0.5564239621162415, + "key": "pi-openclaw-extension-api", + "rank_score": 0.4220048189163208, + "text": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`. External commands use `pi.exec()` but `node:child_process` spawn also works. Pi has NO MCP so Kimetsu integrates via TS extension + SKILL.md only. (context: Implementing Pi host target for Kimetsu plugin install/status/uninstall system.)\n\nAlso: [tags: kimetsu host-integration pi openclaw bridge] When integrating Kimetsu with an external host agent (Pi, OpenClaw, etc.), VERIFY the host's real plugin/extension API against its actual repo before writing embedded assets — docs-from-memory are frequently wrong. Concretely corrected during v1.0: Pi uses a default-export factory `export default function(pi)` (not `defineExtension`) with lifecycle events `session_start`/`agent_end`/`session_shutdown`; OpenClaw plugin entry is `index.ts` via `definePluginEntry` from `openclaw/plugin-sdk/plugin-entry` + an `openclaw.plugin.json` manifest, with snake_case hook events `agent_turn_prepare`/`agent_end`/`session_end` (NOT colon-delimited). Always make the embedded hook shell-out a silent no-op if the `kimetsu` binary isn't on PATH so a wrong guess never breaks the host. (context: Adding Pi + OpenClaw as BridgeTarget hosts in v1.0.0; the inferred extension/plugin APIs from docs were wrong and had to be corrected against the real repos.)" + }, + { + "ce": 0.15500210225582123, + "key": "tokio-runtime-in-tests", + "rank_score": 0.43937844038009644, + "text": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests. For sync test code that calls async, use `tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async { ... })`. Never call `block_on` from inside an async function. (context: Kimetsu remote integration tests — nested runtime panic.)" + } + ], + "delivered": [ + "init-project-git-boundary", + "cargo-feature-unification-embeddings", + "pi-openclaw-extension-api" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8255824446678162 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "clap command version KIMETSU_VERSION_DISPLAY cfg feature embeddings", + "relevant": [ + "clap-version-build-flavor" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999767541885376, + "key": "clap-version-build-flavor", + "rank_score": 0.9549991488456726, + "text": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds. (context: QQ2: --version build flavor + plugin install self-check)" + }, + { + "ce": 0.4735604524612427, + "key": "cargo-dev-dep-leak", + "rank_score": 0.522707998752594, + "text": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates. Run `cargo tree --features ` to trace which crate activated an unexpected feature. (context: Kimetsu testing infra — a dev-dep was activating the embeddings feature in non-test builds.)" + }, + { + "ce": 0.9207596778869629, + "key": "cargo-feature-unification-embeddings", + "rank_score": 0.4714054465293884, + "text": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli). Diagnostic tell: a test that passes alone but fails only under `cargo test --workspace` AND a brand-new crate was just added = suspect feature unification flipping a sibling crate's behavior. (context: Building the kimetsu-remote crate (HTTP MCP server); its default embeddings feature broke 3 kimetsu-chat retrieval tests only under the full workspace test.)" + }, + { + "ce": 0.002430071122944355, + "key": "cargo-msrv", + "rank_score": 0.4355095326900482, + "text": "project:fact - [tags: cargo rust msrv edition compatibility] Set `rust-version` in each `Cargo.toml` to declare the minimum supported Rust version (MSRV). Cargo enforces this with `--check`: `cargo check` fails if the toolchain is older than `rust-version`. Keep MSRV as old as your oldest supported deployment target. When bumping MSRV, update the CI matrix and the workspace root. Common trap: a transitive dep bumps its MSRV, pulling yours up silently — check with `cargo msrv` (cargo-msrv crate) or `cargo tree -e features | grep msrv`. Edition 2021 requires Rust >= 1.56.0. (context: Kimetsu workspace MSRV policy — ensuring it runs on the LTS toolchain.)" + }, + { + "ce": 0.014954467304050922, + "key": "git-reflog-rescue", + "rank_score": 0.40462547540664673, + "text": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone — they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only — remote reflog is not accessible via normal git commands. If you need the remote version, use `git fetch origin +refs/heads/main:refs/heads/main-backup` before a force push. In kimetsu bench development, always create a branch before destructive rebases. (context: Kimetsu bench dataset recovery after accidental hard reset.)" + }, + { + "ce": 0.034077808260917664, + "key": "cfg-cross-platform-dead-code", + "rank_score": 0.40491047501564026, + "text": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform. (context: Adding parse_unix_ps to kimetsu-cli/src/process.rs — used only on Unix at runtime but needed on Windows for cross-platform unit tests.)" + } + ], + "delivered": [ + "clap-version-build-flavor", + "cargo-feature-unification-embeddings", + "cargo-dev-dep-leak" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8659582734107971 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "how do I show the build flavor (lean vs embeddings) in the kimetsu --version output?", + "relevant": [ + "clap-version-build-flavor" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999544620513916, + "key": "clap-version-build-flavor", + "rank_score": 0.9549991488456726, + "text": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds. (context: QQ2: --version build flavor + plugin install self-check)" + }, + { + "ce": 0.8056868314743042, + "key": "cargo-feature-unification-embeddings", + "rank_score": 0.6331191062927246, + "text": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli). Diagnostic tell: a test that passes alone but fails only under `cargo test --workspace` AND a brand-new crate was just added = suspect feature unification flipping a sibling crate's behavior. (context: Building the kimetsu-remote crate (HTTP MCP server); its default embeddings feature broke 3 kimetsu-chat retrieval tests only under the full workspace test.)" + }, + { + "ce": 0.05154510959982872, + "key": "cargo-dev-dep-leak", + "rank_score": 0.6344510912895203, + "text": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates. Run `cargo tree --features ` to trace which crate activated an unexpected feature. (context: Kimetsu testing infra — a dev-dep was activating the embeddings feature in non-test builds.)" + }, + { + "ce": 0.061398327350616455, + "key": "onnx-quantization-drift", + "rank_score": 0.5330023169517517, + "text": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals — cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case. (context: Kimetsu embedding model selection — evaluating jina-v2 int8 vs fp32.)" + }, + { + "ce": 0.01268107071518898, + "key": "tokio-runtime-in-tests", + "rank_score": 0.5423588752746582, + "text": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests. For sync test code that calls async, use `tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async { ... })`. Never call `block_on` from inside an async function. (context: Kimetsu remote integration tests — nested runtime panic.)" + }, + { + "ce": 0.1120506003499031, + "key": "cargo-build-script-rerun", + "rank_score": 0.50947505235672, + "text": "project:fact - [tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory. If the build script generates code from a schema file, emit `rerun-if-changed=schema.json`. If there are NO inputs (e.g. the script only inspects env vars), emit `cargo:rerun-if-changed=` with an empty string to suppress re-runs entirely. Missing this directive is the most common cause of unexpectedly slow incremental builds. (context: kimetsu-cli build.rs for embedding version stamps.)" + } + ], + "delivered": [ + "clap-version-build-flavor", + "cargo-feature-unification-embeddings" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8347951769828796 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "Harbor pyiceberg os.getcwd stale WSL2 DrvFs worker-result subprocess re-exec", + "relevant": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999732971191406, + "key": "harbor-terminal-bench-subprocess-isolation", + "rank_score": 0.9549990892410278, + "text": "project:fact - [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd). Worker re-derives auth internally from .env so the OAuth token never lands in argv; it writes {run,grade} JSON the parent reads back. One Harbor invocation per process always works (baseline-alone passed). (context: kbench multi-trial sweeps crashed on every trial after the 1st; diagnosed as Harbor/pyiceberg os.getcwd staleness on WSL2.)" + }, + { + "ce": 0.00004506401455728337, + "key": "ci-cache-keys", + "rank_score": 0.3580111861228943, + "text": "project:fact - [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key — macOS and Windows have incompatible artifact formats. Separate the registry cache from the build cache: the registry (downloaded crates) changes rarely, the build cache changes every push. Bust the build cache on major dependency changes by adding a manual cache version suffix to the key. (context: Kimetsu CI — cache invalidation strategy.)" + }, + { + "ce": 0.00007715596439084038, + "key": "kimetsu-daemon-lifecycle", + "rank_score": 0.3418598175048828, + "text": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required. The server PID is not stored anywhere; use `kimetsu doctor` to enumerate running MCP server processes via OS APIs. On Windows, the server binary may be locked by AV after first launch — `kimetsu update` must stop all running server processes before replacing the binary. (context: Kimetsu daemon lifecycle — process management for updates.)" + }, + { + "ce": 0.00036219603498466313, + "key": "process-start-time-cross-platform", + "rank_score": 0.3394969701766968, + "text": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path). Keep the skew decision logic in a pure function `assess_mcp_skew(servers, binary_mtime, binary_path) -> Outcome` so it can be unit-tested without any live OS state. (context: Q3 — kimetsu doctor version-skew check for stale MCP server processes)" + }, + { + "ce": 0.00004042124783154577, + "key": "kimetsu-mrr-metric", + "rank_score": 0.3182431161403656, + "text": "project:fact - [tags: kimetsu bench mrr recall metrics evaluation] kimetsu bench reports MRR (Mean Reciprocal Rank) and Recall@K. MRR is 1/rank_of_first_relevant_result, averaged across cases; it penalizes models that rank the correct answer 2nd or 3rd. Recall@K is the fraction of cases where at least one relevant answer appears in the top K. For multi-answer cases, recall@K considers a case satisfied if ANY relevant key appears in top K. MRR is the primary metric for knowledge retrieval because users read the first result first. A 0.01 MRR difference on a 100-case dataset corresponds to about 1 case changing from rank-2 to rank-1. Noise of ~2-3 cases is expected run-to-run. (context: Kimetsu benchmark metric interpretation.)" + }, + { + "ce": 0.000038907837733859196, + "key": "sqlite-vacuum-wal-checkpoint", + "rank_score": 0.32162755727767944, + "text": "project:fact - [tags: rust sqlite vacuum rusqlite windows] When implementing SQLite VACUUM in rusqlite: VACUUM cannot run inside a transaction. rusqlite's Connection does not hold an implicit transaction, so `conn.execute_batch(\"VACUUM;\")` works directly. After VACUUM, run `PRAGMA wal_checkpoint(TRUNCATE);` before measuring file size — on Windows the WAL file can hold significant space that isn't reflected in the main db file until the checkpoint runs. (context: Implementing kimetsu brain compact (Q8) — SQLite VACUUM + WAL checkpoint for accurate post-compact file size.)" + } + ], + "delivered": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8388283252716064 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "why does my kbench sweep crash after the first trial with 'result.json missing' on WSL2?", + "relevant": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9996674060821533, + "key": "harbor-terminal-bench-subprocess-isolation", + "rank_score": 0.9549990892410278, + "text": "project:fact - [2026-09-05] [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd). Worker re-derives auth internally from .env so the OAuth token never lands in argv; it writes {run,grade} JSON the parent reads back. One Harbor invocation per process always works (baseline-alone passed). (context: kbench multi-trial sweeps crashed on every trial after the 1st; diagnosed as Harbor/pyiceberg os.getcwd staleness on WSL2.)" + }, + { + "ce": 0.000027429492547526024, + "key": "sqlite-page-size", + "rank_score": 0.40619152784347534, + "text": "project:fact - [2026-09-05] [tags: sqlite page_size performance rusqlite] SQLite's default page_size is 4096 bytes. For a write-heavy brain database with large BLOB payloads (embedding vectors), raising page_size to 16384 reduces fragmentation and improves sequential scan throughput. `PRAGMA page_size = 16384;` must be set BEFORE the first table is created — changing it on an existing database requires a VACUUM afterward to rebuild all pages. Verify it took effect with `PRAGMA page_size;` after VACUUM. rusqlite's `Connection::open` runs no implicit PRAGMA, so set this in the connection init path. (context: Tuning the kimetsu brain SQLite schema for embedding vector storage.)" + }, + { + "ce": 0.00002720293923630379, + "key": "cargo-patch-section", + "rank_score": 0.4386020004749298, + "text": "project:fact - [2026-09-05] [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace — including transitive deps — that depend on `my-crate`. Remove the patch before publishing. Using `[replace]` is deprecated since Cargo 0.47; always use `[patch]`. When patching a crate pinned via an exact version specifier, the patch must satisfy that exact version. Use `cargo tree` to confirm the patch is applied. (context: Kimetsu patching upstream rusqlite for a Windows-specific WAL fix.)" + }, + { + "ce": 0.00004037101098219864, + "key": "git-submodule-pinning", + "rank_score": 0.4312276542186737, + "text": "project:fact - [2026-09-05] [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip — this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version. If a submodule is the kimetsu-bench repo inside the main repo, pin the bench SHA after validating the dataset change. Use `git diff HEAD -- bench` to see the pinned SHA change before committing. (context: Kimetsu bench as a git submodule of the main repo.)" + }, + { + "ce": 0.00003080123133258894, + "key": "kimetsu-proactive-hooks", + "rank_score": 0.40572816133499146, + "text": "project:fact - [2026-09-05] [tags: kimetsu proactive hooks context injection] kimetsu's proactive context injection runs before each agent turn (pre-turn hook) and injects relevant memories into the system prompt prefix. The hook invocation adds latency to the first token: embedding inference + vector search + reranking + context formatting. On a cold start, this can be 1-3 seconds. The hook is optional — disable with `KIMETSU_PROACTIVE=0`. The semantic floor (min cosine similarity) filters noise capsules before injection; setting the floor too low injects irrelevant memories and wastes context window tokens. The proactive hook does NOT trigger the distiller — that runs post-session only. (context: Kimetsu proactive context injection — latency and floor tuning.)" + }, + { + "ce": 0.00021580554312095046, + "key": "kimetsu-mrr-metric", + "rank_score": 0.4683074355125427, + "text": "project:fact - [2026-09-05] [tags: kimetsu bench mrr recall metrics evaluation] kimetsu bench reports MRR (Mean Reciprocal Rank) and Recall@K. MRR is 1/rank_of_first_relevant_result, averaged across cases; it penalizes models that rank the correct answer 2nd or 3rd. Recall@K is the fraction of cases where at least one relevant answer appears in the top K. For multi-answer cases, recall@K considers a case satisfied if ANY relevant key appears in top K. MRR is the primary metric for knowledge retrieval because users read the first result first. A 0.01 MRR difference on a 100-case dataset corresponds to about 1 case changing from rank-2 to rank-1. Noise of ~2-3 cases is expected run-to-run. (context: Kimetsu benchmark metric interpretation.)" + } + ], + "delivered": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7928394675254822 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "rusqlite VACUUM transaction WAL checkpoint wal_checkpoint TRUNCATE", + "relevant": [ + "sqlite-vacuum-wal-checkpoint" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9998983144760132, + "key": "sqlite-vacuum-wal-checkpoint", + "rank_score": 0.9549990296363831, + "text": "project:fact - [tags: rust sqlite vacuum rusqlite windows] When implementing SQLite VACUUM in rusqlite: VACUUM cannot run inside a transaction. rusqlite's Connection does not hold an implicit transaction, so `conn.execute_batch(\"VACUUM;\")` works directly. After VACUUM, run `PRAGMA wal_checkpoint(TRUNCATE);` before measuring file size — on Windows the WAL file can hold significant space that isn't reflected in the main db file until the checkpoint runs. (context: Implementing kimetsu brain compact (Q8) — SQLite VACUUM + WAL checkpoint for accurate post-compact file size.)" + }, + { + "ce": 0.9661141633987427, + "key": "sqlite-busy-timeout-wal", + "rank_score": 0.5331977009773254, + "text": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch. Set the timeout before any transaction, not inside one — it is a connection-level property. (context: Kimetsu brain writer and reader processes sharing the same SQLite brain database.)" + }, + { + "ce": 0.032501306384801865, + "key": "sqlite-page-size", + "rank_score": 0.423660010099411, + "text": "project:fact - [tags: sqlite page_size performance rusqlite] SQLite's default page_size is 4096 bytes. For a write-heavy brain database with large BLOB payloads (embedding vectors), raising page_size to 16384 reduces fragmentation and improves sequential scan throughput. `PRAGMA page_size = 16384;` must be set BEFORE the first table is created — changing it on an existing database requires a VACUUM afterward to rebuild all pages. Verify it took effect with `PRAGMA page_size;` after VACUUM. rusqlite's `Connection::open` runs no implicit PRAGMA, so set this in the connection init path. (context: Tuning the kimetsu brain SQLite schema for embedding vector storage.)" + }, + { + "ce": 0.0003492985269986093, + "key": "kimetsu-capsule-budgets", + "rank_score": 0.3639942705631256, + "text": "project:fact - [tags: kimetsu capsule tokens budget retrieval] kimetsu retrieval enforces a token budget per capsule type: memory capsules are capped at 6000 tokens total (across all retrieved memories), file capsules at 3000 tokens. When a memory is large and would exceed the budget, it is truncated at a sentence boundary. The budget is enforced AFTER reranking — reranking may reorder results so that a truncated high-ranked memory displaces a full lower-ranked one. `noise_caps` in the bench output counts capsules that scored below the noise floor — they consume budget without contributing signal. Lower noise_caps = tighter retrieval. (context: Kimetsu capsule budget enforcement and noise floor interaction.)" + }, + { + "ce": 0.010361098684370518, + "key": "cargo-patch-section", + "rank_score": 0.35465389490127563, + "text": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace — including transitive deps — that depend on `my-crate`. Remove the patch before publishing. Using `[replace]` is deprecated since Cargo 0.47; always use `[patch]`. When patching a crate pinned via an exact version specifier, the patch must satisfy that exact version. Use `cargo tree` to confirm the patch is applied. (context: Kimetsu patching upstream rusqlite for a Windows-specific WAL fix.)" + }, + { + "ce": 0.0007475473103113472, + "key": "remote-ingest-split-roots", + "rank_score": 0.3212856948375702, + "text": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races. Re-enable kimetsu_brain_ingest_repo in the tool allowlist only when ingest is configured, and INTERCEPT that tools/call in the remote handler (clone+ingest_repo_at_root) before the normal dispatch (which would walk the wrong dir). Hermetic test: git init a temp repo, register url=local path, ingest, then context retrieves the file capsule via FTS (noop embedder). (context: R3c: server-side ingest for kimetsu-remote — cloning repos so file-capsule retrieval works without a local checkout.)" + } + ], + "delivered": [ + "sqlite-vacuum-wal-checkpoint", + "sqlite-busy-timeout-wal" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.827757716178894 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "my SQLite VACUUM reports the file shrank but the disk usage stayed the same — Windows WAL?", + "relevant": [ + "sqlite-vacuum-wal-checkpoint" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.4884326457977295, + "key": "sqlite-vacuum-wal-checkpoint", + "rank_score": 0.9549990296363831, + "text": "project:fact - [tags: rust sqlite vacuum rusqlite windows] When implementing SQLite VACUUM in rusqlite: VACUUM cannot run inside a transaction. rusqlite's Connection does not hold an implicit transaction, so `conn.execute_batch(\"VACUUM;\")` works directly. After VACUUM, run `PRAGMA wal_checkpoint(TRUNCATE);` before measuring file size — on Windows the WAL file can hold significant space that isn't reflected in the main db file until the checkpoint runs. (context: Implementing kimetsu brain compact (Q8) — SQLite VACUUM + WAL checkpoint for accurate post-compact file size.)" + }, + { + "ce": 0.00026769202668219805, + "key": "sqlite-partial-index", + "rank_score": 0.703747570514679, + "text": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query — the planner uses the partial index only when the WHERE clause matches. Verify index usage with `EXPLAIN QUERY PLAN SELECT ...`. Partial indexes are not supported before SQLite 3.8.0; rusqlite's bundled SQLite is always current, but system SQLite on old Debian/Ubuntu may not be. (context: Optimizing kimetsu brain retrieval query over the active-memories subset.)" + }, + { + "ce": 0.0010367996292188764, + "key": "sqlite-page-size", + "rank_score": 0.7230777144432068, + "text": "project:fact - [tags: sqlite page_size performance rusqlite] SQLite's default page_size is 4096 bytes. For a write-heavy brain database with large BLOB payloads (embedding vectors), raising page_size to 16384 reduces fragmentation and improves sequential scan throughput. `PRAGMA page_size = 16384;` must be set BEFORE the first table is created — changing it on an existing database requires a VACUUM afterward to rebuild all pages. Verify it took effect with `PRAGMA page_size;` after VACUUM. rusqlite's `Connection::open` runs no implicit PRAGMA, so set this in the connection init path. (context: Tuning the kimetsu brain SQLite schema for embedding vector storage.)" + }, + { + "ce": 0.0005428269505500793, + "key": "git-sparse-checkout", + "rank_score": 0.6563814878463745, + "text": "project:fact - [tags: git sparse-checkout partial-clone bandwidth] `git sparse-checkout init --cone` combined with `git clone --filter=blob:none` (partial clone) fetches only the commit graph and tree objects, not blobs. Individual blobs are fetched on demand when accessed. This cuts clone time for large repos from minutes to seconds. For kimetsu server-side ingest, use `git clone --depth 1 --filter=blob:none` for the initial checkout, then `git sparse-checkout set ` to limit the working tree to indexed directories. On `git fetch --depth 1 origin main` for refresh, blobs in the sparse set are updated lazily. (context: Kimetsu remote ingest — reducing bandwidth and disk usage for large repo checkouts.)" + }, + { + "ce": 0.003202510764822364, + "key": "windows-junctions-vs-symlinks", + "rank_score": 0.6432731747627258, + "text": "project:fact - [tags: windows junctions symlinks rust std::fs] On Windows, directory junctions (NTFS reparse points) behave like symlinks for directory traversal but `std::fs::symlink_metadata` returns `FileType::is_symlink() = false` for junctions (only true for regular symlinks). Use `std::fs::read_link` — it succeeds for both junction and symlink. `walkdir` crate's `follow_links` follows both, but its `is_symlink()` method correctly reports only actual symlinks. Creating symlinks requires SeCreateSymbolicLinkPrivilege (admin or Developer Mode). Creating junctions requires no special privilege. Use junctions for internal tooling that doesn't need to cross volumes. (context: Kimetsu path handling for brain symlink detection on Windows.)" + }, + { + "ce": 0.4921550154685974, + "key": "sqlite-wal-network-drive", + "rank_score": 0.6431951522827148, + "text": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db. Fallback: `PRAGMA journal_mode=DELETE;` is safe over SMB at the cost of lower concurrency. Detect network drives at startup with `GetFileAttributes` checking FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS or using `PathIsNetworkPath`. (context: Users running kimetsu with the brain database on a mapped network drive.)" + } + ], + "delivered": [ + "sqlite-wal-network-drive", + "sqlite-vacuum-wal-checkpoint" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7539297938346863 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "add_memory import dedup seen_ids snapshot pre-existing active memory IDs", + "relevant": [ + "import-dedup-seen-ids" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999771118164062, + "key": "import-dedup-seen-ids", + "rank_score": 0.9549989700317383, + "text": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount — both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise. (context: Implementing brain export/import (Q5). First naive approach used a single `seen_ids` set local to the function; the dedup test caught it on the second-import assertion.)" + }, + { + "ce": 0.0015473555540665984, + "key": "testing-snapshot-churn", + "rank_score": 0.44061025977134705, + "text": "project:fact - [tags: testing snapshot insta assert churn rust] Snapshot tests (e.g. with the `insta` crate) fail whenever the output changes, even for intended changes. In CI, they fail loudly; locally, `cargo insta review` walks you through accepting or rejecting changes. Snapshot churn becomes a problem when output includes timestamps, process IDs, or randomly-ordered maps. Redact these before snapshotting: use `insta::with_settings!({redactions: [\".timestamp\" => \"[TIMESTAMP]\"]})`. For JSON output, sort maps and arrays before comparing. Keep snapshot files in `src/snapshots/` and always commit them — an untracked snapshot file causes the next CI run to fail with a different error than expected. (context: Kimetsu CLI output snapshot tests — reducing churn.)" + }, + { + "ce": 0.0002443075645714998, + "key": "kimetsu-eval-fixture-shape", + "rank_score": 0.3528152406215668, + "text": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` — a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases). Keys must be unique across the dataset. The bench currently does not validate keys at load — it fails later with an `unwrap()` on a missing HashMap entry. (context: Kimetsu bench dataset shape and validation.)" + }, + { + "ce": 0.00012979446910321712, + "key": "testing-property-tests", + "rank_score": 0.36208072304725647, + "text": "project:fact - [tags: testing property-based proptest quickcheck rust] Property-based tests (proptest, quickcheck) find edge cases that example-based tests miss. For kimetsu's memory text normalization, proptest found that zero-width joiner characters and right-to-left marks caused hash collisions. Run proptest with `PROPTEST_CASES=10000` in CI for thorough coverage. Shrinking: when proptest finds a failure, it automatically shrinks the input to the minimal failing case — read the `Minimized failure` output, not the original random input. Use `prop_assume!` to skip inputs that violate preconditions rather than `if/return`. (context: Kimetsu brain text normalization — property test for dedup hash stability.)" + }, + { + "ce": 0.00008742959471419454, + "key": "sqlite-partial-index", + "rank_score": 0.3471319377422333, + "text": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query — the planner uses the partial index only when the WHERE clause matches. Verify index usage with `EXPLAIN QUERY PLAN SELECT ...`. Partial indexes are not supported before SQLite 3.8.0; rusqlite's bundled SQLite is always current, but system SQLite on old Debian/Ubuntu may not be. (context: Optimizing kimetsu brain retrieval query over the active-memories subset.)" + }, + { + "ce": 0.0006524308701045811, + "key": "harbor-terminal-bench-subprocess-isolation", + "rank_score": 0.3355048894882202, + "text": "project:fact - [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd). Worker re-derives auth internally from .env so the OAuth token never lands in argv; it writes {run,grade} JSON the parent reads back. One Harbor invocation per process always works (baseline-alone passed). (context: kbench multi-trial sweeps crashed on every trial after the 1st; diagnosed as Harbor/pyiceberg os.getcwd staleness on WSL2.)" + } + ], + "delivered": [ + "import-dedup-seen-ids" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8523082733154297 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "brain import re-imports the same JSON file but the deduplication counter is wrong — why?", + "relevant": [ + "import-dedup-seen-ids" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.7890511751174927, + "key": "import-dedup-seen-ids", + "rank_score": 0.9549989700317383, + "text": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount — both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise. (context: Implementing brain export/import (Q5). First naive approach used a single `seen_ids` set local to the function; the dedup test caught it on the second-import assertion.)" + }, + { + "ce": 0.09554054588079453, + "key": "harbor-terminal-bench-subprocess-isolation", + "rank_score": 0.8415305018424988, + "text": "project:fact - [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd). Worker re-derives auth internally from .env so the OAuth token never lands in argv; it writes {run,grade} JSON the parent reads back. One Harbor invocation per process always works (baseline-alone passed). (context: kbench multi-trial sweeps crashed on every trial after the 1st; diagnosed as Harbor/pyiceberg os.getcwd staleness on WSL2.)" + }, + { + "ce": 0.00006890860095154494, + "key": "http-proxy-env", + "rank_score": 0.63133305311203, + "text": "project:fact - [tags: http proxy environment reqwest rust corporate] reqwest respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` environment variables by default (with `default-tls` or `rustls-tls`). In a corporate network, these may redirect traffic through an intercepting proxy that breaks mTLS or adds latency. To disable proxy usage entirely: `reqwest::ClientBuilder::no_proxy()`. On Windows, reqwest does NOT use the system proxy settings (IE/WinInet) — you must set env vars explicitly. `NO_PROXY=127.0.0.1,localhost` prevents proxying loopback traffic (important for kimetsu-remote local dev). (context: Kimetsu provider calls failing behind corporate proxy on Windows.)" + }, + { + "ce": 0.004553746432065964, + "key": "testing-fixture-drift", + "rank_score": 0.5917391777038574, + "text": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code. For kimetsu, `EvalFixture::from_memories(memories)` constructs a dataset from the exported format — use it in tests instead of hardcoded JSON. Tag fixture files with the schema version they were generated against in a comment. (context: Kimetsu eval fixture drift after schema migration.)" + }, + { + "ce": 0.0005994781386107206, + "key": "mcp-tool-naming", + "rank_score": 0.5855820775032043, + "text": "project:fact - [tags: mcp tool naming convention kimetsu] MCP tool names must be valid identifiers for all host agents. Claude Code restricts tool names to `[a-zA-Z0-9_-]` and max 64 chars. Use `snake_case` (kimetsu_brain_context, kimetsu_brain_record) — hyphen is technically allowed but some hosts reject it. Avoid dots (not allowed). Namespace with a prefix (`kimetsu_brain_`) to prevent collisions with other MCP servers. When a tool name changes, update ALL host config files (`.mcp.json`, `openclaw.json`, skill markdown) — mismatched names cause silent failures where the host skips the tool. (context: Kimetsu MCP tool naming convention enforcement.)" + }, + { + "ce": 0.0001677317195571959, + "key": "sqlite-wal-network-drive", + "rank_score": 0.5797498226165771, + "text": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db. Fallback: `PRAGMA journal_mode=DELETE;` is safe over SMB at the cost of lower concurrency. Detect network drives at startup with `GetFileAttributes` checking FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS or using `PathIsNetworkPath`. (context: Users running kimetsu with the brain database on a mapped network drive.)" + } + ], + "delivered": [ + "import-dedup-seen-ids" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8221303820610046 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "toml::from_str Value parse document unexpected content str.parse", + "relevant": [ + "toml-value-parse" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9994491934776306, + "key": "toml-value-parse", + "rank_score": 0.9549989700317383, + "text": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table. (context: Implementing config get/set with toml::Value navigation; str.parse() failed with 'unexpected content' error on document strings.)" + }, + { + "ce": 0.0030436881352216005, + "key": "sqlite-prepared-stmt-cache", + "rank_score": 0.4520691931247711, + "text": "project:fact - [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8). The cache key is the SQL string verbatim, so template strings with interpolated values defeat caching — use `?1, ?2` placeholders instead. Calling `prepare_cached` in a tight loop is effectively free after warmup. (context: Kimetsu brain high-throughput ingest path — replacing prepare() with prepare_cached() cut ingest time by ~30%.)" + }, + { + "ce": 0.0003617967595346272, + "key": "process-start-time-cross-platform", + "rank_score": 0.4080475866794586, + "text": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path). Keep the skew decision logic in a pure function `assess_mcp_skew(servers, binary_mtime, binary_path) -> Outcome` so it can be unit-tested without any live OS state. (context: Q3 — kimetsu doctor version-skew check for stale MCP server processes)" + }, + { + "ce": 0.0003556387673597783, + "key": "mcp-stdout-protocol", + "rank_score": 0.4190349280834198, + "text": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr. `println!` in the request handler is forbidden. Use `eprintln!` or `tracing` with a stderr subscriber. In tests of the MCP server, capture stdout as bytes and validate it parses as JSON-Lines. When debugging, set `KIMETSU_LOG=debug` which writes to stderr only. (context: Kimetsu MCP server stdout protocol hygiene.)" + }, + { + "ce": 0.046183276921510696, + "key": "http-streaming-bodies", + "rank_score": 0.3870428204536438, + "text": "project:fact - [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding — a chunk may split across frame boundaries. In kimetsu's proxy path, accumulate bytes until `\n\n` (SSE frame delimiter) before parsing the JSON data field. Never assume one `.chunk()` call = one SSE event. (context: Kimetsu remote proxy — streaming LLM responses to the client.)" + }, + { + "ce": 0.0003075034765060991, + "key": "cfg-cross-platform-dead-code", + "rank_score": 0.41163069009780884, + "text": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform. (context: Adding parse_unix_ps to kimetsu-cli/src/process.rs — used only on Unix at runtime but needed on Windows for cross-platform unit tests.)" + } + ], + "delivered": [ + "toml-value-parse" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8470227122306824 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "how do I parse a TOML configuration file into a toml::Value in toml 0.9?", + "relevant": [ + "toml-value-parse" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999773502349854, + "key": "toml-value-parse", + "rank_score": 0.9549989700317383, + "text": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table. (context: Implementing config get/set with toml::Value navigation; str.parse() failed with 'unexpected content' error on document strings.)" + }, + { + "ce": 0.005411595106124878, + "key": "cargo-dev-dep-leak", + "rank_score": 0.6006919145584106, + "text": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates. Run `cargo tree --features ` to trace which crate activated an unexpected feature. (context: Kimetsu testing infra — a dev-dep was activating the embeddings feature in non-test builds.)" + }, + { + "ce": 0.002031307900324464, + "key": "testing-fixture-drift", + "rank_score": 0.5112535357475281, + "text": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code. For kimetsu, `EvalFixture::from_memories(memories)` constructs a dataset from the exported format — use it in tests instead of hardcoded JSON. Tag fixture files with the schema version they were generated against in a comment. (context: Kimetsu eval fixture drift after schema migration.)" + }, + { + "ce": 0.0026611266657710075, + "key": "cargo-target-dir-sharing", + "rank_score": 0.48985931277275085, + "text": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps — use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination. (context: Kimetsu development on Windows with Windows Defender causing intermittent link failures.)" + }, + { + "ce": 0.0036140568554401398, + "key": "cargo-profile-override", + "rank_score": 0.47126033902168274, + "text": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug. The downside: rebuild time increases for that crate. For overflow-checks, `overflow-checks = false` per package speeds up hot loops. Never disable overflow-checks in release for business-critical data-mutating code. `[profile.release] strip = \"debuginfo\"` reduces binary size with minimal impact on stack traces. (context: Kimetsu dev experience — embedding inference was 10x slower in debug builds.)" + }, + { + "ce": 0.0042360564693808556, + "key": "cargo-msrv", + "rank_score": 0.4585849642753601, + "text": "project:fact - [tags: cargo rust msrv edition compatibility] Set `rust-version` in each `Cargo.toml` to declare the minimum supported Rust version (MSRV). Cargo enforces this with `--check`: `cargo check` fails if the toolchain is older than `rust-version`. Keep MSRV as old as your oldest supported deployment target. When bumping MSRV, update the CI matrix and the workspace root. Common trap: a transitive dep bumps its MSRV, pulling yours up silently — check with `cargo msrv` (cargo-msrv crate) or `cargo tree -e features | grep msrv`. Edition 2021 requires Rust >= 1.56.0. (context: Kimetsu workspace MSRV policy — ensuring it runs on the LTS toolchain.)" + } + ], + "delivered": [ + "toml-value-parse" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.814325213432312 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "CIM CreationDate DMTF WMI ps etimes started_at assess_mcp_skew", + "relevant": [ + "process-start-time-cross-platform" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999735355377197, + "key": "process-start-time-cross-platform", + "rank_score": 0.9549989104270935, + "text": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path). Keep the skew decision logic in a pure function `assess_mcp_skew(servers, binary_mtime, binary_path) -> Outcome` so it can be unit-tested without any live OS state. (context: Q3 — kimetsu doctor version-skew check for stale MCP server processes)" + }, + { + "ce": 0.000027058513296651654, + "key": "cfg-cross-platform-dead-code", + "rank_score": 0.3810756802558899, + "text": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform. (context: Adding parse_unix_ps to kimetsu-cli/src/process.rs — used only on Unix at runtime but needed on Windows for cross-platform unit tests.)" + }, + { + "ce": 0.000027568430596147664, + "key": "sqlite-foreign-keys-default-off", + "rank_score": 0.2715265154838562, + "text": "project:fact - [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting — every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing. Check your schema with `PRAGMA foreign_key_list(table_name);` and your current setting with `PRAGMA foreign_keys;`. rusqlite does not enable foreign keys automatically. (context: Kimetsu brain schema — memory_tags table has FK to memories table, discovered ON DELETE CASCADE wasn't firing.)" + }, + { + "ce": 0.0005277942400425673, + "key": "harbor-terminal-bench-subprocess-isolation", + "rank_score": 0.27419427037239075, + "text": "project:fact - [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd). Worker re-derives auth internally from .env so the OAuth token never lands in argv; it writes {run,grade} JSON the parent reads back. One Harbor invocation per process always works (baseline-alone passed). (context: kbench multi-trial sweeps crashed on every trial after the 1st; diagnosed as Harbor/pyiceberg os.getcwd staleness on WSL2.)" + }, + { + "ce": 0.00003400736386538483, + "key": "onnx-tokenizer-mismatch", + "rank_score": 0.27147242426872253, + "text": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly — specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings — cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo. Validate by checking a reference embedding against the HuggingFace Python output. (context: Kimetsu custom ONNX reranker loading — wrong tokenizer produced degraded retrieval.)" + }, + { + "ce": 0.0000439775685663335, + "key": "kimetsu-rerank-pool", + "rank_score": 0.27146533131599426, + "text": "project:fact - [tags: kimetsu reranker pool size ann retrieval] kimetsu's retrieval pipeline: ANN (approximate nearest neighbor) retrieves a pool of candidates, then the reranker reorders them, then the top-K are returned. The pool size (default 6 for production, 12 in bench) controls the recall-latency tradeoff: larger pool = higher recall = more reranker calls = more latency. For the jina-tiny reranker, pool 12 adds ~80ms vs pool 6. The bench uses pool 12 to maximize measurable recall differences between rerankers; production uses pool 6 for latency. Increasing pool size beyond 20 has diminishing recall returns on corpora < 1000 memories. (context: Kimetsu ANN pool size tuning for the retrieval benchmark.)" + } + ], + "delivered": [ + "process-start-time-cross-platform" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7988084554672241 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "how do I read a process start time on both Windows and Linux in pure Rust?", + "relevant": [ + "process-start-time-cross-platform" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9942069053649902, + "key": "process-start-time-cross-platform", + "rank_score": 0.9549989104270935, + "text": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path). Keep the skew decision logic in a pure function `assess_mcp_skew(servers, binary_mtime, binary_path) -> Outcome` so it can be unit-tested without any live OS state. (context: Q3 — kimetsu doctor version-skew check for stale MCP server processes)" + }, + { + "ce": 0.0052744243294000626, + "key": "windows-junctions-vs-symlinks", + "rank_score": 0.9085209965705872, + "text": "project:fact - [tags: windows junctions symlinks rust std::fs] On Windows, directory junctions (NTFS reparse points) behave like symlinks for directory traversal but `std::fs::symlink_metadata` returns `FileType::is_symlink() = false` for junctions (only true for regular symlinks). Use `std::fs::read_link` — it succeeds for both junction and symlink. `walkdir` crate's `follow_links` follows both, but its `is_symlink()` method correctly reports only actual symlinks. Creating symlinks requires SeCreateSymbolicLinkPrivilege (admin or Developer Mode). Creating junctions requires no special privilege. Use junctions for internal tooling that doesn't need to cross volumes. (context: Kimetsu path handling for brain symlink detection on Windows.)" + }, + { + "ce": 0.12949159741401672, + "key": "http-timeout-layering", + "rank_score": 0.897041380405426, + "text": "project:fact - [tags: http reqwest timeout connect read total rust] reqwest has three distinct timeout knobs: `connect_timeout`, `read_timeout`, and `timeout` (total). They compose: if all three are set, the request fails at whichever fires first. For LLM API calls with streaming responses, `read_timeout` must be larger than the slowest expected token (often 30-60s) while `connect_timeout` can be tight (3-5s). `timeout` should be your SLA ceiling. If you set only `timeout`, a slow connect eats into the overall budget. For kimetsu-remote, set both `connect_timeout(5s)` and `timeout(120s)` — the LLM call is the bottleneck. (context: Kimetsu provider timeouts — request timing out during streaming.)" + }, + { + "ce": 0.00020636460976675153, + "key": "kimetsu-daemon-lifecycle", + "rank_score": 0.8117845058441162, + "text": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required. The server PID is not stored anywhere; use `kimetsu doctor` to enumerate running MCP server processes via OS APIs. On Windows, the server binary may be locked by AV after first launch — `kimetsu update` must stop all running server processes before replacing the binary. (context: Kimetsu daemon lifecycle — process management for updates.)" + }, + { + "ce": 0.0044531566090881824, + "key": "sqlite-busy-timeout-wal", + "rank_score": 0.7856581807136536, + "text": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch. Set the timeout before any transaction, not inside one — it is a connection-level property. (context: Kimetsu brain writer and reader processes sharing the same SQLite brain database.)" + }, + { + "ce": 0.00011432389146648347, + "key": "onnx-prefix-instructions", + "rank_score": 0.7728502750396729, + "text": "project:fact - [tags: onnx embeddings prefix instruction e5 query passage] E5 and Instructor family models require a text prefix on BOTH query and passage sides to produce meaningful similarities: query prefix `\"query: \"`, passage prefix `\"passage: \"`. Omitting the prefix can drop MRR by 10-15 percentage points on out-of-domain datasets. Check the model's README for the exact prefix string — it varies by model family. In kimetsu, the embedder abstraction has `query_prefix` and `passage_prefix` fields; FallbackEmbedder uses `\"\"` for both. jina-v2-base-code and bge-small use `\"\"` prefixes. (context: Kimetsu embedder trait design — prefix handling for E5/Instructor models.)" + } + ], + "delivered": [ + "process-start-time-cross-platform" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8354871273040771 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "processes_locking_target decide_preflight_action BufRead Write update.rs", + "relevant": [ + "windows-update-process-locking" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.999950647354126, + "key": "windows-update-process-locking", + "rank_score": 0.9549989104270935, + "text": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics — mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code. (context: Q2 — kimetsu update preflight for locked binary on Windows)" + }, + { + "ce": 0.00040877447463572025, + "key": "cargo-build-script-rerun", + "rank_score": 0.43048787117004395, + "text": "project:fact - [tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory. If the build script generates code from a schema file, emit `rerun-if-changed=schema.json`. If there are NO inputs (e.g. the script only inspects env vars), emit `cargo:rerun-if-changed=` with an empty string to suppress re-runs entirely. Missing this directive is the most common cause of unexpectedly slow incremental builds. (context: kimetsu-cli build.rs for embedding version stamps.)" + }, + { + "ce": 0.0011444978881627321, + "key": "clap-version-build-flavor", + "rank_score": 0.4473218321800232, + "text": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds. (context: QQ2: --version build flavor + plugin install self-check)" + }, + { + "ce": 0.0007213042699731886, + "key": "bridge-target-enum-seams", + "rank_score": 0.41817259788513184, + "text": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors. (context: Adding BridgeTarget::OpenClaw host to Kimetsu bridge.rs and main.rs in Workstream C)" + }, + { + "ce": 0.0021272869780659676, + "key": "process-start-time-cross-platform", + "rank_score": 0.40986382961273193, + "text": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path). Keep the skew decision logic in a pure function `assess_mcp_skew(servers, binary_mtime, binary_path) -> Outcome` so it can be unit-tested without any live OS state. (context: Q3 — kimetsu doctor version-skew check for stale MCP server processes)" + }, + { + "ce": 0.0001548184227431193, + "key": "git-submodule-pinning", + "rank_score": 0.38208433985710144, + "text": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip — this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version. If a submodule is the kimetsu-bench repo inside the main repo, pin the bench SHA after validating the dataset change. Use `git diff HEAD -- bench` to see the pinned SHA change before committing. (context: Kimetsu bench as a git submodule of the main repo.)" + } + ], + "delivered": [ + "windows-update-process-locking" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7722512483596802 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "how should I reuse the existing process enumerator in the update preflight check to avoid a second PowerShell query?", + "relevant": [ + "windows-update-process-locking" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999661445617676, + "key": "windows-update-process-locking", + "rank_score": 0.9549989104270935, + "text": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics — mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code. (context: Q2 — kimetsu update preflight for locked binary on Windows)" + }, + { + "ce": 0.00006651978037552908, + "key": "cargo-dev-dep-leak", + "rank_score": 0.5017357468605042, + "text": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates. Run `cargo tree --features ` to trace which crate activated an unexpected feature. (context: Kimetsu testing infra — a dev-dep was activating the embeddings feature in non-test builds.)" + }, + { + "ce": 0.00010976063640555367, + "key": "windows-exit-codes", + "rank_score": 0.4832920730113983, + "text": "project:fact - [tags: windows exit-codes rust process child] On Windows, process exit codes are 32-bit unsigned integers (DWORD). Rust's `ExitStatus::code()` returns `Option` — it's `None` if the process was killed by a signal (which Windows doesn't use; instead, TerminateProcess with a code). Conventional codes: 0=success, 1=generic error, 0xC0000005=access violation. Programs that call `std::process::exit(-1)` on Windows produce exit code 0xFFFFFFFF (4294967295), not -1. When checking for success in a subprocess chain, always check `status.success()` rather than `status.code() == Some(0)` to handle this portably. (context: Kimetsu update binary replacement — exit code handling.)" + }, + { + "ce": 0.00005092794162919745, + "key": "kimetsu-daemon-lifecycle", + "rank_score": 0.4587433934211731, + "text": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required. The server PID is not stored anywhere; use `kimetsu doctor` to enumerate running MCP server processes via OS APIs. On Windows, the server binary may be locked by AV after first launch — `kimetsu update` must stop all running server processes before replacing the binary. (context: Kimetsu daemon lifecycle — process management for updates.)" + }, + { + "ce": 0.00005086353121441789, + "key": "cargo-lockfile-drift", + "rank_score": 0.46771058440208435, + "text": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this — it errors on any lockfile diff. For library crates, `Cargo.lock` is normally gitignored, but for workspace roots with binary crates it should be committed. Use `cargo update --precise ` to pin a specific dep version without touching unrelated entries. (context: Kimetsu workspace lockfile drift after adding kimetsu-remote crate.)" + }, + { + "ce": 0.00009334875358035788, + "key": "http-connection-pooling", + "rank_score": 0.41246455907821655, + "text": "project:fact - [tags: http reqwest connection-pool keep-alive rust] reqwest's `Client` holds a connection pool; always create ONE `Client` instance and clone it for each handler — cloning is cheap (Arc under the hood). Creating a `Client::new()` per request defeats connection pooling and causes TCP connection exhaustion under load. The default pool settings: max_idle_per_host=usize::MAX (unbounded), idle_timeout=90s. For a kimetsu outbound client (LLM provider), set `pool_max_idle_per_host(5)` to limit idle connections. On Windows, the underlying hyper+winapi stack may not reuse connections as aggressively as on Linux — set `connection_verbose(true)` on the builder to confirm reuse. (context: Kimetsu provider HTTP client — connection pooling best practices.)" + } + ], + "delivered": [ + "windows-update-process-locking" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8066720962524414 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "cfg_attr windows allow dead_code parse_unix_ps cross-platform tests", + "relevant": [ + "cfg-cross-platform-dead-code" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999802112579346, + "key": "cfg-cross-platform-dead-code", + "rank_score": 0.9549988508224487, + "text": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform. (context: Adding parse_unix_ps to kimetsu-cli/src/process.rs — used only on Unix at runtime but needed on Windows for cross-platform unit tests.)" + }, + { + "ce": 0.7637738585472107, + "key": "windows-update-process-locking", + "rank_score": 0.5709377527236938, + "text": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics — mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code. (context: Q2 — kimetsu update preflight for locked binary on Windows)" + }, + { + "ce": 0.8274164795875549, + "key": "process-start-time-cross-platform", + "rank_score": 0.5003188848495483, + "text": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path). Keep the skew decision logic in a pure function `assess_mcp_skew(servers, binary_mtime, binary_path) -> Outcome` so it can be unit-tested without any live OS state. (context: Q3 — kimetsu doctor version-skew check for stale MCP server processes)" + }, + { + "ce": 0.00149176933337003, + "key": "ci-flaky-quarantine", + "rank_score": 0.39145737886428833, + "text": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal — a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output. NEVER let a flaky test gate the merge queue. For kimetsu timing-based tests (`test_gc_old_runs_deletes_ancient`), apply `#[cfg_attr(ci, ignore)]` and run only in a dedicated slow-CI job. (context: Kimetsu CI flaky test policy.)" + }, + { + "ce": 0.00004223512951284647, + "key": "git-submodule-pinning", + "rank_score": 0.36482763290405273, + "text": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip — this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version. If a submodule is the kimetsu-bench repo inside the main repo, pin the bench SHA after validating the dataset change. Use `git diff HEAD -- bench` to see the pinned SHA change before committing. (context: Kimetsu bench as a git submodule of the main repo.)" + }, + { + "ce": 0.00032150335027836263, + "key": "windows-junctions-vs-symlinks", + "rank_score": 0.36630702018737793, + "text": "project:fact - [tags: windows junctions symlinks rust std::fs] On Windows, directory junctions (NTFS reparse points) behave like symlinks for directory traversal but `std::fs::symlink_metadata` returns `FileType::is_symlink() = false` for junctions (only true for regular symlinks). Use `std::fs::read_link` — it succeeds for both junction and symlink. `walkdir` crate's `follow_links` follows both, but its `is_symlink()` method correctly reports only actual symlinks. Creating symlinks requires SeCreateSymbolicLinkPrivilege (admin or Developer Mode). Creating junctions requires no special privilege. Use junctions for internal tooling that doesn't need to cross volumes. (context: Kimetsu path handling for brain symlink detection on Windows.)" + } + ], + "delivered": [ + "cfg-cross-platform-dead-code", + "process-start-time-cross-platform", + "windows-update-process-locking" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8830417394638062 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "how do I keep a function that is only called on Unix from triggering dead_code warnings on Windows?", + "relevant": [ + "cfg-cross-platform-dead-code" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9998409748077393, + "key": "cfg-cross-platform-dead-code", + "rank_score": 0.9549988508224487, + "text": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform. (context: Adding parse_unix_ps to kimetsu-cli/src/process.rs — used only on Unix at runtime but needed on Windows for cross-platform unit tests.)" + }, + { + "ce": 0.0071021453477442265, + "key": "gc-trace-env-guard-placement", + "rank_score": 0.8291110992431641, + "text": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site. (context: QQ4 — runs auto-GC on run creation. Env guard placement decision when wiring opportunistic GC into TraceWriter::create.)" + }, + { + "ce": 0.010728728026151657, + "key": "process-start-time-cross-platform", + "rank_score": 0.8298085927963257, + "text": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path). Keep the skew decision logic in a pure function `assess_mcp_skew(servers, binary_mtime, binary_path) -> Outcome` so it can be unit-tested without any live OS state. (context: Q3 — kimetsu doctor version-skew check for stale MCP server processes)" + }, + { + "ce": 0.0001345074560958892, + "key": "mcp-tool-timeouts", + "rank_score": 0.6690194010734558, + "text": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking — in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize — keep it in a process-global `OnceLock`). The reranker adds another 200-800ms; jina-tiny is the fastest. If tool calls are still slow, log the per-stage latency with `tracing::info!` at DEBUG level and profile under load. (context: Kimetsu MCP tool latency optimization.)" + }, + { + "ce": 0.00007907625695224851, + "key": "tokio-spawn-blocking", + "rank_score": 0.604327380657196, + "text": "project:fact - [tags: tokio spawn_blocking thread-pool rust blocking] `tokio::task::spawn_blocking` places work on a dedicated blocking thread pool (default up to 512 threads, configurable via `Builder::max_blocking_threads`). Each call creates or reuses a thread — there's no true pooling, threads may be created on demand. For many short-duration blocking calls (e.g. per-query SQLite reads), thread creation overhead may dominate. Prefer batching: collect N queries, then one `spawn_blocking` to run them all. Alternatively, keep a persistent blocking task that reads from an mpsc channel. Profile with `tokio-console` if you suspect spawn_blocking overhead. (context: Kimetsu retrieval server — per-query spawn_blocking was adding ~0.3ms overhead.)" + }, + { + "ce": 0.002689760411158204, + "key": "tokio-runtime-in-tests", + "rank_score": 0.6130164265632629, + "text": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests. For sync test code that calls async, use `tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async { ... })`. Never call `block_on` from inside an async function. (context: Kimetsu remote integration tests — nested runtime panic.)" + } + ], + "delivered": [ + "cfg-cross-platform-dead-code" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7780129909515381 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "deadlocking a Rust mutex in integration tests", + "relevant": [ + "mutex-deadlock-user-brain-disabled" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9991264939308167, + "key": "mutex-deadlock-user-brain-disabled", + "rank_score": 0.9549987316131592, + "text": "project:fact - [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure — `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation. (context: New tests for Tier-1 perf work called test_env_lock().lock() inside with_user_brain_disabled closure, deadlocking all project::tests that ran after them in the same test binary.)" + }, + { + "ce": 0.020306354388594627, + "key": "kimetsu-query-stemming", + "rank_score": 0.5628361701965332, + "text": "project:fact - [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression. The lexical floor (`min_lexical_coverage`) requires at least N stemmed query tokens to match in any retrieved document — this prevents high-semantic-score but lexically-unrelated documents from dominating. Stemming is applied only when the query has >= 3 tokens; short queries skip it. (context: Kimetsu retrieval — query-side stemming implementation.)" + }, + { + "ce": 0.0002811325539369136, + "key": "init-project-git-boundary", + "rank_score": 0.5654513835906982, + "text": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain. (context: QQ3 — kimetsu setup integration test failed because init_project climbed git tree to real ~/.kimetsu instead of temp workspace)" + }, + { + "ce": 0.04221879318356514, + "key": "tokio-runtime-in-tests", + "rank_score": 0.5656959414482117, + "text": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests. For sync test code that calls async, use `tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async { ... })`. Never call `block_on` from inside an async function. (context: Kimetsu remote integration tests — nested runtime panic.)" + }, + { + "ce": 0.2648780643939972, + "key": "testing-serial-vs-parallel", + "rank_score": 0.5745396018028259, + "text": "project:fact - [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`). `cargo nextest` runs each test in a separate process by default, avoiding the problem entirely at the cost of longer startup time. For kimetsu, prefer nextest in CI and accept that `test_env_lock` exists only for `cargo test` compatibility. (context: Kimetsu test suite — env-var mutation in parallel tests.)" + }, + { + "ce": 0.008110000751912594, + "key": "bridge-target-enum-seams", + "rank_score": 0.5014886260032654, + "text": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors. (context: Adding BridgeTarget::OpenClaw host to Kimetsu bridge.rs and main.rs in Workstream C)" + } + ], + "delivered": [ + "mutex-deadlock-user-brain-disabled" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8435134887695312 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "benchmarking retrieval quality across embedders", + "relevant": [], + "stages": [ + { + "candidates": [ + { + "ce": 0.7459741234779358, + "key": "onnx-quantization-drift", + "rank_score": 0.9549989104270935, + "text": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals — cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case. (context: Kimetsu embedding model selection — evaluating jina-v2 int8 vs fp32.)" + }, + { + "ce": 0.012238612398505211, + "key": "kimetsu-bench-remote-embedder-singleton", + "rank_score": 0.8089169859886169, + "text": "project:fact - [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval. Workaround: run ONE `--embedders` value per invocation and kill the remote process between runs. The local bench path is not affected (each combo is process-isolated via `--single` child spawn). (context: Kimetsu brain bench --remote known issue — multi-embedder contamination.)" + }, + { + "ce": 0.0063359434716403484, + "key": "kimetsu-mrr-metric", + "rank_score": 0.7931993007659912, + "text": "project:fact - [tags: kimetsu bench mrr recall metrics evaluation] kimetsu bench reports MRR (Mean Reciprocal Rank) and Recall@K. MRR is 1/rank_of_first_relevant_result, averaged across cases; it penalizes models that rank the correct answer 2nd or 3rd. Recall@K is the fraction of cases where at least one relevant answer appears in the top K. For multi-answer cases, recall@K considers a case satisfied if ANY relevant key appears in top K. MRR is the primary metric for knowledge retrieval because users read the first result first. A 0.01 MRR difference on a 100-case dataset corresponds to about 1 case changing from rank-2 to rank-1. Noise of ~2-3 cases is expected run-to-run. (context: Kimetsu benchmark metric interpretation.)" + }, + { + "ce": 0.021214036270976067, + "key": "cargo-feature-unification-embeddings", + "rank_score": 0.792500376701355, + "text": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli). Diagnostic tell: a test that passes alone but fails only under `cargo test --workspace` AND a brand-new crate was just added = suspect feature unification flipping a sibling crate's behavior. (context: Building the kimetsu-remote crate (HTTP MCP server); its default embeddings feature broke 3 kimetsu-chat retrieval tests only under the full workspace test.)" + }, + { + "ce": 0.003551563946530223, + "key": "kimetsu-rerank-pool", + "rank_score": 0.7194035649299622, + "text": "project:fact - [tags: kimetsu reranker pool size ann retrieval] kimetsu's retrieval pipeline: ANN (approximate nearest neighbor) retrieves a pool of candidates, then the reranker reorders them, then the top-K are returned. The pool size (default 6 for production, 12 in bench) controls the recall-latency tradeoff: larger pool = higher recall = more reranker calls = more latency. For the jina-tiny reranker, pool 12 adds ~80ms vs pool 6. The bench uses pool 12 to maximize measurable recall differences between rerankers; production uses pool 6 for latency. Increasing pool size beyond 20 has diminishing recall returns on corpora < 1000 memories. (context: Kimetsu ANN pool size tuning for the retrieval benchmark.)" + }, + { + "ce": 0.001070136670023203, + "key": "kimetsu-capsule-budgets", + "rank_score": 0.7183918356895447, + "text": "project:fact - [tags: kimetsu capsule tokens budget retrieval] kimetsu retrieval enforces a token budget per capsule type: memory capsules are capped at 6000 tokens total (across all retrieved memories), file capsules at 3000 tokens. When a memory is large and would exceed the budget, it is truncated at a sentence boundary. The budget is enforced AFTER reranking — reranking may reorder results so that a truncated high-ranked memory displaces a full lower-ranked one. `noise_caps` in the bench output counts capsules that scored below the noise floor — they consume budget without contributing signal. Lower noise_caps = tighter retrieval. (context: Kimetsu capsule budget enforcement and noise floor interaction.)" + } + ], + "delivered": [ + "onnx-quantization-drift" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.730060338973999 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "process memory working set RSS peak measurement Windows", + "relevant": [ + "process-start-time-cross-platform", + "windows-update-process-locking" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.00048580922884866595, + "key": "windows-console-encoding", + "rank_score": 0.9549988508224487, + "text": "project:fact - [tags: windows console encoding utf8 rust] Windows console code page defaults to the system ANSI code page (usually CP1252 or CP932), not UTF-8. Rust's `println!` writes UTF-8 bytes which display as mojibake in a non-UTF-8 console. Fix at process startup: call `SetConsoleOutputCP(65001)` via `winapi` or `windows-sys`, or set `PYTHONUTF8=1`/`RUST_LOG` before launch. In PowerShell, `[Console]::OutputEncoding = [System.Text.Encoding]::UTF8` fixes the session. For binary piped output (MCP stdio protocol), write raw bytes — don't use the console code page. (context: Kimetsu MCP server — Unicode memory text was garbled on non-UTF8 Windows terminals.)" + }, + { + "ce": 0.00033381584216840565, + "key": "sqlite-wal-network-drive", + "rank_score": 0.8211217522621155, + "text": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db. Fallback: `PRAGMA journal_mode=DELETE;` is safe over SMB at the cost of lower concurrency. Detect network drives at startup with `GetFileAttributes` checking FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS or using `PathIsNetworkPath`. (context: Users running kimetsu with the brain database on a mapped network drive.)" + }, + { + "ce": 0.00042495891102589667, + "key": "harbor-terminal-bench-subprocess-isolation", + "rank_score": 0.7930331826210022, + "text": "project:fact - [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd). Worker re-derives auth internally from .env so the OAuth token never lands in argv; it writes {run,grade} JSON the parent reads back. One Harbor invocation per process always works (baseline-alone passed). (context: kbench multi-trial sweeps crashed on every trial after the 1st; diagnosed as Harbor/pyiceberg os.getcwd staleness on WSL2.)" + }, + { + "ce": 0.00014831316366326064, + "key": "onnx-model-cache-paths", + "rank_score": 0.8090136051177979, + "text": "project:fact - [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use. In kimetsu, `KIMETSU_EMBEDDER_CACHE` overrides the path and is forwarded when spawning child bench processes — without forwarding it, each child re-downloads the model. (context: Kimetsu brain bench on CI — model cache path handling in child processes.)" + }, + { + "ce": 0.00033620643080212176, + "key": "windows-exit-codes", + "rank_score": 0.8165555596351624, + "text": "project:fact - [tags: windows exit-codes rust process child] On Windows, process exit codes are 32-bit unsigned integers (DWORD). Rust's `ExitStatus::code()` returns `Option` — it's `None` if the process was killed by a signal (which Windows doesn't use; instead, TerminateProcess with a code). Conventional codes: 0=success, 1=generic error, 0xC0000005=access violation. Programs that call `std::process::exit(-1)` on Windows produce exit code 0xFFFFFFFF (4294967295), not -1. When checking for success in a subprocess chain, always check `status.success()` rather than `status.code() == Some(0)` to handle this portably. (context: Kimetsu update binary replacement — exit code handling.)" + }, + { + "ce": 0.00503937341272831, + "key": "windows-update-process-locking", + "rank_score": 0.7871713638305664, + "text": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics — mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code. (context: Q2 — kimetsu update preflight for locked binary on Windows)" + } + ], + "delivered": [], + "excluded_gold": [ + { + "ce": null, + "key": "process-start-time-cross-platform" + } + ], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7045091390609741 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "cloning a git repository server-side into a managed checkout", + "relevant": [ + "remote-ingest-split-roots" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9940817952156067, + "key": "remote-ingest-split-roots", + "rank_score": 0.9549986720085144, + "text": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races. Re-enable kimetsu_brain_ingest_repo in the tool allowlist only when ingest is configured, and INTERCEPT that tools/call in the remote handler (clone+ingest_repo_at_root) before the normal dispatch (which would walk the wrong dir). Hermetic test: git init a temp repo, register url=local path, ingest, then context retrieves the file capsule via FTS (noop embedder). (context: R3c: server-side ingest for kimetsu-remote — cloning repos so file-capsule retrieval works without a local checkout.)" + }, + { + "ce": 0.9041922092437744, + "key": "git-sparse-checkout", + "rank_score": 0.9158686995506287, + "text": "project:fact - [tags: git sparse-checkout partial-clone bandwidth] `git sparse-checkout init --cone` combined with `git clone --filter=blob:none` (partial clone) fetches only the commit graph and tree objects, not blobs. Individual blobs are fetched on demand when accessed. This cuts clone time for large repos from minutes to seconds. For kimetsu server-side ingest, use `git clone --depth 1 --filter=blob:none` for the initial checkout, then `git sparse-checkout set ` to limit the working tree to indexed directories. On `git fetch --depth 1 origin main` for refresh, blobs in the sparse set are updated lazily. (context: Kimetsu remote ingest — reducing bandwidth and disk usage for large repo checkouts.)" + }, + { + "ce": 0.00005288324609864503, + "key": "kimetsu-daemon-lifecycle", + "rank_score": 0.5475141406059265, + "text": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required. The server PID is not stored anywhere; use `kimetsu doctor` to enumerate running MCP server processes via OS APIs. On Windows, the server binary may be locked by AV after first launch — `kimetsu update` must stop all running server processes before replacing the binary. (context: Kimetsu daemon lifecycle — process management for updates.)" + }, + { + "ce": 0.003147193929180503, + "key": "ci-secrets-masking", + "rank_score": 0.5153538584709167, + "text": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output — but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable. Never reconstruct secrets from parts in step output. For kimetsu bench `--remote` CI runs, `KIMETSU_REMOTE_TOKEN` must be in the repository secrets, not in the workflow YAML. Use `${{ secrets.KIMETSU_REMOTE_TOKEN }}` in env — never `echo ${{ secrets.KIMETSU_REMOTE_TOKEN }}` in a run step. (context: Kimetsu CI remote benchmark — token handling.)" + }, + { + "ce": 0.004797183442860842, + "key": "git-worktree-brain-isolation", + "rank_score": 0.5236510634422302, + "text": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root — if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain. Always set `KIMETSU_BRAIN_DIR` or use `git_init_boundary` in tests to prevent this. (context: Kimetsu development with git worktrees — test isolation.)" + }, + { + "ce": 0.00040271165198646486, + "key": "tokio-shutdown-ordering", + "rank_score": 0.478626549243927, + "text": "project:fact - [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries — the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks. `axum::Server::with_graceful_shutdown` handles steps 1-2; you must handle 3-5 manually. (context: Kimetsu remote server graceful shutdown implementation.)" + } + ], + "delivered": [ + "remote-ingest-split-roots", + "git-sparse-checkout" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7157584428787231 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "SigV4 signing HTTP requests in Rust", + "relevant": [ + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.995133101940155, + "key": "aws-sigv4-bedrock-blocking", + "rank_score": 0.9549986720085144, + "text": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed. aws-smithy-runtime-api required as a companion to supply Identity. (context: Implementing BedrockProvider for Kimetsu with blocking reqwest + SigV4 signing, no tokio/aws-sdk)" + }, + { + "ce": 0.9750049114227295, + "key": "aws-presigned-urls", + "rank_score": 0.7351323366165161, + "text": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time — clock skew > 15 minutes causes `RequestTimeTooSkewed`. kimetsu could use presigned URLs to serve brain exports from S3 without exposing credentials to the client. (context: Kimetsu potential S3 export feature — presigned URL generation.)" + }, + { + "ce": 0.936759352684021, + "key": "bedrock-kimetsu-provider", + "rank_score": 0.6688042283058167, + "text": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env. Wire \"bedrock\" into BOTH pipeline.rs provider matches AND the distiller (normalize_distiller_provider + instantiation); the distiller is configured independently so agent-on-Bedrock + harvester-on-direct-Claude works for free. Sign and send the SAME payload bytes; test signing determinism with a fixed SystemTime. (context: Workstream A: adding AWS Bedrock as a provider for the agent + auto-harvester in v1.0.0.)" + }, + { + "ce": 0.04371044784784317, + "key": "tokio-shutdown-ordering", + "rank_score": 0.5035309791564941, + "text": "project:fact - [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries — the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks. `axum::Server::with_graceful_shutdown` handles steps 1-2; you must handle 3-5 manually. (context: Kimetsu remote server graceful shutdown implementation.)" + }, + { + "ce": 0.0009637943003326654, + "key": "aws-retry-throttling", + "rank_score": 0.49259644746780396, + "text": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with ±25% jitter. Do NOT retry `ValidationException` or `AccessDeniedException` — these are permanent errors. `ModelStreamErrorException` during streaming may be retryable. Log the `x-amzn-requestid` header from failed responses for AWS support debugging. (context: Kimetsu Bedrock provider retry logic.)" + }, + { + "ce": 0.03210313618183136, + "key": "http-connection-pooling", + "rank_score": 0.4770582318305969, + "text": "project:fact - [tags: http reqwest connection-pool keep-alive rust] reqwest's `Client` holds a connection pool; always create ONE `Client` instance and clone it for each handler — cloning is cheap (Arc under the hood). Creating a `Client::new()` per request defeats connection pooling and causes TCP connection exhaustion under load. The default pool settings: max_idle_per_host=usize::MAX (unbounded), idle_timeout=90s. For a kimetsu outbound client (LLM provider), set `pool_max_idle_per_host(5)` to limit idle connections. On Windows, the underlying hyper+winapi stack may not reuse connections as aggressively as on Linux — set `connection_verbose(true)` on the builder to confirm reuse. (context: Kimetsu provider HTTP client — connection pooling best practices.)" + } + ], + "delivered": [ + "aws-sigv4-bedrock-blocking", + "aws-presigned-urls", + "bedrock-kimetsu-provider" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8198769092559814 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "cargo test --workspace feature flag changes broke my unit tests", + "relevant": [ + "cargo-feature-unification-embeddings" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9825970530509949, + "key": "cargo-feature-unification-embeddings", + "rank_score": 0.9549986720085144, + "text": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli). Diagnostic tell: a test that passes alone but fails only under `cargo test --workspace` AND a brand-new crate was just added = suspect feature unification flipping a sibling crate's behavior. (context: Building the kimetsu-remote crate (HTTP MCP server); its default embeddings feature broke 3 kimetsu-chat retrieval tests only under the full workspace test.)" + }, + { + "ce": 0.8159734606742859, + "key": "cargo-dev-dep-leak", + "rank_score": 0.846265971660614, + "text": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates. Run `cargo tree --features ` to trace which crate activated an unexpected feature. (context: Kimetsu testing infra — a dev-dep was activating the embeddings feature in non-test builds.)" + }, + { + "ce": 0.054049111902713776, + "key": "cargo-patch-section", + "rank_score": 0.7498481869697571, + "text": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace — including transitive deps — that depend on `my-crate`. Remove the patch before publishing. Using `[replace]` is deprecated since Cargo 0.47; always use `[patch]`. When patching a crate pinned via an exact version specifier, the patch must satisfy that exact version. Use `cargo tree` to confirm the patch is applied. (context: Kimetsu patching upstream rusqlite for a Windows-specific WAL fix.)" + }, + { + "ce": 0.014233420602977276, + "key": "clap-version-build-flavor", + "rank_score": 0.6706321835517883, + "text": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds. (context: QQ2: --version build flavor + plugin install self-check)" + }, + { + "ce": 0.05609964206814766, + "key": "ci-flaky-quarantine", + "rank_score": 0.6385406255722046, + "text": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal — a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output. NEVER let a flaky test gate the merge queue. For kimetsu timing-based tests (`test_gc_old_runs_deletes_ancient`), apply `#[cfg_attr(ci, ignore)]` and run only in a dedicated slow-CI job. (context: Kimetsu CI flaky test policy.)" + }, + { + "ce": 0.05397938936948776, + "key": "cargo-msrv", + "rank_score": 0.6119231581687927, + "text": "project:fact - [tags: cargo rust msrv edition compatibility] Set `rust-version` in each `Cargo.toml` to declare the minimum supported Rust version (MSRV). Cargo enforces this with `--check`: `cargo check` fails if the toolchain is older than `rust-version`. Keep MSRV as old as your oldest supported deployment target. When bumping MSRV, update the CI matrix and the workspace root. Common trap: a transitive dep bumps its MSRV, pulling yours up silently — check with `cargo msrv` (cargo-msrv crate) or `cargo tree -e features | grep msrv`. Edition 2021 requires Rust >= 1.56.0. (context: Kimetsu workspace MSRV policy — ensuring it runs on the LTS toolchain.)" + } + ], + "delivered": [ + "cargo-feature-unification-embeddings", + "cargo-dev-dep-leak" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.739231526851654 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "how do I make pasta carbonara?", + "relevant": [], + "stages": [ + { + "candidates": [ + { + "ce": 0.00006740693061146885, + "key": "kimetsu-daemon-lifecycle", + "rank_score": 0.9549989104270935, + "text": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required. The server PID is not stored anywhere; use `kimetsu doctor` to enumerate running MCP server processes via OS APIs. On Windows, the server binary may be locked by AV after first launch — `kimetsu update` must stop all running server processes before replacing the binary. (context: Kimetsu daemon lifecycle — process management for updates.)" + }, + { + "ce": 0.0006575608858838677, + "key": "cargo-dev-dep-leak", + "rank_score": 0.8563986420631409, + "text": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates. Run `cargo tree --features ` to trace which crate activated an unexpected feature. (context: Kimetsu testing infra — a dev-dep was activating the embeddings feature in non-test builds.)" + }, + { + "ce": 0.00023942785628605634, + "key": "kimetsu-write-tools-gate", + "rank_score": 0.7236489057540894, + "text": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level — disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients. The feature was introduced to prevent malicious prompts from poisoning the brain. (context: Kimetsu write-tools gate — config-driven security for remote deployments.)" + }, + { + "ce": 0.001962704584002495, + "key": "pi-openclaw-extension-api", + "rank_score": 0.6884595155715942, + "text": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`. External commands use `pi.exec()` but `node:child_process` spawn also works. Pi has NO MCP so Kimetsu integrates via TS extension + SKILL.md only. (context: Implementing Pi host target for Kimetsu plugin install/status/uninstall system.)\n\nAlso: [tags: kimetsu host-integration pi openclaw bridge] When integrating Kimetsu with an external host agent (Pi, OpenClaw, etc.), VERIFY the host's real plugin/extension API against its actual repo before writing embedded assets — docs-from-memory are frequently wrong. Concretely corrected during v1.0: Pi uses a default-export factory `export default function(pi)` (not `defineExtension`) with lifecycle events `session_start`/`agent_end`/`session_shutdown`; OpenClaw plugin entry is `index.ts` via `definePluginEntry` from `openclaw/plugin-sdk/plugin-entry` + an `openclaw.plugin.json` manifest, with snake_case hook events `agent_turn_prepare`/`agent_end`/`session_end` (NOT colon-delimited). Always make the embedded hook shell-out a silent no-op if the `kimetsu` binary isn't on PATH so a wrong guess never breaks the host. (context: Adding Pi + OpenClaw as BridgeTarget hosts in v1.0.0; the inferred extension/plugin APIs from docs were wrong and had to be corrected against the real repos.)" + }, + { + "ce": 0.001349625177681446, + "key": "cargo-feature-unification-embeddings", + "rank_score": 0.664626955986023, + "text": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli). Diagnostic tell: a test that passes alone but fails only under `cargo test --workspace` AND a brand-new crate was just added = suspect feature unification flipping a sibling crate's behavior. (context: Building the kimetsu-remote crate (HTTP MCP server); its default embeddings feature broke 3 kimetsu-chat retrieval tests only under the full workspace test.)" + }, + { + "ce": 0.0010664621368050575, + "key": "remote-ingest-split-roots", + "rank_score": 0.6181415319442749, + "text": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races. Re-enable kimetsu_brain_ingest_repo in the tool allowlist only when ingest is configured, and INTERCEPT that tools/call in the remote handler (clone+ingest_repo_at_root) before the normal dispatch (which would walk the wrong dir). Hermetic test: git init a temp repo, register url=local path, ingest, then context retrieves the file capsule via FTS (noop embedder). (context: R3c: server-side ingest for kimetsu-remote — cloning repos so file-capsule retrieval works without a local checkout.)" + } + ], + "delivered": [], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.5736154913902283 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "what is the offside rule in football?", + "relevant": [], + "stages": [ + { + "candidates": [ + { + "ce": 0.00008030343451537192, + "key": "sqlite-foreign-keys-default-off", + "rank_score": 0.9549986720085144, + "text": "project:fact - [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting — every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing. Check your schema with `PRAGMA foreign_key_list(table_name);` and your current setting with `PRAGMA foreign_keys;`. rusqlite does not enable foreign keys automatically. (context: Kimetsu brain schema — memory_tags table has FK to memories table, discovered ON DELETE CASCADE wasn't firing.)" + }, + { + "ce": 0.0000636228869552724, + "key": "cargo-lockfile-drift", + "rank_score": 0.8112373948097229, + "text": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this — it errors on any lockfile diff. For library crates, `Cargo.lock` is normally gitignored, but for workspace roots with binary crates it should be committed. Use `cargo update --precise ` to pin a specific dep version without touching unrelated entries. (context: Kimetsu workspace lockfile drift after adding kimetsu-remote crate.)" + }, + { + "ce": 0.000043776806705864146, + "key": "sqlite-json1-extract", + "rank_score": 0.8145219683647156, + "text": "project:fact - [tags: sqlite json1 json_extract rusqlite] SQLite's json1 extension (built in since 3.38.0) lets you index and query JSONB columns with `json_extract(col, '$.field')`. To create a partial index over a JSON field: `CREATE INDEX idx ON memories (json_extract(metadata, '$.scope')) WHERE json_extract(metadata, '$.scope') IS NOT NULL;`. Use `json_each` for array fields. On older SQLite builds (rusqlite links whatever the system provides), check for json1 with `SELECT json('{}');` — an error means it's absent. Always prefer column storage over JSON blobs for frequently queried fields. (context: Kimetsu brain querying metadata scopes without migrating a separate column.)" + }, + { + "ce": 0.00006093024421716109, + "key": "onnx-dim-mismatch", + "rank_score": 0.8043054342269897, + "text": "project:fact - [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results — the ANN index shape mismatch isn't always caught at runtime. kimetsu detects this by storing `embedder_id` in the brain schema and refusing to query if the configured embedder differs from what was used at ingest time. Mitigation: re-ingest all memories with the new model, or keep per-memory vector dim metadata. (context: Kimetsu embedder migration — detecting dimension mismatch at startup.)" + }, + { + "ce": 0.0005201936583034694, + "key": "harbor-terminal-bench-subprocess-isolation", + "rank_score": 0.41327226161956787, + "text": "project:fact - [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd). Worker re-derives auth internally from .env so the OAuth token never lands in argv; it writes {run,grade} JSON the parent reads back. One Harbor invocation per process always works (baseline-alone passed). (context: kbench multi-trial sweeps crashed on every trial after the 1st; diagnosed as Harbor/pyiceberg os.getcwd staleness on WSL2.)" + }, + { + "ce": 0.00042902558925561607, + "key": "aws-sigv4-bedrock-blocking", + "rank_score": 0.4119744896888733, + "text": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed. aws-smithy-runtime-api required as a companion to supply Identity. (context: Implementing BedrockProvider for Kimetsu with blocking reqwest + SigV4 signing, no tokio/aws-sdk)" + } + ], + "delivered": [], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.5495027303695679 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "best way to train for a half marathon", + "relevant": [], + "stages": [ + { + "candidates": [ + { + "ce": 0.00016052575665526092, + "key": "onnx-cosine-vs-dot", + "rank_score": 0.9549987316131592, + "text": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing — double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g. E5, GTE with separate query/passage prefixes), the query and document encoders must use different prefix strings. Check the model card's `Similarity function` field. usearch/qdrant: prefer `MetricKind::Cos` over `Dot` for passage vectors that may not be perfectly normalized. (context: Kimetsu embedding storage — similarity metric selection.)" + }, + { + "ce": 0.00004726546103483997, + "key": "kimetsu-daemon-lifecycle", + "rank_score": 0.9382681250572205, + "text": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required. The server PID is not stored anywhere; use `kimetsu doctor` to enumerate running MCP server processes via OS APIs. On Windows, the server binary may be locked by AV after first launch — `kimetsu update` must stop all running server processes before replacing the binary. (context: Kimetsu daemon lifecycle — process management for updates.)" + }, + { + "ce": 0.00032587762689217925, + "key": "tokio-select-cancellation", + "rank_score": 0.9233837127685547, + "text": "project:fact - [tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded. For correctness, cancelled futures must be cancellation-safe: holding no partially committed state. `tokio::sync::watch::Receiver::changed()` is cancellation-safe; `tokio::sync::mpsc::Sender::send()` is NOT (the item is lost). In kimetsu shutdown, use a `CancellationToken` and `select!` branches that are all cancellation-safe. (context: Kimetsu remote graceful shutdown — race between incoming requests and shutdown signal.)" + }, + { + "ce": 0.00007477253529941663, + "key": "testing-fixture-drift", + "rank_score": 0.8885408639907837, + "text": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code. For kimetsu, `EvalFixture::from_memories(memories)` constructs a dataset from the exported format — use it in tests instead of hardcoded JSON. Tag fixture files with the schema version they were generated against in a comment. (context: Kimetsu eval fixture drift after schema migration.)" + }, + { + "ce": 0.0001970350422197953, + "key": "http-connection-pooling", + "rank_score": 0.852365255355835, + "text": "project:fact - [tags: http reqwest connection-pool keep-alive rust] reqwest's `Client` holds a connection pool; always create ONE `Client` instance and clone it for each handler — cloning is cheap (Arc under the hood). Creating a `Client::new()` per request defeats connection pooling and causes TCP connection exhaustion under load. The default pool settings: max_idle_per_host=usize::MAX (unbounded), idle_timeout=90s. For a kimetsu outbound client (LLM provider), set `pool_max_idle_per_host(5)` to limit idle connections. On Windows, the underlying hyper+winapi stack may not reuse connections as aggressively as on Linux — set `connection_verbose(true)` on the builder to confirm reuse. (context: Kimetsu provider HTTP client — connection pooling best practices.)" + }, + { + "ce": 0.000607633322943002, + "key": "harbor-terminal-bench-subprocess-isolation", + "rank_score": 0.3779822587966919, + "text": "project:fact - [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd). Worker re-derives auth internally from .env so the OAuth token never lands in argv; it writes {run,grade} JSON the parent reads back. One Harbor invocation per process always works (baseline-alone passed). (context: kbench multi-trial sweeps crashed on every trial after the 1st; diagnosed as Harbor/pyiceberg os.getcwd staleness on WSL2.)" + } + ], + "delivered": [], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.5194114446640015 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "my test passes when I run it alone but fails under cargo test --workspace", + "relevant": [ + "cargo-feature-unification-embeddings" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9996473789215088, + "key": "cargo-feature-unification-embeddings", + "rank_score": 0.9549985527992249, + "text": "project:fact - [2026-09-05] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli). Diagnostic tell: a test that passes alone but fails only under `cargo test --workspace` AND a brand-new crate was just added = suspect feature unification flipping a sibling crate's behavior. (context: Building the kimetsu-remote crate (HTTP MCP server); its default embeddings feature broke 3 kimetsu-chat retrieval tests only under the full workspace test.)" + }, + { + "ce": 0.0680859237909317, + "key": "init-project-git-boundary", + "rank_score": 0.6449244618415833, + "text": "project:fact - [2026-09-05] [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain. (context: QQ3 — kimetsu setup integration test failed because init_project climbed git tree to real ~/.kimetsu instead of temp workspace)" + }, + { + "ce": 0.49686914682388306, + "key": "cargo-dev-dep-leak", + "rank_score": 0.6206000447273254, + "text": "project:fact - [2026-09-05] [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates. Run `cargo tree --features ` to trace which crate activated an unexpected feature. (context: Kimetsu testing infra — a dev-dep was activating the embeddings feature in non-test builds.)" + }, + { + "ce": 0.0344105064868927, + "key": "cargo-patch-section", + "rank_score": 0.6201047301292419, + "text": "project:fact - [2026-09-05] [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace — including transitive deps — that depend on `my-crate`. Remove the patch before publishing. Using `[replace]` is deprecated since Cargo 0.47; always use `[patch]`. When patching a crate pinned via an exact version specifier, the patch must satisfy that exact version. Use `cargo tree` to confirm the patch is applied. (context: Kimetsu patching upstream rusqlite for a Windows-specific WAL fix.)" + }, + { + "ce": 0.0037569606211036444, + "key": "tokio-runtime-in-tests", + "rank_score": 0.5984641909599304, + "text": "project:fact - [2026-09-05] [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests. For sync test code that calls async, use `tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async { ... })`. Never call `block_on` from inside an async function. (context: Kimetsu remote integration tests — nested runtime panic.)" + }, + { + "ce": 0.12012618780136108, + "key": "ci-flaky-quarantine", + "rank_score": 0.573323667049408, + "text": "project:fact - [2026-09-05] [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal — a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output. NEVER let a flaky test gate the merge queue. For kimetsu timing-based tests (`test_gc_old_runs_deletes_ancient`), apply `#[cfg_attr(ci, ignore)]` and run only in a dedicated slow-CI job. (context: Kimetsu CI flaky test policy.)" + } + ], + "delivered": [ + "cargo-feature-unification-embeddings", + "cargo-dev-dep-leak" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7090137004852295 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "all the project tests started hanging forever after I added my new test", + "relevant": [ + "mutex-deadlock-user-brain-disabled" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.0015294905751943588, + "key": "mutex-deadlock-user-brain-disabled", + "rank_score": 0.8987635970115662, + "text": "project:fact - [2026-09-05] [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure — `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation. (context: New tests for Tier-1 perf work called test_env_lock().lock() inside with_user_brain_disabled closure, deadlocking all project::tests that ran after them in the same test binary.)" + }, + { + "ce": 0.004509805701673031, + "key": "cargo-feature-unification-embeddings", + "rank_score": 0.9549985527992249, + "text": "project:fact - [2026-09-05] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli). Diagnostic tell: a test that passes alone but fails only under `cargo test --workspace` AND a brand-new crate was just added = suspect feature unification flipping a sibling crate's behavior. (context: Building the kimetsu-remote crate (HTTP MCP server); its default embeddings feature broke 3 kimetsu-chat retrieval tests only under the full workspace test.)" + }, + { + "ce": 0.0005520696868188679, + "key": "windows-update-process-locking", + "rank_score": 0.8031283020973206, + "text": "project:fact - [2026-09-05] [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics — mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code. (context: Q2 — kimetsu update preflight for locked binary on Windows)" + }, + { + "ce": 0.00013249147741589695, + "key": "cargo-patch-section", + "rank_score": 0.7379404306411743, + "text": "project:fact - [2026-09-05] [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace — including transitive deps — that depend on `my-crate`. Remove the patch before publishing. Using `[replace]` is deprecated since Cargo 0.47; always use `[patch]`. When patching a crate pinned via an exact version specifier, the patch must satisfy that exact version. Use `cargo tree` to confirm the patch is applied. (context: Kimetsu patching upstream rusqlite for a Windows-specific WAL fix.)" + }, + { + "ce": 0.000837179715745151, + "key": "tokio-runtime-in-tests", + "rank_score": 0.9154738783836365, + "text": "project:fact - [2026-09-05] [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests. For sync test code that calls async, use `tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async { ... })`. Never call `block_on` from inside an async function. (context: Kimetsu remote integration tests — nested runtime panic.)" + }, + { + "ce": 0.00019887284724973142, + "key": "ci-secrets-masking", + "rank_score": 0.7608482241630554, + "text": "project:fact - [2026-09-05] [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output — but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable. Never reconstruct secrets from parts in step output. For kimetsu bench `--remote` CI runs, `KIMETSU_REMOTE_TOKEN` must be in the repository secrets, not in the workflow YAML. Use `${{ secrets.KIMETSU_REMOTE_TOKEN }}` in env — never `echo ${{ secrets.KIMETSU_REMOTE_TOKEN }}` in a run step. (context: Kimetsu CI remote benchmark — token handling.)" + } + ], + "delivered": [], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.6294053792953491 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "my integration test silently wrote memories into my real home brain instead of the temp workspace", + "relevant": [ + "init-project-git-boundary" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.7656484842300415, + "key": "init-project-git-boundary", + "rank_score": 0.9549984931945801, + "text": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain. (context: QQ3 — kimetsu setup integration test failed because init_project climbed git tree to real ~/.kimetsu instead of temp workspace)" + }, + { + "ce": 0.00003331818516016938, + "key": "cargo-patch-section", + "rank_score": 0.8179427981376648, + "text": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace — including transitive deps — that depend on `my-crate`. Remove the patch before publishing. Using `[replace]` is deprecated since Cargo 0.47; always use `[patch]`. When patching a crate pinned via an exact version specifier, the patch must satisfy that exact version. Use `cargo tree` to confirm the patch is applied. (context: Kimetsu patching upstream rusqlite for a Windows-specific WAL fix.)" + }, + { + "ce": 0.03968885913491249, + "key": "cargo-feature-unification-embeddings", + "rank_score": 0.6933931708335876, + "text": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli). Diagnostic tell: a test that passes alone but fails only under `cargo test --workspace` AND a brand-new crate was just added = suspect feature unification flipping a sibling crate's behavior. (context: Building the kimetsu-remote crate (HTTP MCP server); its default embeddings feature broke 3 kimetsu-chat retrieval tests only under the full workspace test.)" + }, + { + "ce": 0.00004255196108715609, + "key": "ci-secrets-masking", + "rank_score": 0.6267334818840027, + "text": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output — but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable. Never reconstruct secrets from parts in step output. For kimetsu bench `--remote` CI runs, `KIMETSU_REMOTE_TOKEN` must be in the repository secrets, not in the workflow YAML. Use `${{ secrets.KIMETSU_REMOTE_TOKEN }}` in env — never `echo ${{ secrets.KIMETSU_REMOTE_TOKEN }}` in a run step. (context: Kimetsu CI remote benchmark — token handling.)" + }, + { + "ce": 0.007367032580077648, + "key": "pi-openclaw-extension-api", + "rank_score": 0.5955445766448975, + "text": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`. External commands use `pi.exec()` but `node:child_process` spawn also works. Pi has NO MCP so Kimetsu integrates via TS extension + SKILL.md only. (context: Implementing Pi host target for Kimetsu plugin install/status/uninstall system.)\n\nAlso: [tags: kimetsu host-integration pi openclaw bridge] When integrating Kimetsu with an external host agent (Pi, OpenClaw, etc.), VERIFY the host's real plugin/extension API against its actual repo before writing embedded assets — docs-from-memory are frequently wrong. Concretely corrected during v1.0: Pi uses a default-export factory `export default function(pi)` (not `defineExtension`) with lifecycle events `session_start`/`agent_end`/`session_shutdown`; OpenClaw plugin entry is `index.ts` via `definePluginEntry` from `openclaw/plugin-sdk/plugin-entry` + an `openclaw.plugin.json` manifest, with snake_case hook events `agent_turn_prepare`/`agent_end`/`session_end` (NOT colon-delimited). Always make the embedded hook shell-out a silent no-op if the `kimetsu` binary isn't on PATH so a wrong guess never breaks the host. (context: Adding Pi + OpenClaw as BridgeTarget hosts in v1.0.0; the inferred extension/plugin APIs from docs were wrong and had to be corrected against the real repos.)" + }, + { + "ce": 0.00007524239481426775, + "key": "testing-fixture-drift", + "rank_score": 0.594153106212616, + "text": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code. For kimetsu, `EvalFixture::from_memories(memories)` constructs a dataset from the exported format — use it in tests instead of hardcoded JSON. Tag fixture files with the schema version they were generated against in a comment. (context: Kimetsu eval fixture drift after schema migration.)" + } + ], + "delivered": [ + "init-project-git-boundary" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.6420913338661194 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "where should the env-var opt-out check live for a cleanup feature triggered from a hot code path", + "relevant": [ + "gc-trace-env-guard-placement" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9995137453079224, + "key": "gc-trace-env-guard-placement", + "rank_score": 0.9549984931945801, + "text": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site. (context: QQ4 — runs auto-GC on run creation. Env guard placement decision when wiring opportunistic GC into TraceWriter::create.)" + }, + { + "ce": 0.00009497456630924717, + "key": "clap-version-build-flavor", + "rank_score": 0.7588470578193665, + "text": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds. (context: QQ2: --version build flavor + plugin install self-check)" + }, + { + "ce": 0.00006371298513840884, + "key": "cargo-dev-dep-leak", + "rank_score": 0.7473104000091553, + "text": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates. Run `cargo tree --features ` to trace which crate activated an unexpected feature. (context: Kimetsu testing infra — a dev-dep was activating the embeddings feature in non-test builds.)" + }, + { + "ce": 0.00007743517926428467, + "key": "process-start-time-cross-platform", + "rank_score": 0.7108870148658752, + "text": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path). Keep the skew decision logic in a pure function `assess_mcp_skew(servers, binary_mtime, binary_path) -> Outcome` so it can be unit-tested without any live OS state. (context: Q3 — kimetsu doctor version-skew check for stale MCP server processes)" + }, + { + "ce": 0.0000279199502983829, + "key": "ci-matrix-explosion", + "rank_score": 0.6877268552780151, + "text": "project:fact - [tags: ci github-actions matrix jobs resources] A CI matrix combining OS (3) x Rust toolchain (3) x features (2) = 18 jobs. Each spawns a runner; at $0.008/min for Ubuntu and $0.016/min for Windows, a 10-minute build costs $2.40 per push. Reduce: test the full matrix only on PRs to main; on feature branches, test only Linux+stable. Use `fail-fast: false` to see all failures, not just the first. Combine related checks (clippy + test) in one job when they share build artifacts. For Windows-specific tests, run only the OS-specific job to reduce cost. (context: Kimetsu CI matrix cost optimization.)" + }, + { + "ce": 0.00002978612064907793, + "key": "sqlite-partial-index", + "rank_score": 0.6708593368530273, + "text": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query — the planner uses the partial index only when the WHERE clause matches. Verify index usage with `EXPLAIN QUERY PLAN SELECT ...`. Partial indexes are not supported before SQLite 3.8.0; rusqlite's bundled SQLite is always current, but system SQLite on old Debian/Ubuntu may not be. (context: Optimizing kimetsu brain retrieval query over the active-memories subset.)" + } + ], + "delivered": [ + "gc-trace-env-guard-placement" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7968748807907104 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "the brain database file stays huge on Windows even after deleting most rows", + "relevant": [ + "sqlite-vacuum-wal-checkpoint" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.0011156953405588865, + "key": "sqlite-vacuum-wal-checkpoint", + "rank_score": 0.7549425959587097, + "text": "project:fact - [2026-09-05] [tags: rust sqlite vacuum rusqlite windows] When implementing SQLite VACUUM in rusqlite: VACUUM cannot run inside a transaction. rusqlite's Connection does not hold an implicit transaction, so `conn.execute_batch(\"VACUUM;\")` works directly. After VACUUM, run `PRAGMA wal_checkpoint(TRUNCATE);` before measuring file size — on Windows the WAL file can hold significant space that isn't reflected in the main db file until the checkpoint runs. (context: Implementing kimetsu brain compact (Q8) — SQLite VACUUM + WAL checkpoint for accurate post-compact file size.)" + }, + { + "ce": 0.004629878327250481, + "key": "sqlite-wal-network-drive", + "rank_score": 0.9549984931945801, + "text": "project:fact - [2026-09-05] [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db. Fallback: `PRAGMA journal_mode=DELETE;` is safe over SMB at the cost of lower concurrency. Detect network drives at startup with `GetFileAttributes` checking FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS or using `PathIsNetworkPath`. (context: Users running kimetsu with the brain database on a mapped network drive.)" + }, + { + "ce": 0.007689201273024082, + "key": "sqlite-page-size", + "rank_score": 0.801258385181427, + "text": "project:fact - [2026-09-05] [tags: sqlite page_size performance rusqlite] SQLite's default page_size is 4096 bytes. For a write-heavy brain database with large BLOB payloads (embedding vectors), raising page_size to 16384 reduces fragmentation and improves sequential scan throughput. `PRAGMA page_size = 16384;` must be set BEFORE the first table is created — changing it on an existing database requires a VACUUM afterward to rebuild all pages. Verify it took effect with `PRAGMA page_size;` after VACUUM. rusqlite's `Connection::open` runs no implicit PRAGMA, so set this in the connection init path. (context: Tuning the kimetsu brain SQLite schema for embedding vector storage.)" + }, + { + "ce": 0.00040836670086719096, + "key": "sqlite-foreign-keys-default-off", + "rank_score": 0.7798386812210083, + "text": "project:fact - [2026-09-05] [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting — every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing. Check your schema with `PRAGMA foreign_key_list(table_name);` and your current setting with `PRAGMA foreign_keys;`. rusqlite does not enable foreign keys automatically. (context: Kimetsu brain schema — memory_tags table has FK to memories table, discovered ON DELETE CASCADE wasn't firing.)" + }, + { + "ce": 0.00066490558674559, + "key": "windows-long-paths", + "rank_score": 0.8518781661987305, + "text": "project:fact - [2026-09-05] [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe. On Windows 10 1607+ the LongPathsEnabled key is sufficient for most tools. `cargo build` itself works after the registry change; MSI installers may still fail on paths > 260 in the installer runtime. (context: Kimetsu CI on Windows Server 2019 — build failed with OS error 3 on deeply nested proc-macro paths.)" + }, + { + "ce": 0.0016301126452162862, + "key": "testing-temp-dirs-ci", + "rank_score": 0.7220563888549805, + "text": "project:fact - [2026-09-05] [tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure. On Windows, `env::temp_dir()` returns `C:\\Users\\\\AppData\\Local\\Temp` — ensure the test binary has write permissions there. Avoid using the workspace root as a temp dir — tests should never write to the source tree. (context: Kimetsu test infrastructure — temp directory discipline.)" + } + ], + "delivered": [], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.6145706176757812 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "re-importing the same exported memories file counts them as new instead of deduplicated", + "relevant": [ + "import-dedup-seen-ids" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9581347703933716, + "key": "import-dedup-seen-ids", + "rank_score": 0.9549984335899353, + "text": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount — both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise. (context: Implementing brain export/import (Q5). First naive approach used a single `seen_ids` set local to the function; the dedup test caught it on the second-import assertion.)" + }, + { + "ce": 0.008556769229471684, + "key": "testing-fixture-drift", + "rank_score": 0.9239808917045593, + "text": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code. For kimetsu, `EvalFixture::from_memories(memories)` constructs a dataset from the exported format — use it in tests instead of hardcoded JSON. Tag fixture files with the schema version they were generated against in a comment. (context: Kimetsu eval fixture drift after schema migration.)" + }, + { + "ce": 0.00007593278132844716, + "key": "bridge-target-enum-seams", + "rank_score": 0.822823703289032, + "text": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors. (context: Adding BridgeTarget::OpenClaw host to Kimetsu bridge.rs and main.rs in Workstream C)" + }, + { + "ce": 0.0005914645153097808, + "key": "kimetsu-capsule-budgets", + "rank_score": 0.7160404920578003, + "text": "project:fact - [tags: kimetsu capsule tokens budget retrieval] kimetsu retrieval enforces a token budget per capsule type: memory capsules are capped at 6000 tokens total (across all retrieved memories), file capsules at 3000 tokens. When a memory is large and would exceed the budget, it is truncated at a sentence boundary. The budget is enforced AFTER reranking — reranking may reorder results so that a truncated high-ranked memory displaces a full lower-ranked one. `noise_caps` in the bench output counts capsules that scored below the noise floor — they consume budget without contributing signal. Lower noise_caps = tighter retrieval. (context: Kimetsu capsule budget enforcement and noise floor interaction.)" + }, + { + "ce": 0.00936016533523798, + "key": "pi-openclaw-extension-api", + "rank_score": 0.6793498396873474, + "text": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`. External commands use `pi.exec()` but `node:child_process` spawn also works. Pi has NO MCP so Kimetsu integrates via TS extension + SKILL.md only. (context: Implementing Pi host target for Kimetsu plugin install/status/uninstall system.)\n\nAlso: [tags: kimetsu host-integration pi openclaw bridge] When integrating Kimetsu with an external host agent (Pi, OpenClaw, etc.), VERIFY the host's real plugin/extension API against its actual repo before writing embedded assets — docs-from-memory are frequently wrong. Concretely corrected during v1.0: Pi uses a default-export factory `export default function(pi)` (not `defineExtension`) with lifecycle events `session_start`/`agent_end`/`session_shutdown`; OpenClaw plugin entry is `index.ts` via `definePluginEntry` from `openclaw/plugin-sdk/plugin-entry` + an `openclaw.plugin.json` manifest, with snake_case hook events `agent_turn_prepare`/`agent_end`/`session_end` (NOT colon-delimited). Always make the embedded hook shell-out a silent no-op if the `kimetsu` binary isn't on PATH so a wrong guess never breaks the host. (context: Adding Pi + OpenClaw as BridgeTarget hosts in v1.0.0; the inferred extension/plugin APIs from docs were wrong and had to be corrected against the real repos.)" + }, + { + "ce": 0.00010149937588721514, + "key": "mutex-deadlock-user-brain-disabled", + "rank_score": 0.6712791323661804, + "text": "project:fact - [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure — `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation. (context: New tests for Tier-1 perf work called test_env_lock().lock() inside with_user_brain_disabled closure, deadlocking all project::tests that ran after them in the same test binary.)" + } + ], + "delivered": [ + "import-dedup-seen-ids" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.826894998550415 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "a helper function only called on Unix at runtime fails the dead-code lint on the Windows build", + "relevant": [ + "cfg-cross-platform-dead-code" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9773045778274536, + "key": "cfg-cross-platform-dead-code", + "rank_score": 0.9549984335899353, + "text": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform. (context: Adding parse_unix_ps to kimetsu-cli/src/process.rs — used only on Unix at runtime but needed on Windows for cross-platform unit tests.)" + }, + { + "ce": 0.26297447085380554, + "key": "windows-update-process-locking", + "rank_score": 0.6697207093238831, + "text": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics — mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code. (context: Q2 — kimetsu update preflight for locked binary on Windows)" + }, + { + "ce": 0.035900890827178955, + "key": "tokio-runtime-in-tests", + "rank_score": 0.6368221044540405, + "text": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests. For sync test code that calls async, use `tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async { ... })`. Never call `block_on` from inside an async function. (context: Kimetsu remote integration tests — nested runtime panic.)" + }, + { + "ce": 0.0003457825514487922, + "key": "gc-trace-env-guard-placement", + "rank_score": 0.5318381190299988, + "text": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site. (context: QQ4 — runs auto-GC on run creation. Env guard placement decision when wiring opportunistic GC into TraceWriter::create.)" + }, + { + "ce": 0.01826341450214386, + "key": "cargo-dev-dep-leak", + "rank_score": 0.49280956387519836, + "text": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates. Run `cargo tree --features ` to trace which crate activated an unexpected feature. (context: Kimetsu testing infra — a dev-dep was activating the embeddings feature in non-test builds.)" + }, + { + "ce": 0.001792949391528964, + "key": "process-start-time-cross-platform", + "rank_score": 0.5118843913078308, + "text": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path). Keep the skew decision logic in a pure function `assess_mcp_skew(servers, binary_mtime, binary_path) -> Outcome` so it can be unit-tested without any live OS state. (context: Q3 — kimetsu doctor version-skew check for stale MCP server processes)" + } + ], + "delivered": [ + "cfg-cross-platform-dead-code" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7269633412361145 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "the second Terminal-Bench trial always crashes even though the first one passes", + "relevant": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9305554032325745, + "key": "harbor-terminal-bench-subprocess-isolation", + "rank_score": 0.9549984335899353, + "text": "project:fact - [2026-09-05] [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd). Worker re-derives auth internally from .env so the OAuth token never lands in argv; it writes {run,grade} JSON the parent reads back. One Harbor invocation per process always works (baseline-alone passed). (context: kbench multi-trial sweeps crashed on every trial after the 1st; diagnosed as Harbor/pyiceberg os.getcwd staleness on WSL2.)" + }, + { + "ce": 0.00004953910320182331, + "key": "sqlite-busy-timeout-wal", + "rank_score": 0.44868743419647217, + "text": "project:fact - [2026-09-05] [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch. Set the timeout before any transaction, not inside one — it is a connection-level property. (context: Kimetsu brain writer and reader processes sharing the same SQLite brain database.)" + }, + { + "ce": 0.000043418298446340486, + "key": "onnx-model-cache-paths", + "rank_score": 0.45124658942222595, + "text": "project:fact - [2026-09-05] [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use. In kimetsu, `KIMETSU_EMBEDDER_CACHE` overrides the path and is forwarded when spawning child bench processes — without forwarding it, each child re-downloads the model. (context: Kimetsu brain bench on CI — model cache path handling in child processes.)" + }, + { + "ce": 0.000048898196837399155, + "key": "http-streaming-bodies", + "rank_score": 0.49324125051498413, + "text": "project:fact - [2026-09-05] [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding — a chunk may split across frame boundaries. In kimetsu's proxy path, accumulate bytes until `\n\n` (SSE frame delimiter) before parsing the JSON data field. Never assume one `.chunk()` call = one SSE event. (context: Kimetsu remote proxy — streaming LLM responses to the client.)" + }, + { + "ce": 0.00036063676816411316, + "key": "kimetsu-bench-remote-embedder-singleton", + "rank_score": 0.5764146447181702, + "text": "project:fact - [2026-09-05] [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval. Workaround: run ONE `--embedders` value per invocation and kill the remote process between runs. The local bench path is not affected (each combo is process-isolated via `--single` child spawn). (context: Kimetsu brain bench --remote known issue — multi-embedder contamination.)" + }, + { + "ce": 0.00009127527300734073, + "key": "kimetsu-mrr-metric", + "rank_score": 0.5369663238525391, + "text": "project:fact - [2026-09-05] [tags: kimetsu bench mrr recall metrics evaluation] kimetsu bench reports MRR (Mean Reciprocal Rank) and Recall@K. MRR is 1/rank_of_first_relevant_result, averaged across cases; it penalizes models that rank the correct answer 2nd or 3rd. Recall@K is the fraction of cases where at least one relevant answer appears in the top K. For multi-answer cases, recall@K considers a case satisfied if ANY relevant key appears in top K. MRR is the primary metric for knowledge retrieval because users read the first result first. A 0.01 MRR difference on a 100-case dataset corresponds to about 1 case changing from rank-2 to rank-1. Noise of ~2-3 cases is expected run-to-run. (context: Kimetsu benchmark metric interpretation.)" + } + ], + "delivered": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7033175826072693 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "how does doctor tell a running MCP server process is older than the kimetsu binary on disk", + "relevant": [ + "process-start-time-cross-platform" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9223618507385254, + "key": "kimetsu-daemon-lifecycle", + "rank_score": 0.9549986720085144, + "text": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required. The server PID is not stored anywhere; use `kimetsu doctor` to enumerate running MCP server processes via OS APIs. On Windows, the server binary may be locked by AV after first launch — `kimetsu update` must stop all running server processes before replacing the binary. (context: Kimetsu daemon lifecycle — process management for updates.)" + }, + { + "ce": 0.33758771419525146, + "key": "process-start-time-cross-platform", + "rank_score": 0.7939879894256592, + "text": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path). Keep the skew decision logic in a pure function `assess_mcp_skew(servers, binary_mtime, binary_path) -> Outcome` so it can be unit-tested without any live OS state. (context: Q3 — kimetsu doctor version-skew check for stale MCP server processes)" + }, + { + "ce": 0.0013419092865660787, + "key": "windows-unc-paths", + "rank_score": 0.6210092902183533, + "text": "project:fact - [tags: windows unc-paths rust std::fs] Windows UNC paths (`\\\\server\\share\\...`) are not supported by most Rust `std::fs` operations unless passed through the extended-length prefix `\\\\?\\UNC\\server\\share\\...`. `std::path::Path::new(\"\\\\\\\\server\\\\share\")` works for basic operations but breaks with `canonicalize()` which returns the verbatim prefix form. When walking directory trees that may start on UNC paths, use the `dunce` crate to strip the verbatim prefix before comparing or displaying paths. Never `cd` into a UNC path in a subprocess started with `std::process::Command` — the subprocess may not inherit it correctly on older Windows. (context: Kimetsu ingest walking paths on network-mounted project directories.)" + }, + { + "ce": 0.0806439146399498, + "key": "mcp-env-propagation", + "rank_score": 0.6168702840805054, + "text": "project:fact - [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment — changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate. For kimetsu hooks (pre-commit, post-commit), the hook script inherits the shell's env at hook invocation time, not the server's. If `KIMETSU_BRAIN_DIR` needs to vary per project, set it in the project's `.env` file and source it in the hook script. (context: Kimetsu env propagation from hooks to MCP server.)" + }, + { + "ce": 0.029313581064343452, + "key": "cargo-feature-unification-embeddings", + "rank_score": 0.5814064145088196, + "text": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli). Diagnostic tell: a test that passes alone but fails only under `cargo test --workspace` AND a brand-new crate was just added = suspect feature unification flipping a sibling crate's behavior. (context: Building the kimetsu-remote crate (HTTP MCP server); its default embeddings feature broke 3 kimetsu-chat retrieval tests only under the full workspace test.)" + }, + { + "ce": 0.00026987100136466324, + "key": "windows-exit-codes", + "rank_score": 0.6065178513526917, + "text": "project:fact - [tags: windows exit-codes rust process child] On Windows, process exit codes are 32-bit unsigned integers (DWORD). Rust's `ExitStatus::code()` returns `Option` — it's `None` if the process was killed by a signal (which Windows doesn't use; instead, TerminateProcess with a code). Conventional codes: 0=success, 1=generic error, 0xC0000005=access violation. Programs that call `std::process::exit(-1)` on Windows produce exit code 0xFFFFFFFF (4294967295), not -1. When checking for success in a subprocess chain, always check `status.success()` rather than `status.code() == Some(0)` to handle this portably. (context: Kimetsu update binary replacement — exit code handling.)" + } + ], + "delivered": [ + "kimetsu-daemon-lifecycle", + "process-start-time-cross-platform" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8068485856056213 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "the self-update preflight needs the list of running kimetsu processes without re-running the OS query", + "relevant": [ + "windows-update-process-locking" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9925383925437927, + "key": "windows-update-process-locking", + "rank_score": 0.9549983739852905, + "text": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics — mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code. (context: Q2 — kimetsu update preflight for locked binary on Windows)" + }, + { + "ce": 0.5589243769645691, + "key": "kimetsu-daemon-lifecycle", + "rank_score": 0.7388665676116943, + "text": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required. The server PID is not stored anywhere; use `kimetsu doctor` to enumerate running MCP server processes via OS APIs. On Windows, the server binary may be locked by AV after first launch — `kimetsu update` must stop all running server processes before replacing the binary. (context: Kimetsu daemon lifecycle — process management for updates.)" + }, + { + "ce": 0.004136857111006975, + "key": "git-submodule-pinning", + "rank_score": 0.6123369932174683, + "text": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip — this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version. If a submodule is the kimetsu-bench repo inside the main repo, pin the bench SHA after validating the dataset change. Use `git diff HEAD -- bench` to see the pinned SHA change before committing. (context: Kimetsu bench as a git submodule of the main repo.)" + }, + { + "ce": 0.002789895748719573, + "key": "sqlite-foreign-keys-default-off", + "rank_score": 0.560276985168457, + "text": "project:fact - [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting — every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing. Check your schema with `PRAGMA foreign_key_list(table_name);` and your current setting with `PRAGMA foreign_keys;`. rusqlite does not enable foreign keys automatically. (context: Kimetsu brain schema — memory_tags table has FK to memories table, discovered ON DELETE CASCADE wasn't firing.)" + }, + { + "ce": 0.026035143062472343, + "key": "bridge-target-enum-seams", + "rank_score": 0.5433083772659302, + "text": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors. (context: Adding BridgeTarget::OpenClaw host to Kimetsu bridge.rs and main.rs in Workstream C)" + }, + { + "ce": 0.0025470328982919455, + "key": "clap-version-build-flavor", + "rank_score": 0.5688250064849854, + "text": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds. (context: QQ2: --version build flavor + plugin install self-check)" + } + ], + "delivered": [ + "windows-update-process-locking", + "kimetsu-daemon-lifecycle" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7911025881767273 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "parsing the WMI DMTF CreationDate timestamp into epoch seconds without extra crates", + "relevant": [ + "process-start-time-cross-platform" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9751563668251038, + "key": "process-start-time-cross-platform", + "rank_score": 0.9549983739852905, + "text": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path). Keep the skew decision logic in a pure function `assess_mcp_skew(servers, binary_mtime, binary_path) -> Outcome` so it can be unit-tested without any live OS state. (context: Q3 — kimetsu doctor version-skew check for stale MCP server processes)" + }, + { + "ce": 0.00041183296707458794, + "key": "testing-snapshot-churn", + "rank_score": 0.5177012085914612, + "text": "project:fact - [tags: testing snapshot insta assert churn rust] Snapshot tests (e.g. with the `insta` crate) fail whenever the output changes, even for intended changes. In CI, they fail loudly; locally, `cargo insta review` walks you through accepting or rejecting changes. Snapshot churn becomes a problem when output includes timestamps, process IDs, or randomly-ordered maps. Redact these before snapshotting: use `insta::with_settings!({redactions: [\".timestamp\" => \"[TIMESTAMP]\"]})`. For JSON output, sort maps and arrays before comparing. Keep snapshot files in `src/snapshots/` and always commit them — an untracked snapshot file causes the next CI run to fail with a different error than expected. (context: Kimetsu CLI output snapshot tests — reducing churn.)" + }, + { + "ce": 0.00006806720921304077, + "key": "sqlite-json1-extract", + "rank_score": 0.5033067464828491, + "text": "project:fact - [tags: sqlite json1 json_extract rusqlite] SQLite's json1 extension (built in since 3.38.0) lets you index and query JSONB columns with `json_extract(col, '$.field')`. To create a partial index over a JSON field: `CREATE INDEX idx ON memories (json_extract(metadata, '$.scope')) WHERE json_extract(metadata, '$.scope') IS NOT NULL;`. Use `json_each` for array fields. On older SQLite builds (rusqlite links whatever the system provides), check for json1 with `SELECT json('{}');` — an error means it's absent. Always prefer column storage over JSON blobs for frequently queried fields. (context: Kimetsu brain querying metadata scopes without migrating a separate column.)" + }, + { + "ce": 0.00005266568041406572, + "key": "toml-value-parse", + "rank_score": 0.4797390103340149, + "text": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table. (context: Implementing config get/set with toml::Value navigation; str.parse() failed with 'unexpected content' error on document strings.)" + }, + { + "ce": 0.008917167782783508, + "key": "bedrock-kimetsu-provider", + "rank_score": 0.46585139632225037, + "text": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env. Wire \"bedrock\" into BOTH pipeline.rs provider matches AND the distiller (normalize_distiller_provider + instantiation); the distiller is configured independently so agent-on-Bedrock + harvester-on-direct-Claude works for free. Sign and send the SAME payload bytes; test signing determinism with a fixed SystemTime. (context: Workstream A: adding AWS Bedrock as a provider for the agent + auto-harvester in v1.0.0.)" + }, + { + "ce": 0.000058358065871289, + "key": "windows-update-process-locking", + "rank_score": 0.43733376264572144, + "text": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics — mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code. (context: Q2 — kimetsu update preflight for locked binary on Windows)" + } + ], + "delivered": [ + "process-start-time-cross-platform" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7829744219779968 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "calling Bedrock InvokeModel from blocking reqwest without the aws sdk", + "relevant": [ + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9989731311798096, + "key": "aws-sigv4-bedrock-blocking", + "rank_score": 0.9549983143806458, + "text": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed. aws-smithy-runtime-api required as a companion to supply Identity. (context: Implementing BedrockProvider for Kimetsu with blocking reqwest + SigV4 signing, no tokio/aws-sdk)" + }, + { + "ce": 0.9999415874481201, + "key": "bedrock-kimetsu-provider", + "rank_score": 0.8190571665763855, + "text": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env. Wire \"bedrock\" into BOTH pipeline.rs provider matches AND the distiller (normalize_distiller_provider + instantiation); the distiller is configured independently so agent-on-Bedrock + harvester-on-direct-Claude works for free. Sign and send the SAME payload bytes; test signing determinism with a fixed SystemTime. (context: Workstream A: adding AWS Bedrock as a provider for the agent + auto-harvester in v1.0.0.)" + }, + { + "ce": 0.6464323401451111, + "key": "aws-region-resolution", + "rank_score": 0.7066664695739746, + "text": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time. For cross-region inference (e.g. us-west-2 for Claude Opus), set `AWS_REGION=us-west-2`; do NOT rely on the Bedrock endpoint prefix being region-agnostic. (context: Kimetsu Bedrock provider region configuration.)" + }, + { + "ce": 0.026527782902121544, + "key": "aws-credentials-chain", + "rank_score": 0.6143652200698853, + "text": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually. On Windows, `~/.aws` is `%USERPROFILE%\\.aws` — `std::env::var(\"USERPROFILE\")` to get the path since `~` expansion is shell-level. (context: Kimetsu Bedrock provider credential resolution.)" + }, + { + "ce": 0.0017825873801484704, + "key": "tokio-blocking-in-async", + "rank_score": 0.5535415410995483, + "text": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking — never call rusqlite directly from an async fn without spawn_blocking. fastembed inference is also blocking (ONNX Runtime is synchronous). The threshold: any operation taking more than 100 microseconds that can't be made async belongs in spawn_blocking. Ignoring this causes tail-latency spikes and request timeouts under load in kimetsu-remote. (context: Kimetsu remote server — SQLite and embedding calls from async handlers.)" + }, + { + "ce": 0.4132297933101654, + "key": "aws-retry-throttling", + "rank_score": 0.5636123418807983, + "text": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with ±25% jitter. Do NOT retry `ValidationException` or `AccessDeniedException` — these are permanent errors. `ModelStreamErrorException` during streaming may be retryable. Log the `x-amzn-requestid` header from failed responses for AWS support debugging. (context: Kimetsu Bedrock provider retry logic.)" + } + ], + "delivered": [ + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking", + "aws-region-resolution", + "aws-retry-throttling" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8363407850265503 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "how do I rotate the encryption key protecting the kimetsu brain database", + "relevant": [], + "stages": [ + { + "candidates": [ + { + "ce": 0.40598493814468384, + "key": "sqlite-foreign-keys-default-off", + "rank_score": 0.9549983143806458, + "text": "project:fact - [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting — every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing. Check your schema with `PRAGMA foreign_key_list(table_name);` and your current setting with `PRAGMA foreign_keys;`. rusqlite does not enable foreign keys automatically. (context: Kimetsu brain schema — memory_tags table has FK to memories table, discovered ON DELETE CASCADE wasn't firing.)" + }, + { + "ce": 0.076267309486866, + "key": "kimetsu-eval-fixture-shape", + "rank_score": 0.7289228439331055, + "text": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` — a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases). Keys must be unique across the dataset. The bench currently does not validate keys at load — it fails later with an `unwrap()` on a missing HashMap entry. (context: Kimetsu bench dataset shape and validation.)" + }, + { + "ce": 0.06728042662143707, + "key": "windows-registry-rust", + "rank_score": 0.6718675494194031, + "text": "project:fact - [tags: windows registry rust winreg read write] Reading and writing the Windows registry from Rust requires the `winreg` crate. Open a key with `RegKey::predef(HKEY_LOCAL_MACHINE).open_subkey_with_flags(path, KEY_READ)` — use `KEY_READ` for reads and `KEY_READ | KEY_WRITE` for writes (NOT `KEY_ALL_ACCESS`, which requires admin). To set a DWORD value: `key.set_value(\"LongPathsEnabled\", &1u32)`. Registry paths use backslash separators and are case-insensitive. Prefer reading env vars over registry for runtime config — registry reads are expensive (kernel transition) and inappropriate for hot paths. For kimetsu, registry access is limited to the `kimetsu doctor` check for long-path enablement. (context: Kimetsu doctor — checking LongPathsEnabled registry value on Windows.)" + }, + { + "ce": 0.01893455907702446, + "key": "sqlite-busy-timeout-wal", + "rank_score": 0.684426486492157, + "text": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch. Set the timeout before any transaction, not inside one — it is a connection-level property. (context: Kimetsu brain writer and reader processes sharing the same SQLite brain database.)" + }, + { + "ce": 0.01573677733540535, + "key": "sqlite-page-size", + "rank_score": 0.6899924278259277, + "text": "project:fact - [tags: sqlite page_size performance rusqlite] SQLite's default page_size is 4096 bytes. For a write-heavy brain database with large BLOB payloads (embedding vectors), raising page_size to 16384 reduces fragmentation and improves sequential scan throughput. `PRAGMA page_size = 16384;` must be set BEFORE the first table is created — changing it on an existing database requires a VACUUM afterward to rebuild all pages. Verify it took effect with `PRAGMA page_size;` after VACUUM. rusqlite's `Connection::open` runs no implicit PRAGMA, so set this in the connection init path. (context: Tuning the kimetsu brain SQLite schema for embedding vector storage.)" + }, + { + "ce": 0.0011574053205549717, + "key": "cargo-dev-dep-leak", + "rank_score": 0.6217539310455322, + "text": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates. Run `cargo tree --features ` to trace which crate activated an unexpected feature. (context: Kimetsu testing infra — a dev-dep was activating the embeddings feature in non-test builds.)" + } + ], + "delivered": [ + "sqlite-foreign-keys-default-off" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7420233488082886 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "which tokio runtime worker-thread settings does the kimetsu MCP server use", + "relevant": [], + "stages": [ + { + "candidates": [ + { + "ce": 0.9897258877754211, + "key": "tokio-blocking-in-async", + "rank_score": 0.9549984335899353, + "text": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking — never call rusqlite directly from an async fn without spawn_blocking. fastembed inference is also blocking (ONNX Runtime is synchronous). The threshold: any operation taking more than 100 microseconds that can't be made async belongs in spawn_blocking. Ignoring this causes tail-latency spikes and request timeouts under load in kimetsu-remote. (context: Kimetsu remote server — SQLite and embedding calls from async handlers.)" + }, + { + "ce": 0.9347747564315796, + "key": "tokio-runtime-in-tests", + "rank_score": 0.8523565530776978, + "text": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests. For sync test code that calls async, use `tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async { ... })`. Never call `block_on` from inside an async function. (context: Kimetsu remote integration tests — nested runtime panic.)" + }, + { + "ce": 0.840076208114624, + "key": "tokio-spawn-blocking", + "rank_score": 0.7396880388259888, + "text": "project:fact - [tags: tokio spawn_blocking thread-pool rust blocking] `tokio::task::spawn_blocking` places work on a dedicated blocking thread pool (default up to 512 threads, configurable via `Builder::max_blocking_threads`). Each call creates or reuses a thread — there's no true pooling, threads may be created on demand. For many short-duration blocking calls (e.g. per-query SQLite reads), thread creation overhead may dominate. Prefer batching: collect N queries, then one `spawn_blocking` to run them all. Alternatively, keep a persistent blocking task that reads from an mpsc channel. Profile with `tokio-console` if you suspect spawn_blocking overhead. (context: Kimetsu retrieval server — per-query spawn_blocking was adding ~0.3ms overhead.)" + }, + { + "ce": 0.46386709809303284, + "key": "onnx-ort-threading", + "rank_score": 0.6337621808052063, + "text": "project:fact - [tags: onnx ort thread-pool parallelism cpu] ORT (ONNX Runtime) creates its own inter-op and intra-op thread pools. In a multi-process bench setup, each child inherits these pools and they compete for CPU cores. Set `SessionOptionsBuilder::with_intra_threads(1).with_inter_threads(1)` if you're running many parallel bench processes — this sacrifices per-inference throughput for lower contention. In a single-threaded embedding pipeline, 2-4 intra-op threads are better. For benchmarking, set `ORT_NUM_THREADS=1` via env var to get deterministic single-threaded latency numbers. (context: Kimetsu brain bench multi-process parallelism — ORT thread contention causing inconsistent latency.)" + }, + { + "ce": 0.6246721744537354, + "key": "mcp-stdout-protocol", + "rank_score": 0.6045668721199036, + "text": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr. `println!` in the request handler is forbidden. Use `eprintln!` or `tracing` with a stderr subscriber. In tests of the MCP server, capture stdout as bytes and validate it parses as JSON-Lines. When debugging, set `KIMETSU_LOG=debug` which writes to stderr only. (context: Kimetsu MCP server stdout protocol hygiene.)" + }, + { + "ce": 0.0011244280030950904, + "key": "sqlite-foreign-keys-default-off", + "rank_score": 0.5690731406211853, + "text": "project:fact - [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting — every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing. Check your schema with `PRAGMA foreign_key_list(table_name);` and your current setting with `PRAGMA foreign_keys;`. rusqlite does not enable foreign keys automatically. (context: Kimetsu brain schema — memory_tags table has FK to memories table, discovered ON DELETE CASCADE wasn't firing.)" + } + ], + "delivered": [ + "tokio-blocking-in-async", + "tokio-runtime-in-tests", + "tokio-spawn-blocking", + "mcp-stdout-protocol" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7746961712837219 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "how does kimetsu sync memories between two machines over the network", + "relevant": [], + "stages": [ + { + "candidates": [ + { + "ce": 0.028736835345625877, + "key": "sqlite-wal-network-drive", + "rank_score": 0.954998254776001, + "text": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db. Fallback: `PRAGMA journal_mode=DELETE;` is safe over SMB at the cost of lower concurrency. Detect network drives at startup with `GetFileAttributes` checking FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS or using `PathIsNetworkPath`. (context: Users running kimetsu with the brain database on a mapped network drive.)" + }, + { + "ce": 0.0014112096978351474, + "key": "onnx-quantization-drift", + "rank_score": 0.8090553283691406, + "text": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals — cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case. (context: Kimetsu embedding model selection — evaluating jina-v2 int8 vs fp32.)" + }, + { + "ce": 0.5229146480560303, + "key": "tokio-channel-backpressure", + "rank_score": 0.8016353845596313, + "text": "project:fact - [tags: tokio mpsc channel backpressure async rust] `tokio::sync::mpsc::channel(N)` with a bounded buffer provides backpressure: senders block when the buffer is full. This prevents unbounded memory growth but can cause sender tasks to stall. Choosing N: too small causes frequent backpressure (throughput drops); too large defeats the purpose. For kimetsu's harvest pipeline, N=16 was a good balance — the harvester is I/O bound (LLM call), producers are fast (hook callbacks). Prefer bounded channels over unbounded in production code. `tokio::sync::mpsc::unbounded_channel()` is a footgun for bursty producers. (context: Kimetsu auto-harvester pipeline — bounded vs unbounded channel selection.)" + }, + { + "ce": 0.023622307926416397, + "key": "tokio-select-cancellation", + "rank_score": 0.7823782563209534, + "text": "project:fact - [tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded. For correctness, cancelled futures must be cancellation-safe: holding no partially committed state. `tokio::sync::watch::Receiver::changed()` is cancellation-safe; `tokio::sync::mpsc::Sender::send()` is NOT (the item is lost). In kimetsu shutdown, use a `CancellationToken` and `select!` branches that are all cancellation-safe. (context: Kimetsu remote graceful shutdown — race between incoming requests and shutdown signal.)" + }, + { + "ce": 0.0011995546519756317, + "key": "http-proxy-env", + "rank_score": 0.7517261505126953, + "text": "project:fact - [tags: http proxy environment reqwest rust corporate] reqwest respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` environment variables by default (with `default-tls` or `rustls-tls`). In a corporate network, these may redirect traffic through an intercepting proxy that breaks mTLS or adds latency. To disable proxy usage entirely: `reqwest::ClientBuilder::no_proxy()`. On Windows, reqwest does NOT use the system proxy settings (IE/WinInet) — you must set env vars explicitly. `NO_PROXY=127.0.0.1,localhost` prevents proxying loopback traffic (important for kimetsu-remote local dev). (context: Kimetsu provider calls failing behind corporate proxy on Windows.)" + }, + { + "ce": 0.012248733080923557, + "key": "kimetsu-eval-fixture-shape", + "rank_score": 0.6730448007583618, + "text": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` — a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases). Keys must be unique across the dataset. The bench currently does not validate keys at load — it fails later with an `unwrap()` on a missing HashMap entry. (context: Kimetsu bench dataset shape and validation.)" + } + ], + "delivered": [ + "tokio-channel-backpressure" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7137143611907959 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "recovering a corrupted usearch ANN index after a power loss", + "relevant": [], + "stages": [ + { + "candidates": [ + { + "ce": 0.000042125193431274965, + "key": "sqlite-partial-index", + "rank_score": 0.6410198211669922, + "text": "project:fact - [2026-09-05] [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query — the planner uses the partial index only when the WHERE clause matches. Verify index usage with `EXPLAIN QUERY PLAN SELECT ...`. Partial indexes are not supported before SQLite 3.8.0; rusqlite's bundled SQLite is always current, but system SQLite on old Debian/Ubuntu may not be. (context: Optimizing kimetsu brain retrieval query over the active-memories subset.)" + }, + { + "ce": 0.00008799098577583209, + "key": "cargo-incremental-cache-corruption", + "rank_score": 0.6740216612815857, + "text": "project:fact - [2026-09-05] [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase. Fix: `cargo clean` then rebuild. Adding `CARGO_INCREMENTAL=0` to CI matrices prevents this class of false failures. (context: Kimetsu development — spurious type mismatch errors after branch switches.)" + }, + { + "ce": 0.0005847580614499748, + "key": "onnx-cosine-vs-dot", + "rank_score": 0.9384474754333496, + "text": "project:fact - [2026-09-05] [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing — double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g. E5, GTE with separate query/passage prefixes), the query and document encoders must use different prefix strings. Check the model card's `Similarity function` field. usearch/qdrant: prefer `MetricKind::Cos` over `Dot` for passage vectors that may not be perfectly normalized. (context: Kimetsu embedding storage — similarity metric selection.)" + }, + { + "ce": 0.009334061294794083, + "key": "onnx-dim-mismatch", + "rank_score": 0.7430413961410522, + "text": "project:fact - [2026-09-05] [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results — the ANN index shape mismatch isn't always caught at runtime. kimetsu detects this by storing `embedder_id` in the brain schema and refusing to query if the configured embedder differs from what was used at ingest time. Mitigation: re-ingest all memories with the new model, or keep per-memory vector dim metadata. (context: Kimetsu embedder migration — detecting dimension mismatch at startup.)" + }, + { + "ce": 0.0019107687985524535, + "key": "git-reflog-rescue", + "rank_score": 0.9549983739852905, + "text": "project:fact - [2026-09-05] [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone — they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only — remote reflog is not accessible via normal git commands. If you need the remote version, use `git fetch origin +refs/heads/main:refs/heads/main-backup` before a force push. In kimetsu bench development, always create a branch before destructive rebases. (context: Kimetsu bench dataset recovery after accidental hard reset.)" + }, + { + "ce": 0.0015594890573993325, + "key": "kimetsu-rerank-pool", + "rank_score": 0.7196881771087646, + "text": "project:fact - [2026-09-05] [tags: kimetsu reranker pool size ann retrieval] kimetsu's retrieval pipeline: ANN (approximate nearest neighbor) retrieves a pool of candidates, then the reranker reorders them, then the top-K are returned. The pool size (default 6 for production, 12 in bench) controls the recall-latency tradeoff: larger pool = higher recall = more reranker calls = more latency. For the jina-tiny reranker, pool 12 adds ~80ms vs pool 6. The bench uses pool 12 to maximize measurable recall differences between rerankers; production uses pool 6 for latency. Increasing pool size beyond 20 has diminishing recall returns on corpora < 1000 memories. (context: Kimetsu ANN pool size tuning for the retrieval benchmark.)" + } + ], + "delivered": [], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.6496827602386475 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "what postgres schema should I use to store kimetsu memories", + "relevant": [], + "stages": [ + { + "candidates": [ + { + "ce": 0.6243454217910767, + "key": "onnx-dim-mismatch", + "rank_score": 0.9549983143806458, + "text": "project:fact - [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results — the ANN index shape mismatch isn't always caught at runtime. kimetsu detects this by storing `embedder_id` in the brain schema and refusing to query if the configured embedder differs from what was used at ingest time. Mitigation: re-ingest all memories with the new model, or keep per-memory vector dim metadata. (context: Kimetsu embedder migration — detecting dimension mismatch at startup.)" + }, + { + "ce": 0.44681620597839355, + "key": "kimetsu-memory-scopes", + "rank_score": 0.6453843712806702, + "text": "project:fact - [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available — if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope. The `kimetsu_brain_record` MCP tool inherits the scope from the server's launch context. When running kimetsu-remote, all memories are project-scoped to the registered repo-id. (context: Kimetsu memory scope system — project vs user isolation.)" + }, + { + "ce": 0.0009307341533713043, + "key": "cargo-lockfile-drift", + "rank_score": 0.6267129182815552, + "text": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this — it errors on any lockfile diff. For library crates, `Cargo.lock` is normally gitignored, but for workspace roots with binary crates it should be committed. Use `cargo update --precise ` to pin a specific dep version without touching unrelated entries. (context: Kimetsu workspace lockfile drift after adding kimetsu-remote crate.)" + }, + { + "ce": 0.3200061023235321, + "key": "testing-fixture-drift", + "rank_score": 0.6243594884872437, + "text": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code. For kimetsu, `EvalFixture::from_memories(memories)` constructs a dataset from the exported format — use it in tests instead of hardcoded JSON. Tag fixture files with the schema version they were generated against in a comment. (context: Kimetsu eval fixture drift after schema migration.)" + }, + { + "ce": 0.15969523787498474, + "key": "sqlite-foreign-keys-default-off", + "rank_score": 0.607491672039032, + "text": "project:fact - [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting — every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing. Check your schema with `PRAGMA foreign_key_list(table_name);` and your current setting with `PRAGMA foreign_keys;`. rusqlite does not enable foreign keys automatically. (context: Kimetsu brain schema — memory_tags table has FK to memories table, discovered ON DELETE CASCADE wasn't firing.)" + }, + { + "ce": 0.010999307036399841, + "key": "http-tls-roots", + "rank_score": 0.53696608543396, + "text": "project:fact - [tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle — the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle. Alternatively, add the custom root with `add_root_certificate`. On Linux, the system CA bundle is at `/etc/ssl/certs/ca-certificates.crt`; on Windows it's in the Windows Certificate Store. (context: Kimetsu on a corporate Windows machine with a custom proxy CA.)" + } + ], + "delivered": [ + "onnx-dim-mismatch", + "kimetsu-memory-scopes", + "testing-fixture-drift" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7252835035324097 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "the whole CI job just froze forever with no failure output after my latest test PR", + "relevant": [ + "mutex-deadlock-user-brain-disabled" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.00035706080961972475, + "key": "cargo-incremental-cache-corruption", + "rank_score": 0.5385597944259644, + "text": "project:fact - [2026-09-05] [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase. Fix: `cargo clean` then rebuild. Adding `CARGO_INCREMENTAL=0` to CI matrices prevents this class of false failures. (context: Kimetsu development — spurious type mismatch errors after branch switches.)" + }, + { + "ce": 0.016272665932774544, + "key": "testing-snapshot-churn", + "rank_score": 0.5539107918739319, + "text": "project:fact - [2026-09-05] [tags: testing snapshot insta assert churn rust] Snapshot tests (e.g. with the `insta` crate) fail whenever the output changes, even for intended changes. In CI, they fail loudly; locally, `cargo insta review` walks you through accepting or rejecting changes. Snapshot churn becomes a problem when output includes timestamps, process IDs, or randomly-ordered maps. Redact these before snapshotting: use `insta::with_settings!({redactions: [\".timestamp\" => \"[TIMESTAMP]\"]})`. For JSON output, sort maps and arrays before comparing. Keep snapshot files in `src/snapshots/` and always commit them — an untracked snapshot file causes the next CI run to fail with a different error than expected. (context: Kimetsu CLI output snapshot tests — reducing churn.)" + }, + { + "ce": 0.0115434555336833, + "key": "testing-property-tests", + "rank_score": 0.6094257831573486, + "text": "project:fact - [2026-09-05] [tags: testing property-based proptest quickcheck rust] Property-based tests (proptest, quickcheck) find edge cases that example-based tests miss. For kimetsu's memory text normalization, proptest found that zero-width joiner characters and right-to-left marks caused hash collisions. Run proptest with `PROPTEST_CASES=10000` in CI for thorough coverage. Shrinking: when proptest finds a failure, it automatically shrinks the input to the minimal failing case — read the `Minimized failure` output, not the original random input. Use `prop_assume!` to skip inputs that violate preconditions rather than `if/return`. (context: Kimetsu brain text normalization — property test for dedup hash stability.)" + }, + { + "ce": 0.019795874133706093, + "key": "ci-matrix-explosion", + "rank_score": 0.9549984335899353, + "text": "project:fact - [2026-09-05] [tags: ci github-actions matrix jobs resources] A CI matrix combining OS (3) x Rust toolchain (3) x features (2) = 18 jobs. Each spawns a runner; at $0.008/min for Ubuntu and $0.016/min for Windows, a 10-minute build costs $2.40 per push. Reduce: test the full matrix only on PRs to main; on feature branches, test only Linux+stable. Use `fail-fast: false` to see all failures, not just the first. Combine related checks (clippy + test) in one job when they share build artifacts. For Windows-specific tests, run only the OS-specific job to reduce cost. (context: Kimetsu CI matrix cost optimization.)" + }, + { + "ce": 0.0006332639022730291, + "key": "ci-secrets-masking", + "rank_score": 0.5537144541740417, + "text": "project:fact - [2026-09-05] [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output — but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable. Never reconstruct secrets from parts in step output. For kimetsu bench `--remote` CI runs, `KIMETSU_REMOTE_TOKEN` must be in the repository secrets, not in the workflow YAML. Use `${{ secrets.KIMETSU_REMOTE_TOKEN }}` in env — never `echo ${{ secrets.KIMETSU_REMOTE_TOKEN }}` in a run step. (context: Kimetsu CI remote benchmark — token handling.)" + }, + { + "ce": 0.12042461335659027, + "key": "ci-flaky-quarantine", + "rank_score": 0.8477945327758789, + "text": "project:fact - [2026-09-05] [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal — a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output. NEVER let a flaky test gate the merge queue. For kimetsu timing-based tests (`test_gc_old_runs_deletes_ancient`), apply `#[cfg_attr(ci, ignore)]` and run only in a dedicated slow-CI job. (context: Kimetsu CI flaky test policy.)" + } + ], + "delivered": [], + "excluded_gold": [ + { + "ce": null, + "key": "mutex-deadlock-user-brain-disabled" + } + ], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.6282770037651062 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "running the test suite left junk state in my home directory", + "relevant": [ + "init-project-git-boundary" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.005620913580060005, + "key": "testing-serial-vs-parallel", + "rank_score": 0.9549983739852905, + "text": "project:fact - [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`). `cargo nextest` runs each test in a separate process by default, avoiding the problem entirely at the cost of longer startup time. For kimetsu, prefer nextest in CI and accept that `test_env_lock` exists only for `cargo test` compatibility. (context: Kimetsu test suite — env-var mutation in parallel tests.)" + }, + { + "ce": 0.0002491519844625145, + "key": "onnx-model-cache-paths", + "rank_score": 0.6851150393486023, + "text": "project:fact - [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use. In kimetsu, `KIMETSU_EMBEDDER_CACHE` overrides the path and is forwarded when spawning child bench processes — without forwarding it, each child re-downloads the model. (context: Kimetsu brain bench on CI — model cache path handling in child processes.)" + }, + { + "ce": 0.00007800332241458818, + "key": "cargo-patch-section", + "rank_score": 0.6269536018371582, + "text": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace — including transitive deps — that depend on `my-crate`. Remove the patch before publishing. Using `[replace]` is deprecated since Cargo 0.47; always use `[patch]`. When patching a crate pinned via an exact version specifier, the patch must satisfy that exact version. Use `cargo tree` to confirm the patch is applied. (context: Kimetsu patching upstream rusqlite for a Windows-specific WAL fix.)" + }, + { + "ce": 0.0006558956811204553, + "key": "testing-property-tests", + "rank_score": 0.6192891001701355, + "text": "project:fact - [tags: testing property-based proptest quickcheck rust] Property-based tests (proptest, quickcheck) find edge cases that example-based tests miss. For kimetsu's memory text normalization, proptest found that zero-width joiner characters and right-to-left marks caused hash collisions. Run proptest with `PROPTEST_CASES=10000` in CI for thorough coverage. Shrinking: when proptest finds a failure, it automatically shrinks the input to the minimal failing case — read the `Minimized failure` output, not the original random input. Use `prop_assume!` to skip inputs that violate preconditions rather than `if/return`. (context: Kimetsu brain text normalization — property test for dedup hash stability.)" + }, + { + "ce": 0.000702595803886652, + "key": "ci-flaky-quarantine", + "rank_score": 0.6305263042449951, + "text": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal — a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output. NEVER let a flaky test gate the merge queue. For kimetsu timing-based tests (`test_gc_old_runs_deletes_ancient`), apply `#[cfg_attr(ci, ignore)]` and run only in a dedicated slow-CI job. (context: Kimetsu CI flaky test policy.)" + }, + { + "ce": 0.0019092088332399726, + "key": "testing-temp-dirs-ci", + "rank_score": 0.6230031251907349, + "text": "project:fact - [tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure. On Windows, `env::temp_dir()` returns `C:\\Users\\\\AppData\\Local\\Temp` — ensure the test binary has write permissions there. Avoid using the workspace root as a temp dir — tests should never write to the source tree. (context: Kimetsu test infrastructure — temp directory discipline.)" + } + ], + "delivered": [], + "excluded_gold": [ + { + "ce": null, + "key": "init-project-git-boundary" + } + ], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.6605696678161621 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "I deleted a bunch of old rows but the file on disk is still the same size", + "relevant": [ + "sqlite-vacuum-wal-checkpoint" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.00043804117012768984, + "key": "sqlite-partial-index", + "rank_score": 0.9549981951713562, + "text": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query — the planner uses the partial index only when the WHERE clause matches. Verify index usage with `EXPLAIN QUERY PLAN SELECT ...`. Partial indexes are not supported before SQLite 3.8.0; rusqlite's bundled SQLite is always current, but system SQLite on old Debian/Ubuntu may not be. (context: Optimizing kimetsu brain retrieval query over the active-memories subset.)" + }, + { + "ce": 0.00006548449164256454, + "key": "sqlite-wal-network-drive", + "rank_score": 0.7471310496330261, + "text": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db. Fallback: `PRAGMA journal_mode=DELETE;` is safe over SMB at the cost of lower concurrency. Detect network drives at startup with `GetFileAttributes` checking FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS or using `PathIsNetworkPath`. (context: Users running kimetsu with the brain database on a mapped network drive.)" + }, + { + "ce": 0.00005948381658527069, + "key": "ci-flaky-quarantine", + "rank_score": 0.6904758214950562, + "text": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal — a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output. NEVER let a flaky test gate the merge queue. For kimetsu timing-based tests (`test_gc_old_runs_deletes_ancient`), apply `#[cfg_attr(ci, ignore)]` and run only in a dedicated slow-CI job. (context: Kimetsu CI flaky test policy.)" + }, + { + "ce": 0.00009978871821658686, + "key": "testing-fixture-drift", + "rank_score": 0.6776096820831299, + "text": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code. For kimetsu, `EvalFixture::from_memories(memories)` constructs a dataset from the exported format — use it in tests instead of hardcoded JSON. Tag fixture files with the schema version they were generated against in a comment. (context: Kimetsu eval fixture drift after schema migration.)" + }, + { + "ce": 0.00018773565534502268, + "key": "windows-long-paths", + "rank_score": 0.6311657428741455, + "text": "project:fact - [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe. On Windows 10 1607+ the LongPathsEnabled key is sufficient for most tools. `cargo build` itself works after the registry change; MSI installers may still fail on paths > 260 in the installer runtime. (context: Kimetsu CI on Windows Server 2019 — build failed with OS error 3 on deeply nested proc-macro paths.)" + }, + { + "ce": 0.00005639329174300656, + "key": "cargo-dev-dep-leak", + "rank_score": 0.5948135256767273, + "text": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates. Run `cargo tree --features ` to trace which crate activated an unexpected feature. (context: Kimetsu testing infra — a dev-dep was activating the embeddings feature in non-test builds.)" + } + ], + "delivered": [], + "excluded_gold": [ + { + "ce": null, + "key": "sqlite-vacuum-wal-checkpoint" + } + ], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.6023610830307007 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "adding one new crate quietly changed how the whole workspace builds", + "relevant": [ + "cargo-feature-unification-embeddings" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.0758836567401886, + "key": "cargo-dev-dep-leak", + "rank_score": 0.9549981951713562, + "text": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates. Run `cargo tree --features ` to trace which crate activated an unexpected feature. (context: Kimetsu testing infra — a dev-dep was activating the embeddings feature in non-test builds.)" + }, + { + "ce": 0.9790327548980713, + "key": "cargo-feature-unification-embeddings", + "rank_score": 0.8282424807548523, + "text": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli). Diagnostic tell: a test that passes alone but fails only under `cargo test --workspace` AND a brand-new crate was just added = suspect feature unification flipping a sibling crate's behavior. (context: Building the kimetsu-remote crate (HTTP MCP server); its default embeddings feature broke 3 kimetsu-chat retrieval tests only under the full workspace test.)" + }, + { + "ce": 0.9958756566047668, + "key": "cargo-lockfile-drift", + "rank_score": 0.7921401858329773, + "text": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this — it errors on any lockfile diff. For library crates, `Cargo.lock` is normally gitignored, but for workspace roots with binary crates it should be committed. Use `cargo update --precise ` to pin a specific dep version without touching unrelated entries. (context: Kimetsu workspace lockfile drift after adding kimetsu-remote crate.)" + }, + { + "ce": 0.12835270166397095, + "key": "bridge-target-enum-seams", + "rank_score": 0.6321377158164978, + "text": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors. (context: Adding BridgeTarget::OpenClaw host to Kimetsu bridge.rs and main.rs in Workstream C)" + }, + { + "ce": 0.00013952561130281538, + "key": "ci-cache-keys", + "rank_score": 0.635733425617218, + "text": "project:fact - [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key — macOS and Windows have incompatible artifact formats. Separate the registry cache from the build cache: the registry (downloaded crates) changes rarely, the build cache changes every push. Bust the build cache on major dependency changes by adding a manual cache version suffix to the key. (context: Kimetsu CI — cache invalidation strategy.)" + }, + { + "ce": 0.05214308202266693, + "key": "cargo-profile-override", + "rank_score": 0.6209315657615662, + "text": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug. The downside: rebuild time increases for that crate. For overflow-checks, `overflow-checks = false` per package speeds up hot loops. Never disable overflow-checks in release for business-critical data-mutating code. `[profile.release] strip = \"debuginfo\"` reduces binary size with minimal impact on stack traces. (context: Kimetsu dev experience — embedding inference was 10x slower in debug builds.)" + } + ], + "delivered": [ + "cargo-lockfile-drift", + "cargo-feature-unification-embeddings" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7614372372627258 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "we cannot pull an async runtime into the agent just to talk to AWS", + "relevant": [ + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.3322145938873291, + "key": "tokio-runtime-in-tests", + "rank_score": 0.954998254776001, + "text": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests. For sync test code that calls async, use `tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async { ... })`. Never call `block_on` from inside an async function. (context: Kimetsu remote integration tests — nested runtime panic.)" + }, + { + "ce": 0.45147860050201416, + "key": "bedrock-kimetsu-provider", + "rank_score": 0.7693817019462585, + "text": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env. Wire \"bedrock\" into BOTH pipeline.rs provider matches AND the distiller (normalize_distiller_provider + instantiation); the distiller is configured independently so agent-on-Bedrock + harvester-on-direct-Claude works for free. Sign and send the SAME payload bytes; test signing determinism with a fixed SystemTime. (context: Workstream A: adding AWS Bedrock as a provider for the agent + auto-harvester in v1.0.0.)" + }, + { + "ce": 0.06377310305833817, + "key": "tokio-blocking-in-async", + "rank_score": 0.717248797416687, + "text": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking — never call rusqlite directly from an async fn without spawn_blocking. fastembed inference is also blocking (ONNX Runtime is synchronous). The threshold: any operation taking more than 100 microseconds that can't be made async belongs in spawn_blocking. Ignoring this causes tail-latency spikes and request timeouts under load in kimetsu-remote. (context: Kimetsu remote server — SQLite and embedding calls from async handlers.)" + }, + { + "ce": 0.0031363130547106266, + "key": "pi-openclaw-extension-api", + "rank_score": 0.5781509280204773, + "text": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`. External commands use `pi.exec()` but `node:child_process` spawn also works. Pi has NO MCP so Kimetsu integrates via TS extension + SKILL.md only. (context: Implementing Pi host target for Kimetsu plugin install/status/uninstall system.)\n\nAlso: [tags: kimetsu host-integration pi openclaw bridge] When integrating Kimetsu with an external host agent (Pi, OpenClaw, etc.), VERIFY the host's real plugin/extension API against its actual repo before writing embedded assets — docs-from-memory are frequently wrong. Concretely corrected during v1.0: Pi uses a default-export factory `export default function(pi)` (not `defineExtension`) with lifecycle events `session_start`/`agent_end`/`session_shutdown`; OpenClaw plugin entry is `index.ts` via `definePluginEntry` from `openclaw/plugin-sdk/plugin-entry` + an `openclaw.plugin.json` manifest, with snake_case hook events `agent_turn_prepare`/`agent_end`/`session_end` (NOT colon-delimited). Always make the embedded hook shell-out a silent no-op if the `kimetsu` binary isn't on PATH so a wrong guess never breaks the host. (context: Adding Pi + OpenClaw as BridgeTarget hosts in v1.0.0; the inferred extension/plugin APIs from docs were wrong and had to be corrected against the real repos.)" + }, + { + "ce": 0.0002370959846302867, + "key": "cargo-feature-unification-embeddings", + "rank_score": 0.5411094427108765, + "text": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli). Diagnostic tell: a test that passes alone but fails only under `cargo test --workspace` AND a brand-new crate was just added = suspect feature unification flipping a sibling crate's behavior. (context: Building the kimetsu-remote crate (HTTP MCP server); its default embeddings feature broke 3 kimetsu-chat retrieval tests only under the full workspace test.)" + }, + { + "ce": 0.0006204545497894287, + "key": "aws-region-resolution", + "rank_score": 0.528308093547821, + "text": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time. For cross-region inference (e.g. us-west-2 for Claude Opus), set `AWS_REGION=us-west-2`; do NOT rely on the Bedrock endpoint prefix being region-agnostic. (context: Kimetsu Bedrock provider region configuration.)" + } + ], + "delivered": [ + "bedrock-kimetsu-provider", + "tokio-runtime-in-tests" + ], + "excluded_gold": [ + { + "ce": null, + "key": "aws-sigv4-bedrock-blocking" + } + ], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.6652303338050842 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "users should be able to tell which build variant they installed from the version output", + "relevant": [ + "clap-version-build-flavor" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.00030966935446485877, + "key": "testing-temp-dirs-ci", + "rank_score": 0.954998254776001, + "text": "project:fact - [tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure. On Windows, `env::temp_dir()` returns `C:\\Users\\\\AppData\\Local\\Temp` — ensure the test binary has write permissions there. Avoid using the workspace root as a temp dir — tests should never write to the source tree. (context: Kimetsu test infrastructure — temp directory discipline.)" + }, + { + "ce": 0.001418151194229722, + "key": "toml-value-parse", + "rank_score": 0.8957605957984924, + "text": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table. (context: Implementing config get/set with toml::Value navigation; str.parse() failed with 'unexpected content' error on document strings.)" + }, + { + "ce": 0.00016555534966755658, + "key": "http-timeout-layering", + "rank_score": 0.8932791352272034, + "text": "project:fact - [tags: http reqwest timeout connect read total rust] reqwest has three distinct timeout knobs: `connect_timeout`, `read_timeout`, and `timeout` (total). They compose: if all three are set, the request fails at whichever fires first. For LLM API calls with streaming responses, `read_timeout` must be larger than the slowest expected token (often 30-60s) while `connect_timeout` can be tight (3-5s). `timeout` should be your SLA ceiling. If you set only `timeout`, a slow connect eats into the overall budget. For kimetsu-remote, set both `connect_timeout(5s)` and `timeout(120s)` — the LLM call is the bottleneck. (context: Kimetsu provider timeouts — request timing out during streaming.)" + }, + { + "ce": 0.00015134166460484266, + "key": "remote-mcp-host-wiring", + "rank_score": 0.8515204191207886, + "text": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal. Derive a stable repo id from the git remote: strip `.git`, scheme (`://`), and `user@`, then map non-alphanumerics to '-' and collapse — so both https://github.com/org/repo.git and git@github.com:org/repo.git -> `github-com-org-repo`. Remote install writes ONLY the MCP entry + instructions (no local hooks — the brain is on the server). Codex/Pi don't get --remote (no remote-MCP / no MCP). (context: R2: implementing `kimetsu plugin install --remote` to wire a host at a kimetsu-remote HTTP MCP server.)" + }, + { + "ce": 0.0003598023031372577, + "key": "cargo-feature-unification-embeddings", + "rank_score": 0.8551393151283264, + "text": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli). Diagnostic tell: a test that passes alone but fails only under `cargo test --workspace` AND a brand-new crate was just added = suspect feature unification flipping a sibling crate's behavior. (context: Building the kimetsu-remote crate (HTTP MCP server); its default embeddings feature broke 3 kimetsu-chat retrieval tests only under the full workspace test.)" + }, + { + "ce": 0.00032707626814953983, + "key": "git-line-endings-windows", + "rank_score": 0.8349424004554749, + "text": "project:fact - [tags: git line-endings windows crlf autocrlf] On Windows, `core.autocrlf=true` (git's default for Windows installs) converts LF to CRLF on checkout and CRLF to LF on commit. This causes spurious diffs when files are edited on Windows then committed — the content is identical but the line endings differ in the index vs the working tree. Fix: set `core.autocrlf=false` and `.gitattributes` with `* text=auto eol=lf` for the repo. For Rust projects, all source files should be LF; only Windows batch scripts need CRLF. Warn: AV scanners that modify newly written files can re-introduce CRLF in files Rust writes. (context: Kimetsu CI — spurious diffs from Windows CRLF conversion.)" + } + ], + "delivered": [], + "excluded_gold": [ + { + "ce": null, + "key": "clap-version-build-flavor" + } + ], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.703101396560669 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "what gotchas should I expect writing process-inspection code that works on both Windows and Unix?", + "relevant": [ + "process-start-time-cross-platform", + "cfg-cross-platform-dead-code", + "windows-update-process-locking" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.000047987090511014685, + "key": "cargo-lockfile-drift", + "rank_score": 0.9549980759620667, + "text": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this — it errors on any lockfile diff. For library crates, `Cargo.lock` is normally gitignored, but for workspace roots with binary crates it should be committed. Use `cargo update --precise ` to pin a specific dep version without touching unrelated entries. (context: Kimetsu workspace lockfile drift after adding kimetsu-remote crate.)" + }, + { + "ce": 0.00011884020204888657, + "key": "http-timeout-layering", + "rank_score": 0.9534198641777039, + "text": "project:fact - [tags: http reqwest timeout connect read total rust] reqwest has three distinct timeout knobs: `connect_timeout`, `read_timeout`, and `timeout` (total). They compose: if all three are set, the request fails at whichever fires first. For LLM API calls with streaming responses, `read_timeout` must be larger than the slowest expected token (often 30-60s) while `connect_timeout` can be tight (3-5s). `timeout` should be your SLA ceiling. If you set only `timeout`, a slow connect eats into the overall budget. For kimetsu-remote, set both `connect_timeout(5s)` and `timeout(120s)` — the LLM call is the bottleneck. (context: Kimetsu provider timeouts — request timing out during streaming.)" + }, + { + "ce": 0.00009919386502588168, + "key": "remote-mcp-host-wiring", + "rank_score": 0.9146764874458313, + "text": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal. Derive a stable repo id from the git remote: strip `.git`, scheme (`://`), and `user@`, then map non-alphanumerics to '-' and collapse — so both https://github.com/org/repo.git and git@github.com:org/repo.git -> `github-com-org-repo`. Remote install writes ONLY the MCP entry + instructions (no local hooks — the brain is on the server). Codex/Pi don't get --remote (no remote-MCP / no MCP). (context: R2: implementing `kimetsu plugin install --remote` to wire a host at a kimetsu-remote HTTP MCP server.)" + }, + { + "ce": 0.0014196173287928104, + "key": "windows-update-process-locking", + "rank_score": 0.8984737396240234, + "text": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics — mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code. (context: Q2 — kimetsu update preflight for locked binary on Windows)" + }, + { + "ce": 0.0005783207598142326, + "key": "cargo-feature-unification-embeddings", + "rank_score": 0.873472273349762, + "text": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli). Diagnostic tell: a test that passes alone but fails only under `cargo test --workspace` AND a brand-new crate was just added = suspect feature unification flipping a sibling crate's behavior. (context: Building the kimetsu-remote crate (HTTP MCP server); its default embeddings feature broke 3 kimetsu-chat retrieval tests only under the full workspace test.)" + }, + { + "ce": 0.004943201318383217, + "key": "windows-exit-codes", + "rank_score": 0.8381485939025879, + "text": "project:fact - [tags: windows exit-codes rust process child] On Windows, process exit codes are 32-bit unsigned integers (DWORD). Rust's `ExitStatus::code()` returns `Option` — it's `None` if the process was killed by a signal (which Windows doesn't use; instead, TerminateProcess with a code). Conventional codes: 0=success, 1=generic error, 0xC0000005=access violation. Programs that call `std::process::exit(-1)` on Windows produce exit code 0xFFFFFFFF (4294967295), not -1. When checking for success in a subprocess chain, always check `status.success()` rather than `status.code() == Some(0)` to handle this portably. (context: Kimetsu update binary replacement — exit code handling.)" + } + ], + "delivered": [], + "excluded_gold": [ + { + "ce": null, + "key": "cfg-cross-platform-dead-code" + }, + { + "ce": null, + "key": "process-start-time-cross-platform" + } + ], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7296757698059082 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "why might tests behave differently on my machine than in the full CI run?", + "relevant": [ + "cargo-feature-unification-embeddings", + "mutex-deadlock-user-brain-disabled", + "init-project-git-boundary" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.000047090496082091704, + "key": "cargo-patch-section", + "rank_score": 0.9549980759620667, + "text": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace — including transitive deps — that depend on `my-crate`. Remove the patch before publishing. Using `[replace]` is deprecated since Cargo 0.47; always use `[patch]`. When patching a crate pinned via an exact version specifier, the patch must satisfy that exact version. Use `cargo tree` to confirm the patch is applied. (context: Kimetsu patching upstream rusqlite for a Windows-specific WAL fix.)" + }, + { + "ce": 0.00011222007742617279, + "key": "cargo-target-dir-sharing", + "rank_score": 0.9502453207969666, + "text": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps — use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination. (context: Kimetsu development on Windows with Windows Defender causing intermittent link failures.)" + }, + { + "ce": 0.0022980005014687777, + "key": "ci-matrix-explosion", + "rank_score": 0.8290490508079529, + "text": "project:fact - [tags: ci github-actions matrix jobs resources] A CI matrix combining OS (3) x Rust toolchain (3) x features (2) = 18 jobs. Each spawns a runner; at $0.008/min for Ubuntu and $0.016/min for Windows, a 10-minute build costs $2.40 per push. Reduce: test the full matrix only on PRs to main; on feature branches, test only Linux+stable. Use `fail-fast: false` to see all failures, not just the first. Combine related checks (clippy + test) in one job when they share build artifacts. For Windows-specific tests, run only the OS-specific job to reduce cost. (context: Kimetsu CI matrix cost optimization.)" + }, + { + "ce": 0.00024345048586837947, + "key": "windows-junctions-vs-symlinks", + "rank_score": 0.7926579713821411, + "text": "project:fact - [tags: windows junctions symlinks rust std::fs] On Windows, directory junctions (NTFS reparse points) behave like symlinks for directory traversal but `std::fs::symlink_metadata` returns `FileType::is_symlink() = false` for junctions (only true for regular symlinks). Use `std::fs::read_link` — it succeeds for both junction and symlink. `walkdir` crate's `follow_links` follows both, but its `is_symlink()` method correctly reports only actual symlinks. Creating symlinks requires SeCreateSymbolicLinkPrivilege (admin or Developer Mode). Creating junctions requires no special privilege. Use junctions for internal tooling that doesn't need to cross volumes. (context: Kimetsu path handling for brain symlink detection on Windows.)" + }, + { + "ce": 0.0019461577758193016, + "key": "cargo-feature-unification-embeddings", + "rank_score": 0.7952772378921509, + "text": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli). Diagnostic tell: a test that passes alone but fails only under `cargo test --workspace` AND a brand-new crate was just added = suspect feature unification flipping a sibling crate's behavior. (context: Building the kimetsu-remote crate (HTTP MCP server); its default embeddings feature broke 3 kimetsu-chat retrieval tests only under the full workspace test.)" + }, + { + "ce": 0.05688394978642464, + "key": "testing-temp-dirs-ci", + "rank_score": 0.7556017637252808, + "text": "project:fact - [tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure. On Windows, `env::temp_dir()` returns `C:\\Users\\\\AppData\\Local\\Temp` — ensure the test binary has write permissions there. Avoid using the workspace root as a temp dir — tests should never write to the source tree. (context: Kimetsu test infrastructure — temp directory discipline.)" + } + ], + "delivered": [], + "excluded_gold": [ + { + "ce": null, + "key": "init-project-git-boundary" + }, + { + "ce": null, + "key": "mutex-deadlock-user-brain-disabled" + } + ], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.6828793883323669 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "what do I need to know before wiring kimetsu into a brand new host agent?", + "relevant": [ + "bridge-target-enum-seams", + "pi-openclaw-extension-api", + "remote-mcp-host-wiring" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.24936777353286743, + "key": "remote-mcp-host-wiring", + "rank_score": 0.7972679138183594, + "text": "project:fact - [2026-09-05] [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal. Derive a stable repo id from the git remote: strip `.git`, scheme (`://`), and `user@`, then map non-alphanumerics to '-' and collapse — so both https://github.com/org/repo.git and git@github.com:org/repo.git -> `github-com-org-repo`. Remote install writes ONLY the MCP entry + instructions (no local hooks — the brain is on the server). Codex/Pi don't get --remote (no remote-MCP / no MCP). (context: R2: implementing `kimetsu plugin install --remote` to wire a host at a kimetsu-remote HTTP MCP server.)" + }, + { + "ce": 0.047442421317100525, + "key": "cargo-feature-unification-embeddings", + "rank_score": 0.905073344707489, + "text": "project:fact - [2026-09-05] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli). Diagnostic tell: a test that passes alone but fails only under `cargo test --workspace` AND a brand-new crate was just added = suspect feature unification flipping a sibling crate's behavior. (context: Building the kimetsu-remote crate (HTTP MCP server); its default embeddings feature broke 3 kimetsu-chat retrieval tests only under the full workspace test.)" + }, + { + "ce": 0.8544660806655884, + "key": "bridge-target-enum-seams", + "rank_score": 0.9184182286262512, + "text": "project:fact - [2026-09-05] [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors. (context: Adding BridgeTarget::OpenClaw host to Kimetsu bridge.rs and main.rs in Workstream C)" + }, + { + "ce": 0.01165983360260725, + "key": "aws-sigv4-bedrock-blocking", + "rank_score": 0.9549979567527771, + "text": "project:fact - [2026-09-05] [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed. aws-smithy-runtime-api required as a companion to supply Identity. (context: Implementing BedrockProvider for Kimetsu with blocking reqwest + SigV4 signing, no tokio/aws-sdk)" + }, + { + "ce": 0.013208108954131603, + "key": "gc-trace-env-guard-placement", + "rank_score": 0.8049699664115906, + "text": "project:fact - [2026-09-05] [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site. (context: QQ4 — runs auto-GC on run creation. Env guard placement decision when wiring opportunistic GC into TraceWriter::create.)" + }, + { + "ce": 0.4057532548904419, + "key": "kimetsu-daemon-lifecycle", + "rank_score": 0.8084792494773865, + "text": "project:fact - [2026-09-05] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required. The server PID is not stored anywhere; use `kimetsu doctor` to enumerate running MCP server processes via OS APIs. On Windows, the server binary may be locked by AV after first launch — `kimetsu update` must stop all running server processes before replacing the binary. (context: Kimetsu daemon lifecycle — process management for updates.)" + } + ], + "delivered": [ + "bridge-target-enum-seams", + "kimetsu-daemon-lifecycle" + ], + "excluded_gold": [ + { + "ce": null, + "key": "pi-openclaw-extension-api" + } + ], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7425166368484497 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "tell me everything relevant to running kimetsu against AWS", + "relevant": [ + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.23238645493984222, + "key": "cargo-feature-unification-embeddings", + "rank_score": 0.9549979567527771, + "text": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli). Diagnostic tell: a test that passes alone but fails only under `cargo test --workspace` AND a brand-new crate was just added = suspect feature unification flipping a sibling crate's behavior. (context: Building the kimetsu-remote crate (HTTP MCP server); its default embeddings feature broke 3 kimetsu-chat retrieval tests only under the full workspace test.)" + }, + { + "ce": 0.24565565586090088, + "key": "kimetsu-eval-fixture-shape", + "rank_score": 0.9526882767677307, + "text": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` — a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases). Keys must be unique across the dataset. The bench currently does not validate keys at load — it fails later with an `unwrap()` on a missing HashMap entry. (context: Kimetsu bench dataset shape and validation.)" + }, + { + "ce": 0.3769190013408661, + "key": "aws-credentials-chain", + "rank_score": 0.9113891124725342, + "text": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually. On Windows, `~/.aws` is `%USERPROFILE%\\.aws` — `std::env::var(\"USERPROFILE\")` to get the path since `~` expansion is shell-level. (context: Kimetsu Bedrock provider credential resolution.)" + }, + { + "ce": 0.37350255250930786, + "key": "kimetsu-mrr-metric", + "rank_score": 0.9127525687217712, + "text": "project:fact - [tags: kimetsu bench mrr recall metrics evaluation] kimetsu bench reports MRR (Mean Reciprocal Rank) and Recall@K. MRR is 1/rank_of_first_relevant_result, averaged across cases; it penalizes models that rank the correct answer 2nd or 3rd. Recall@K is the fraction of cases where at least one relevant answer appears in the top K. For multi-answer cases, recall@K considers a case satisfied if ANY relevant key appears in top K. MRR is the primary metric for knowledge retrieval because users read the first result first. A 0.01 MRR difference on a 100-case dataset corresponds to about 1 case changing from rank-2 to rank-1. Noise of ~2-3 cases is expected run-to-run. (context: Kimetsu benchmark metric interpretation.)" + }, + { + "ce": 0.01913449540734291, + "key": "aws-instance-metadata", + "rank_score": 0.9015417695045471, + "text": "project:fact - [tags: aws imds instance-metadata ec2 token] The AWS Instance Metadata Service v2 (IMDSv2) requires a session token: PUT `http://169.254.169.254/latest/api/token` with `X-aws-ec2-metadata-token-ttl-seconds: 21600` to get a token, then GET metadata with `X-aws-ec2-metadata-token: `. IMDSv1 (no token) is disabled on hardened instances. The metadata endpoint is only reachable from within EC2 — a connection timeout means you're not on EC2. Set a short connect timeout (200ms) when probing for the metadata service to avoid slow startup on non-EC2 hosts. (context: Kimetsu Bedrock provider — EC2 instance role credential fallback.)" + }, + { + "ce": 0.18715272843837738, + "key": "aws-region-resolution", + "rank_score": 0.8873588442802429, + "text": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time. For cross-region inference (e.g. us-west-2 for Claude Opus), set `AWS_REGION=us-west-2`; do NOT rely on the Bedrock endpoint prefix being region-agnostic. (context: Kimetsu Bedrock provider region configuration.)" + } + ], + "delivered": [ + "aws-credentials-chain", + "kimetsu-mrr-metric" + ], + "excluded_gold": [ + { + "ce": null, + "key": "bedrock-kimetsu-provider" + }, + { + "ce": null, + "key": "aws-sigv4-bedrock-blocking" + } + ], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.775814950466156 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "ingesting a cloned repo when the brain lives under a different root", + "relevant": [ + "remote-ingest-split-roots" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9838081002235413, + "key": "remote-ingest-split-roots", + "rank_score": 0.9549978971481323, + "text": "project:fact - [2026-09-05] [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races. Re-enable kimetsu_brain_ingest_repo in the tool allowlist only when ingest is configured, and INTERCEPT that tools/call in the remote handler (clone+ingest_repo_at_root) before the normal dispatch (which would walk the wrong dir). Hermetic test: git init a temp repo, register url=local path, ingest, then context retrieves the file capsule via FTS (noop embedder). (context: R3c: server-side ingest for kimetsu-remote — cloning repos so file-capsule retrieval works without a local checkout.)" + }, + { + "ce": 0.00009350002073915675, + "key": "sqlite-prepared-stmt-cache", + "rank_score": 0.42155367136001587, + "text": "project:fact - [2026-09-05] [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8). The cache key is the SQL string verbatim, so template strings with interpolated values defeat caching — use `?1, ?2` placeholders instead. Calling `prepare_cached` in a tight loop is effectively free after warmup. (context: Kimetsu brain high-throughput ingest path — replacing prepare() with prepare_cached() cut ingest time by ~30%.)" + }, + { + "ce": 0.0003916346176993102, + "key": "cargo-lockfile-drift", + "rank_score": 0.43323227763175964, + "text": "project:fact - [2026-09-05] [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this — it errors on any lockfile diff. For library crates, `Cargo.lock` is normally gitignored, but for workspace roots with binary crates it should be committed. Use `cargo update --precise ` to pin a specific dep version without touching unrelated entries. (context: Kimetsu workspace lockfile drift after adding kimetsu-remote crate.)" + }, + { + "ce": 0.0030899227131158113, + "key": "git-worktree-brain-isolation", + "rank_score": 0.4353761076927185, + "text": "project:fact - [2026-09-05] [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root — if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain. Always set `KIMETSU_BRAIN_DIR` or use `git_init_boundary` in tests to prevent this. (context: Kimetsu development with git worktrees — test isolation.)" + }, + { + "ce": 0.0014432404423132539, + "key": "git-sparse-checkout", + "rank_score": 0.6121955513954163, + "text": "project:fact - [2026-09-05] [tags: git sparse-checkout partial-clone bandwidth] `git sparse-checkout init --cone` combined with `git clone --filter=blob:none` (partial clone) fetches only the commit graph and tree objects, not blobs. Individual blobs are fetched on demand when accessed. This cuts clone time for large repos from minutes to seconds. For kimetsu server-side ingest, use `git clone --depth 1 --filter=blob:none` for the initial checkout, then `git sparse-checkout set ` to limit the working tree to indexed directories. On `git fetch --depth 1 origin main` for refresh, blobs in the sparse set are updated lazily. (context: Kimetsu remote ingest — reducing bandwidth and disk usage for large repo checkouts.)" + }, + { + "ce": 0.00010410377581138164, + "key": "http-connection-pooling", + "rank_score": 0.47870534658432007, + "text": "project:fact - [2026-09-05] [tags: http reqwest connection-pool keep-alive rust] reqwest's `Client` holds a connection pool; always create ONE `Client` instance and clone it for each handler — cloning is cheap (Arc under the hood). Creating a `Client::new()` per request defeats connection pooling and causes TCP connection exhaustion under load. The default pool settings: max_idle_per_host=usize::MAX (unbounded), idle_timeout=90s. For a kimetsu outbound client (LLM provider), set `pool_max_idle_per_host(5)` to limit idle connections. On Windows, the underlying hyper+winapi stack may not reuse connections as aggressively as on Linux — set `connection_verbose(true)` on the builder to confirm reuse. (context: Kimetsu provider HTTP client — connection pooling best practices.)" + } + ], + "delivered": [ + "remote-ingest-split-roots" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7667502164840698 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "streamable-http transport entry for openclaw.json with a bearer token", + "relevant": [ + "remote-mcp-host-wiring" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9984819293022156, + "key": "remote-mcp-host-wiring", + "rank_score": 0.9549978971481323, + "text": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal. Derive a stable repo id from the git remote: strip `.git`, scheme (`://`), and `user@`, then map non-alphanumerics to '-' and collapse — so both https://github.com/org/repo.git and git@github.com:org/repo.git -> `github-com-org-repo`. Remote install writes ONLY the MCP entry + instructions (no local hooks — the brain is on the server). Codex/Pi don't get --remote (no remote-MCP / no MCP). (context: R2: implementing `kimetsu plugin install --remote` to wire a host at a kimetsu-remote HTTP MCP server.)" + }, + { + "ce": 0.16717945039272308, + "key": "pi-openclaw-extension-api", + "rank_score": 0.559110701084137, + "text": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`. External commands use `pi.exec()` but `node:child_process` spawn also works. Pi has NO MCP so Kimetsu integrates via TS extension + SKILL.md only. (context: Implementing Pi host target for Kimetsu plugin install/status/uninstall system.)\n\nAlso: [tags: kimetsu host-integration pi openclaw bridge] When integrating Kimetsu with an external host agent (Pi, OpenClaw, etc.), VERIFY the host's real plugin/extension API against its actual repo before writing embedded assets — docs-from-memory are frequently wrong. Concretely corrected during v1.0: Pi uses a default-export factory `export default function(pi)` (not `defineExtension`) with lifecycle events `session_start`/`agent_end`/`session_shutdown`; OpenClaw plugin entry is `index.ts` via `definePluginEntry` from `openclaw/plugin-sdk/plugin-entry` + an `openclaw.plugin.json` manifest, with snake_case hook events `agent_turn_prepare`/`agent_end`/`session_end` (NOT colon-delimited). Always make the embedded hook shell-out a silent no-op if the `kimetsu` binary isn't on PATH so a wrong guess never breaks the host. (context: Adding Pi + OpenClaw as BridgeTarget hosts in v1.0.0; the inferred extension/plugin APIs from docs were wrong and had to be corrected against the real repos.)" + }, + { + "ce": 0.0003876896807923913, + "key": "kimetsu-daemon-lifecycle", + "rank_score": 0.463876336812973, + "text": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required. The server PID is not stored anywhere; use `kimetsu doctor` to enumerate running MCP server processes via OS APIs. On Windows, the server binary may be locked by AV after first launch — `kimetsu update` must stop all running server processes before replacing the binary. (context: Kimetsu daemon lifecycle — process management for updates.)" + }, + { + "ce": 0.05050680786371231, + "key": "aws-sigv4-bedrock-blocking", + "rank_score": 0.4559200406074524, + "text": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed. aws-smithy-runtime-api required as a companion to supply Identity. (context: Implementing BedrockProvider for Kimetsu with blocking reqwest + SigV4 signing, no tokio/aws-sdk)" + }, + { + "ce": 0.011679286137223244, + "key": "mcp-stdout-protocol", + "rank_score": 0.4505144953727722, + "text": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr. `println!` in the request handler is forbidden. Use `eprintln!` or `tracing` with a stderr subscriber. In tests of the MCP server, capture stdout as bytes and validate it parses as JSON-Lines. When debugging, set `KIMETSU_LOG=debug` which writes to stderr only. (context: Kimetsu MCP server stdout protocol hygiene.)" + }, + { + "ce": 0.012501555494964123, + "key": "onnx-tokenizer-mismatch", + "rank_score": 0.4355301260948181, + "text": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly — specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings — cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo. Validate by checking a reference embedding against the HuggingFace Python output. (context: Kimetsu custom ONNX reranker loading — wrong tokenizer produced degraded retrieval.)" + } + ], + "delivered": [ + "remote-mcp-host-wiring" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.69273841381073 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "serializing ingests with a tokio mutex to avoid checkout races", + "relevant": [ + "remote-ingest-split-roots" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9989351630210876, + "key": "remote-ingest-split-roots", + "rank_score": 0.9549978375434875, + "text": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races. Re-enable kimetsu_brain_ingest_repo in the tool allowlist only when ingest is configured, and INTERCEPT that tools/call in the remote handler (clone+ingest_repo_at_root) before the normal dispatch (which would walk the wrong dir). Hermetic test: git init a temp repo, register url=local path, ingest, then context retrieves the file capsule via FTS (noop embedder). (context: R3c: server-side ingest for kimetsu-remote — cloning repos so file-capsule retrieval works without a local checkout.)" + }, + { + "ce": 0.17443609237670898, + "key": "testing-serial-vs-parallel", + "rank_score": 0.674502968788147, + "text": "project:fact - [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`). `cargo nextest` runs each test in a separate process by default, avoiding the problem entirely at the cost of longer startup time. For kimetsu, prefer nextest in CI and accept that `test_env_lock` exists only for `cargo test` compatibility. (context: Kimetsu test suite — env-var mutation in parallel tests.)" + }, + { + "ce": 0.06693430989980698, + "key": "tokio-select-cancellation", + "rank_score": 0.6133453845977783, + "text": "project:fact - [tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded. For correctness, cancelled futures must be cancellation-safe: holding no partially committed state. `tokio::sync::watch::Receiver::changed()` is cancellation-safe; `tokio::sync::mpsc::Sender::send()` is NOT (the item is lost). In kimetsu shutdown, use a `CancellationToken` and `select!` branches that are all cancellation-safe. (context: Kimetsu remote graceful shutdown — race between incoming requests and shutdown signal.)" + }, + { + "ce": 0.0009408604819327593, + "key": "git-sparse-checkout", + "rank_score": 0.5978021621704102, + "text": "project:fact - [tags: git sparse-checkout partial-clone bandwidth] `git sparse-checkout init --cone` combined with `git clone --filter=blob:none` (partial clone) fetches only the commit graph and tree objects, not blobs. Individual blobs are fetched on demand when accessed. This cuts clone time for large repos from minutes to seconds. For kimetsu server-side ingest, use `git clone --depth 1 --filter=blob:none` for the initial checkout, then `git sparse-checkout set ` to limit the working tree to indexed directories. On `git fetch --depth 1 origin main` for refresh, blobs in the sparse set are updated lazily. (context: Kimetsu remote ingest — reducing bandwidth and disk usage for large repo checkouts.)" + }, + { + "ce": 0.0010470702545717359, + "key": "tokio-shutdown-ordering", + "rank_score": 0.5345081090927124, + "text": "project:fact - [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries — the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks. `axum::Server::with_graceful_shutdown` handles steps 1-2; you must handle 3-5 manually. (context: Kimetsu remote server graceful shutdown implementation.)" + }, + { + "ce": 0.03336517885327339, + "key": "mutex-deadlock-user-brain-disabled", + "rank_score": 0.5445879697799683, + "text": "project:fact - [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure — `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation. (context: New tests for Tier-1 perf work called test_env_lock().lock() inside with_user_brain_disabled closure, deadlocking all project::tests that ran after them in the same test binary.)" + } + ], + "delivered": [ + "remote-ingest-split-roots" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7278138995170593 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "percent-encoding the colon in the bedrock model id for the invoke URL", + "relevant": [ + "bedrock-kimetsu-provider" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9490103721618652, + "key": "bedrock-kimetsu-provider", + "rank_score": 0.9549978375434875, + "text": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env. Wire \"bedrock\" into BOTH pipeline.rs provider matches AND the distiller (normalize_distiller_provider + instantiation); the distiller is configured independently so agent-on-Bedrock + harvester-on-direct-Claude works for free. Sign and send the SAME payload bytes; test signing determinism with a fixed SystemTime. (context: Workstream A: adding AWS Bedrock as a provider for the agent + auto-harvester in v1.0.0.)" + }, + { + "ce": 0.1932196021080017, + "key": "aws-sigv4-bedrock-blocking", + "rank_score": 0.6355333924293518, + "text": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed. aws-smithy-runtime-api required as a companion to supply Identity. (context: Implementing BedrockProvider for Kimetsu with blocking reqwest + SigV4 signing, no tokio/aws-sdk)" + }, + { + "ce": 0.0252359788864851, + "key": "aws-region-resolution", + "rank_score": 0.5717531442642212, + "text": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time. For cross-region inference (e.g. us-west-2 for Claude Opus), set `AWS_REGION=us-west-2`; do NOT rely on the Bedrock endpoint prefix being region-agnostic. (context: Kimetsu Bedrock provider region configuration.)" + }, + { + "ce": 0.033275045454502106, + "key": "aws-retry-throttling", + "rank_score": 0.5612382888793945, + "text": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with ±25% jitter. Do NOT retry `ValidationException` or `AccessDeniedException` — these are permanent errors. `ModelStreamErrorException` during streaming may be retryable. Log the `x-amzn-requestid` header from failed responses for AWS support debugging. (context: Kimetsu Bedrock provider retry logic.)" + }, + { + "ce": 0.001735225203447044, + "key": "onnx-prefix-instructions", + "rank_score": 0.5348153114318848, + "text": "project:fact - [tags: onnx embeddings prefix instruction e5 query passage] E5 and Instructor family models require a text prefix on BOTH query and passage sides to produce meaningful similarities: query prefix `\"query: \"`, passage prefix `\"passage: \"`. Omitting the prefix can drop MRR by 10-15 percentage points on out-of-domain datasets. Check the model's README for the exact prefix string — it varies by model family. In kimetsu, the embedder abstraction has `query_prefix` and `passage_prefix` fields; FallbackEmbedder uses `\"\"` for both. jina-v2-base-code and bge-small use `\"\"` prefixes. (context: Kimetsu embedder trait design — prefix handling for E5/Instructor models.)" + }, + { + "ce": 0.0005889140302315354, + "key": "sqlite-fts5-tokenizer", + "rank_score": 0.5078831315040588, + "text": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon. If you switch tokenizers on an existing FTS5 table, you MUST rebuild the shadow tables: `INSERT INTO tbl(tbl) VALUES('rebuild');` — a schema-only change leaves the inverted index unusable. The `porter` stemmer is available as `tokenize='porter unicode61'` but aggressively strips suffixes and hurts precision on technical terms. (context: Kimetsu brain FTS5 index tuning for Rust identifier retrieval.)" + } + ], + "delivered": [ + "bedrock-kimetsu-provider" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7030869722366333 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "deduplicating re-imported memories against pre-existing ids", + "relevant": [ + "import-dedup-seen-ids" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9965111613273621, + "key": "import-dedup-seen-ids", + "rank_score": 0.9549978375434875, + "text": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount — both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise. (context: Implementing brain export/import (Q5). First naive approach used a single `seen_ids` set local to the function; the dedup test caught it on the second-import assertion.)" + }, + { + "ce": 0.00133004121016711, + "key": "onnx-tokenizer-mismatch", + "rank_score": 0.5034101009368896, + "text": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly — specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings — cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo. Validate by checking a reference embedding against the HuggingFace Python output. (context: Kimetsu custom ONNX reranker loading — wrong tokenizer produced degraded retrieval.)" + }, + { + "ce": 0.000118047340947669, + "key": "testing-serial-vs-parallel", + "rank_score": 0.49112004041671753, + "text": "project:fact - [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`). `cargo nextest` runs each test in a separate process by default, avoiding the problem entirely at the cost of longer startup time. For kimetsu, prefer nextest in CI and accept that `test_env_lock` exists only for `cargo test` compatibility. (context: Kimetsu test suite — env-var mutation in parallel tests.)" + }, + { + "ce": 0.0014983402797952294, + "key": "kimetsu-eval-fixture-shape", + "rank_score": 0.47658705711364746, + "text": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` — a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases). Keys must be unique across the dataset. The bench currently does not validate keys at load — it fails later with an `unwrap()` on a missing HashMap entry. (context: Kimetsu bench dataset shape and validation.)" + }, + { + "ce": 0.001972627593204379, + "key": "http-retry-idempotency", + "rank_score": 0.45026251673698425, + "text": "project:fact - [tags: http retry idempotency post put reqwest] Only retry idempotent requests automatically. GET, HEAD, PUT, DELETE are idempotent. POST is NOT — retrying a POST may create duplicate resources. For LLM API calls (POST), implement retry with idempotency keys: include a stable `X-Idempotency-Key: ` header; the provider deduplicates. For transient 429 (rate limit) responses, back off with jitter: `min(base * 2^attempt, cap) + rand(0, base)`. For 5xx, retry at most 3 times. Never retry on 4xx (except 429). In kimetsu, retry logic lives in the provider layer, not the distiller. (context: Kimetsu LLM provider retry strategy.)" + }, + { + "ce": 0.002699719974771142, + "key": "harbor-terminal-bench-subprocess-isolation", + "rank_score": 0.4372662305831909, + "text": "project:fact - [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd). Worker re-derives auth internally from .env so the OAuth token never lands in argv; it writes {run,grade} JSON the parent reads back. One Harbor invocation per process always works (baseline-alone passed). (context: kbench multi-trial sweeps crashed on every trial after the 1st; diagnosed as Harbor/pyiceberg os.getcwd staleness on WSL2.)" + } + ], + "delivered": [ + "import-dedup-seen-ids" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7938126921653748 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "parsing DMTF datetimes", + "relevant": [ + "process-start-time-cross-platform" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.990456759929657, + "key": "process-start-time-cross-platform", + "rank_score": 0.9549977779388428, + "text": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path). Keep the skew decision logic in a pure function `assess_mcp_skew(servers, binary_mtime, binary_path) -> Outcome` so it can be unit-tested without any live OS state. (context: Q3 — kimetsu doctor version-skew check for stale MCP server processes)" + }, + { + "ce": 0.00006801718700444326, + "key": "toml-value-parse", + "rank_score": 0.532379686832428, + "text": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table. (context: Implementing config get/set with toml::Value navigation; str.parse() failed with 'unexpected content' error on document strings.)" + }, + { + "ce": 0.00004596505459630862, + "key": "cfg-cross-platform-dead-code", + "rank_score": 0.5202652812004089, + "text": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform. (context: Adding parse_unix_ps to kimetsu-cli/src/process.rs — used only on Unix at runtime but needed on Windows for cross-platform unit tests.)" + }, + { + "ce": 0.00005526612949324772, + "key": "mcp-stdout-protocol", + "rank_score": 0.4926776885986328, + "text": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr. `println!` in the request handler is forbidden. Use `eprintln!` or `tracing` with a stderr subscriber. In tests of the MCP server, capture stdout as bytes and validate it parses as JSON-Lines. When debugging, set `KIMETSU_LOG=debug` which writes to stderr only. (context: Kimetsu MCP server stdout protocol hygiene.)" + }, + { + "ce": 0.0004347079957369715, + "key": "sqlite-prepared-stmt-cache", + "rank_score": 0.4471241235733032, + "text": "project:fact - [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8). The cache key is the SQL string verbatim, so template strings with interpolated values defeat caching — use `?1, ?2` placeholders instead. Calling `prepare_cached` in a tight loop is effectively free after warmup. (context: Kimetsu brain high-throughput ingest path — replacing prepare() with prepare_cached() cut ingest time by ~30%.)" + }, + { + "ce": 0.0003906908386852592, + "key": "http-streaming-bodies", + "rank_score": 0.44387805461883545, + "text": "project:fact - [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding — a chunk may split across frame boundaries. In kimetsu's proxy path, accumulate bytes until `\n\n` (SSE frame delimiter) before parsing the JSON data field. Never assume one `.chunk()` call = one SSE event. (context: Kimetsu remote proxy — streaming LLM responses to the client.)" + } + ], + "delivered": [ + "process-start-time-cross-platform" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.6983275413513184 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "how should install derive a stable identifier from the git remote URL?", + "relevant": [ + "remote-mcp-host-wiring" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9954527020454407, + "key": "remote-mcp-host-wiring", + "rank_score": 0.954997718334198, + "text": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal. Derive a stable repo id from the git remote: strip `.git`, scheme (`://`), and `user@`, then map non-alphanumerics to '-' and collapse — so both https://github.com/org/repo.git and git@github.com:org/repo.git -> `github-com-org-repo`. Remote install writes ONLY the MCP entry + instructions (no local hooks — the brain is on the server). Codex/Pi don't get --remote (no remote-MCP / no MCP). (context: R2: implementing `kimetsu plugin install --remote` to wire a host at a kimetsu-remote HTTP MCP server.)" + }, + { + "ce": 0.0005956329987384379, + "key": "git-line-endings-windows", + "rank_score": 0.5827495455741882, + "text": "project:fact - [tags: git line-endings windows crlf autocrlf] On Windows, `core.autocrlf=true` (git's default for Windows installs) converts LF to CRLF on checkout and CRLF to LF on commit. This causes spurious diffs when files are edited on Windows then committed — the content is identical but the line endings differ in the index vs the working tree. Fix: set `core.autocrlf=false` and `.gitattributes` with `* text=auto eol=lf` for the repo. For Rust projects, all source files should be LF; only Windows batch scripts need CRLF. Warn: AV scanners that modify newly written files can re-introduce CRLF in files Rust writes. (context: Kimetsu CI — spurious diffs from Windows CRLF conversion.)" + }, + { + "ce": 0.07131136208772659, + "key": "remote-ingest-split-roots", + "rank_score": 0.5691297054290771, + "text": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races. Re-enable kimetsu_brain_ingest_repo in the tool allowlist only when ingest is configured, and INTERCEPT that tools/call in the remote handler (clone+ingest_repo_at_root) before the normal dispatch (which would walk the wrong dir). Hermetic test: git init a temp repo, register url=local path, ingest, then context retrieves the file capsule via FTS (noop embedder). (context: R3c: server-side ingest for kimetsu-remote — cloning repos so file-capsule retrieval works without a local checkout.)" + }, + { + "ce": 0.00012886192416772246, + "key": "cargo-dev-dep-leak", + "rank_score": 0.5255123972892761, + "text": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates. Run `cargo tree --features ` to trace which crate activated an unexpected feature. (context: Kimetsu testing infra — a dev-dep was activating the embeddings feature in non-test builds.)" + }, + { + "ce": 0.0003406208998057991, + "key": "aws-presigned-urls", + "rank_score": 0.4944252371788025, + "text": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time — clock skew > 15 minutes causes `RequestTimeTooSkewed`. kimetsu could use presigned URLs to serve brain exports from S3 without exposing credentials to the client. (context: Kimetsu potential S3 export feature — presigned URL generation.)" + }, + { + "ce": 0.001252179965376854, + "key": "ci-secrets-masking", + "rank_score": 0.4832351505756378, + "text": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output — but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable. Never reconstruct secrets from parts in step output. For kimetsu bench `--remote` CI runs, `KIMETSU_REMOTE_TOKEN` must be in the repository secrets, not in the workflow YAML. Use `${{ secrets.KIMETSU_REMOTE_TOKEN }}` in env — never `echo ${{ secrets.KIMETSU_REMOTE_TOKEN }}` in a run step. (context: Kimetsu CI remote benchmark — token handling.)" + } + ], + "delivered": [ + "remote-mcp-host-wiring" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.719642698764801 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "the secret token must not end up written into the host config file", + "relevant": [ + "remote-mcp-host-wiring" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.8757492899894714, + "key": "remote-mcp-host-wiring", + "rank_score": 0.954997718334198, + "text": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal. Derive a stable repo id from the git remote: strip `.git`, scheme (`://`), and `user@`, then map non-alphanumerics to '-' and collapse — so both https://github.com/org/repo.git and git@github.com:org/repo.git -> `github-com-org-repo`. Remote install writes ONLY the MCP entry + instructions (no local hooks — the brain is on the server). Codex/Pi don't get --remote (no remote-MCP / no MCP). (context: R2: implementing `kimetsu plugin install --remote` to wire a host at a kimetsu-remote HTTP MCP server.)" + }, + { + "ce": 0.06268610805273056, + "key": "ci-secrets-masking", + "rank_score": 0.8597803115844727, + "text": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output — but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable. Never reconstruct secrets from parts in step output. For kimetsu bench `--remote` CI runs, `KIMETSU_REMOTE_TOKEN` must be in the repository secrets, not in the workflow YAML. Use `${{ secrets.KIMETSU_REMOTE_TOKEN }}` in env — never `echo ${{ secrets.KIMETSU_REMOTE_TOKEN }}` in a run step. (context: Kimetsu CI remote benchmark — token handling.)" + }, + { + "ce": 0.0017127359751611948, + "key": "aws-instance-metadata", + "rank_score": 0.749809980392456, + "text": "project:fact - [tags: aws imds instance-metadata ec2 token] The AWS Instance Metadata Service v2 (IMDSv2) requires a session token: PUT `http://169.254.169.254/latest/api/token` with `X-aws-ec2-metadata-token-ttl-seconds: 21600` to get a token, then GET metadata with `X-aws-ec2-metadata-token: `. IMDSv1 (no token) is disabled on hardened instances. The metadata endpoint is only reachable from within EC2 — a connection timeout means you're not on EC2. Set a short connect timeout (200ms) when probing for the metadata service to avoid slow startup on non-EC2 hosts. (context: Kimetsu Bedrock provider — EC2 instance role credential fallback.)" + }, + { + "ce": 0.004072585608810186, + "key": "mcp-tool-naming", + "rank_score": 0.724313497543335, + "text": "project:fact - [tags: mcp tool naming convention kimetsu] MCP tool names must be valid identifiers for all host agents. Claude Code restricts tool names to `[a-zA-Z0-9_-]` and max 64 chars. Use `snake_case` (kimetsu_brain_context, kimetsu_brain_record) — hyphen is technically allowed but some hosts reject it. Avoid dots (not allowed). Namespace with a prefix (`kimetsu_brain_`) to prevent collisions with other MCP servers. When a tool name changes, update ALL host config files (`.mcp.json`, `openclaw.json`, skill markdown) — mismatched names cause silent failures where the host skips the tool. (context: Kimetsu MCP tool naming convention enforcement.)" + }, + { + "ce": 0.00037542750942520797, + "key": "git-line-endings-windows", + "rank_score": 0.7094454169273376, + "text": "project:fact - [tags: git line-endings windows crlf autocrlf] On Windows, `core.autocrlf=true` (git's default for Windows installs) converts LF to CRLF on checkout and CRLF to LF on commit. This causes spurious diffs when files are edited on Windows then committed — the content is identical but the line endings differ in the index vs the working tree. Fix: set `core.autocrlf=false` and `.gitattributes` with `* text=auto eol=lf` for the repo. For Rust projects, all source files should be LF; only Windows batch scripts need CRLF. Warn: AV scanners that modify newly written files can re-introduce CRLF in files Rust writes. (context: Kimetsu CI — spurious diffs from Windows CRLF conversion.)" + }, + { + "ce": 0.013059692457318306, + "key": "pi-openclaw-extension-api", + "rank_score": 0.6913667321205139, + "text": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`. External commands use `pi.exec()` but `node:child_process` spawn also works. Pi has NO MCP so Kimetsu integrates via TS extension + SKILL.md only. (context: Implementing Pi host target for Kimetsu plugin install/status/uninstall system.)\n\nAlso: [tags: kimetsu host-integration pi openclaw bridge] When integrating Kimetsu with an external host agent (Pi, OpenClaw, etc.), VERIFY the host's real plugin/extension API against its actual repo before writing embedded assets — docs-from-memory are frequently wrong. Concretely corrected during v1.0: Pi uses a default-export factory `export default function(pi)` (not `defineExtension`) with lifecycle events `session_start`/`agent_end`/`session_shutdown`; OpenClaw plugin entry is `index.ts` via `definePluginEntry` from `openclaw/plugin-sdk/plugin-entry` + an `openclaw.plugin.json` manifest, with snake_case hook events `agent_turn_prepare`/`agent_end`/`session_end` (NOT colon-delimited). Always make the embedded hook shell-out a silent no-op if the `kimetsu` binary isn't on PATH so a wrong guess never breaks the host. (context: Adding Pi + OpenClaw as BridgeTarget hosts in v1.0.0; the inferred extension/plugin APIs from docs were wrong and had to be corrected against the real repos.)" + } + ], + "delivered": [ + "remote-mcp-host-wiring" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7192769050598145 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "keep the cleanup logic unit-testable without touching environment variables", + "relevant": [ + "gc-trace-env-guard-placement" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.01578493043780327, + "key": "process-start-time-cross-platform", + "rank_score": 0.954997718334198, + "text": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path). Keep the skew decision logic in a pure function `assess_mcp_skew(servers, binary_mtime, binary_path) -> Outcome` so it can be unit-tested without any live OS state. (context: Q3 — kimetsu doctor version-skew check for stale MCP server processes)" + }, + { + "ce": 0.008414640091359615, + "key": "onnx-model-cache-paths", + "rank_score": 0.9447476863861084, + "text": "project:fact - [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use. In kimetsu, `KIMETSU_EMBEDDER_CACHE` overrides the path and is forwarded when spawning child bench processes — without forwarding it, each child re-downloads the model. (context: Kimetsu brain bench on CI — model cache path handling in child processes.)" + }, + { + "ce": 0.0018113780533894897, + "key": "http-proxy-env", + "rank_score": 0.7985784411430359, + "text": "project:fact - [tags: http proxy environment reqwest rust corporate] reqwest respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` environment variables by default (with `default-tls` or `rustls-tls`). In a corporate network, these may redirect traffic through an intercepting proxy that breaks mTLS or adds latency. To disable proxy usage entirely: `reqwest::ClientBuilder::no_proxy()`. On Windows, reqwest does NOT use the system proxy settings (IE/WinInet) — you must set env vars explicitly. `NO_PROXY=127.0.0.1,localhost` prevents proxying loopback traffic (important for kimetsu-remote local dev). (context: Kimetsu provider calls failing behind corporate proxy on Windows.)" + }, + { + "ce": 0.0010532436426728964, + "key": "gc-trace-env-guard-placement", + "rank_score": 0.7203147411346436, + "text": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site. (context: QQ4 — runs auto-GC on run creation. Env guard placement decision when wiring opportunistic GC into TraceWriter::create.)" + }, + { + "ce": 0.00015398820687551051, + "key": "cargo-lockfile-drift", + "rank_score": 0.6815653443336487, + "text": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this — it errors on any lockfile diff. For library crates, `Cargo.lock` is normally gitignored, but for workspace roots with binary crates it should be committed. Use `cargo update --precise ` to pin a specific dep version without touching unrelated entries. (context: Kimetsu workspace lockfile drift after adding kimetsu-remote crate.)" + }, + { + "ce": 0.003714530263096094, + "key": "cfg-cross-platform-dead-code", + "rank_score": 0.6562572717666626, + "text": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform. (context: Adding parse_unix_ps to kimetsu-cli/src/process.rs — used only on Unix at runtime but needed on Windows for cross-platform unit tests.)" + } + ], + "delivered": [], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7000005841255188 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "how do we stop the server from cloning arbitrary repos clients request?", + "relevant": [ + "remote-ingest-split-roots" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.6168935894966125, + "key": "remote-ingest-split-roots", + "rank_score": 0.9549976587295532, + "text": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races. Re-enable kimetsu_brain_ingest_repo in the tool allowlist only when ingest is configured, and INTERCEPT that tools/call in the remote handler (clone+ingest_repo_at_root) before the normal dispatch (which would walk the wrong dir). Hermetic test: git init a temp repo, register url=local path, ingest, then context retrieves the file capsule via FTS (noop embedder). (context: R3c: server-side ingest for kimetsu-remote — cloning repos so file-capsule retrieval works without a local checkout.)" + }, + { + "ce": 0.018787361681461334, + "key": "http-connection-pooling", + "rank_score": 0.7630175352096558, + "text": "project:fact - [tags: http reqwest connection-pool keep-alive rust] reqwest's `Client` holds a connection pool; always create ONE `Client` instance and clone it for each handler — cloning is cheap (Arc under the hood). Creating a `Client::new()` per request defeats connection pooling and causes TCP connection exhaustion under load. The default pool settings: max_idle_per_host=usize::MAX (unbounded), idle_timeout=90s. For a kimetsu outbound client (LLM provider), set `pool_max_idle_per_host(5)` to limit idle connections. On Windows, the underlying hyper+winapi stack may not reuse connections as aggressively as on Linux — set `connection_verbose(true)` on the builder to confirm reuse. (context: Kimetsu provider HTTP client — connection pooling best practices.)" + }, + { + "ce": 0.018106315284967422, + "key": "git-sparse-checkout", + "rank_score": 0.7520216107368469, + "text": "project:fact - [tags: git sparse-checkout partial-clone bandwidth] `git sparse-checkout init --cone` combined with `git clone --filter=blob:none` (partial clone) fetches only the commit graph and tree objects, not blobs. Individual blobs are fetched on demand when accessed. This cuts clone time for large repos from minutes to seconds. For kimetsu server-side ingest, use `git clone --depth 1 --filter=blob:none` for the initial checkout, then `git sparse-checkout set ` to limit the working tree to indexed directories. On `git fetch --depth 1 origin main` for refresh, blobs in the sparse set are updated lazily. (context: Kimetsu remote ingest — reducing bandwidth and disk usage for large repo checkouts.)" + }, + { + "ce": 0.015473255887627602, + "key": "tokio-shutdown-ordering", + "rank_score": 0.7047540545463562, + "text": "project:fact - [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries — the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks. `axum::Server::with_graceful_shutdown` handles steps 1-2; you must handle 3-5 manually. (context: Kimetsu remote server graceful shutdown implementation.)" + }, + { + "ce": 0.0001006325037451461, + "key": "aws-region-resolution", + "rank_score": 0.643276572227478, + "text": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time. For cross-region inference (e.g. us-west-2 for Claude Opus), set `AWS_REGION=us-west-2`; do NOT rely on the Bedrock endpoint prefix being region-agnostic. (context: Kimetsu Bedrock provider region configuration.)" + }, + { + "ce": 0.00006001582005410455, + "key": "kimetsu-daemon-lifecycle", + "rank_score": 0.6043210625648499, + "text": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required. The server PID is not stored anywhere; use `kimetsu doctor` to enumerate running MCP server processes via OS APIs. On Windows, the server binary may be locked by AV after first launch — `kimetsu update` must stop all running server processes before replacing the binary. (context: Kimetsu daemon lifecycle — process management for updates.)" + } + ], + "delivered": [ + "remote-ingest-split-roots" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.6713894009590149 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "make sure a wrong guess about a host plugin API never breaks that host", + "relevant": [ + "pi-openclaw-extension-api" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9010460376739502, + "key": "pi-openclaw-extension-api", + "rank_score": 0.9549976587295532, + "text": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`. External commands use `pi.exec()` but `node:child_process` spawn also works. Pi has NO MCP so Kimetsu integrates via TS extension + SKILL.md only. (context: Implementing Pi host target for Kimetsu plugin install/status/uninstall system.)\n\nAlso: [tags: kimetsu host-integration pi openclaw bridge] When integrating Kimetsu with an external host agent (Pi, OpenClaw, etc.), VERIFY the host's real plugin/extension API against its actual repo before writing embedded assets — docs-from-memory are frequently wrong. Concretely corrected during v1.0: Pi uses a default-export factory `export default function(pi)` (not `defineExtension`) with lifecycle events `session_start`/`agent_end`/`session_shutdown`; OpenClaw plugin entry is `index.ts` via `definePluginEntry` from `openclaw/plugin-sdk/plugin-entry` + an `openclaw.plugin.json` manifest, with snake_case hook events `agent_turn_prepare`/`agent_end`/`session_end` (NOT colon-delimited). Always make the embedded hook shell-out a silent no-op if the `kimetsu` binary isn't on PATH so a wrong guess never breaks the host. (context: Adding Pi + OpenClaw as BridgeTarget hosts in v1.0.0; the inferred extension/plugin APIs from docs were wrong and had to be corrected against the real repos.)" + }, + { + "ce": 0.005015871487557888, + "key": "bridge-target-enum-seams", + "rank_score": 0.5803130865097046, + "text": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors. (context: Adding BridgeTarget::OpenClaw host to Kimetsu bridge.rs and main.rs in Workstream C)" + }, + { + "ce": 0.0009497363353148103, + "key": "kimetsu-write-tools-gate", + "rank_score": 0.5154125690460205, + "text": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level — disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients. The feature was introduced to prevent malicious prompts from poisoning the brain. (context: Kimetsu write-tools gate — config-driven security for remote deployments.)" + }, + { + "ce": 0.012550226412713528, + "key": "remote-mcp-host-wiring", + "rank_score": 0.5226041078567505, + "text": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal. Derive a stable repo id from the git remote: strip `.git`, scheme (`://`), and `user@`, then map non-alphanumerics to '-' and collapse — so both https://github.com/org/repo.git and git@github.com:org/repo.git -> `github-com-org-repo`. Remote install writes ONLY the MCP entry + instructions (no local hooks — the brain is on the server). Codex/Pi don't get --remote (no remote-MCP / no MCP). (context: R2: implementing `kimetsu plugin install --remote` to wire a host at a kimetsu-remote HTTP MCP server.)" + }, + { + "ce": 0.00009706865239422768, + "key": "kimetsu-daemon-lifecycle", + "rank_score": 0.46945011615753174, + "text": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required. The server PID is not stored anywhere; use `kimetsu doctor` to enumerate running MCP server processes via OS APIs. On Windows, the server binary may be locked by AV after first launch — `kimetsu update` must stop all running server processes before replacing the binary. (context: Kimetsu daemon lifecycle — process management for updates.)" + }, + { + "ce": 0.0024870324414223433, + "key": "aws-instance-metadata", + "rank_score": 0.43716543912887573, + "text": "project:fact - [tags: aws imds instance-metadata ec2 token] The AWS Instance Metadata Service v2 (IMDSv2) requires a session token: PUT `http://169.254.169.254/latest/api/token` with `X-aws-ec2-metadata-token-ttl-seconds: 21600` to get a token, then GET metadata with `X-aws-ec2-metadata-token: `. IMDSv1 (no token) is disabled on hardened instances. The metadata endpoint is only reachable from within EC2 — a connection timeout means you're not on EC2. Set a short connect timeout (200ms) when probing for the metadata service to avoid slow startup on non-EC2 hosts. (context: Kimetsu Bedrock provider — EC2 instance role credential fallback.)" + } + ], + "delivered": [ + "pi-openclaw-extension-api" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.6916542649269104 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "which wire-format trick lets us reuse the existing Anthropic request builder for AWS?", + "relevant": [ + "bedrock-kimetsu-provider" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9861636757850647, + "key": "bedrock-kimetsu-provider", + "rank_score": 0.9549975991249084, + "text": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env. Wire \"bedrock\" into BOTH pipeline.rs provider matches AND the distiller (normalize_distiller_provider + instantiation); the distiller is configured independently so agent-on-Bedrock + harvester-on-direct-Claude works for free. Sign and send the SAME payload bytes; test signing determinism with a fixed SystemTime. (context: Workstream A: adding AWS Bedrock as a provider for the agent + auto-harvester in v1.0.0.)" + }, + { + "ce": 0.003794819815084338, + "key": "http-connection-pooling", + "rank_score": 0.5885782837867737, + "text": "project:fact - [tags: http reqwest connection-pool keep-alive rust] reqwest's `Client` holds a connection pool; always create ONE `Client` instance and clone it for each handler — cloning is cheap (Arc under the hood). Creating a `Client::new()` per request defeats connection pooling and causes TCP connection exhaustion under load. The default pool settings: max_idle_per_host=usize::MAX (unbounded), idle_timeout=90s. For a kimetsu outbound client (LLM provider), set `pool_max_idle_per_host(5)` to limit idle connections. On Windows, the underlying hyper+winapi stack may not reuse connections as aggressively as on Linux — set `connection_verbose(true)` on the builder to confirm reuse. (context: Kimetsu provider HTTP client — connection pooling best practices.)" + }, + { + "ce": 0.0001864425139501691, + "key": "tokio-spawn-blocking", + "rank_score": 0.48362866044044495, + "text": "project:fact - [tags: tokio spawn_blocking thread-pool rust blocking] `tokio::task::spawn_blocking` places work on a dedicated blocking thread pool (default up to 512 threads, configurable via `Builder::max_blocking_threads`). Each call creates or reuses a thread — there's no true pooling, threads may be created on demand. For many short-duration blocking calls (e.g. per-query SQLite reads), thread creation overhead may dominate. Prefer batching: collect N queries, then one `spawn_blocking` to run them all. Alternatively, keep a persistent blocking task that reads from an mpsc channel. Profile with `tokio-console` if you suspect spawn_blocking overhead. (context: Kimetsu retrieval server — per-query spawn_blocking was adding ~0.3ms overhead.)" + }, + { + "ce": 0.00011366537364665419, + "key": "import-dedup-seen-ids", + "rank_score": 0.4531535506248474, + "text": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount — both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise. (context: Implementing brain export/import (Q5). First naive approach used a single `seen_ids` set local to the function; the dedup test caught it on the second-import assertion.)" + }, + { + "ce": 0.0001069825011654757, + "key": "sqlite-json1-extract", + "rank_score": 0.4347028434276581, + "text": "project:fact - [tags: sqlite json1 json_extract rusqlite] SQLite's json1 extension (built in since 3.38.0) lets you index and query JSONB columns with `json_extract(col, '$.field')`. To create a partial index over a JSON field: `CREATE INDEX idx ON memories (json_extract(metadata, '$.scope')) WHERE json_extract(metadata, '$.scope') IS NOT NULL;`. Use `json_each` for array fields. On older SQLite builds (rusqlite links whatever the system provides), check for json1 with `SELECT json('{}');` — an error means it's absent. Always prefer column storage over JSON blobs for frequently queried fields. (context: Kimetsu brain querying metadata scopes without migrating a separate column.)" + }, + { + "ce": 0.00010027328244177625, + "key": "sqlite-fts5-tokenizer", + "rank_score": 0.434348464012146, + "text": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon. If you switch tokenizers on an existing FTS5 table, you MUST rebuild the shadow tables: `INSERT INTO tbl(tbl) VALUES('rebuild');` — a schema-only change leaves the inverted index unusable. The `porter` stemmer is available as `tokenize='porter unicode61'` but aggressively strips suffixes and hurts precision on technical terms. (context: Kimetsu brain FTS5 index tuning for Rust identifier retrieval.)" + } + ], + "delivered": [ + "bedrock-kimetsu-provider" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7403530478477478 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "the self-update froze because something was still holding the executable", + "relevant": [ + "windows-update-process-locking" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.0005139941931702197, + "key": "clap-version-build-flavor", + "rank_score": 0.9549975991249084, + "text": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds. (context: QQ2: --version build flavor + plugin install self-check)" + }, + { + "ce": 0.0005262026097625494, + "key": "tokio-select-cancellation", + "rank_score": 0.7923360466957092, + "text": "project:fact - [tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded. For correctness, cancelled futures must be cancellation-safe: holding no partially committed state. `tokio::sync::watch::Receiver::changed()` is cancellation-safe; `tokio::sync::mpsc::Sender::send()` is NOT (the item is lost). In kimetsu shutdown, use a `CancellationToken` and `select!` branches that are all cancellation-safe. (context: Kimetsu remote graceful shutdown — race between incoming requests and shutdown signal.)" + }, + { + "ce": 0.00016018831229303032, + "key": "cargo-dev-dep-leak", + "rank_score": 0.7986634969711304, + "text": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates. Run `cargo tree --features ` to trace which crate activated an unexpected feature. (context: Kimetsu testing infra — a dev-dep was activating the embeddings feature in non-test builds.)" + }, + { + "ce": 0.00027088820934295654, + "key": "sqlite-vacuum-wal-checkpoint", + "rank_score": 0.7555246353149414, + "text": "project:fact - [tags: rust sqlite vacuum rusqlite windows] When implementing SQLite VACUUM in rusqlite: VACUUM cannot run inside a transaction. rusqlite's Connection does not hold an implicit transaction, so `conn.execute_batch(\"VACUUM;\")` works directly. After VACUUM, run `PRAGMA wal_checkpoint(TRUNCATE);` before measuring file size — on Windows the WAL file can hold significant space that isn't reflected in the main db file until the checkpoint runs. (context: Implementing kimetsu brain compact (Q8) — SQLite VACUUM + WAL checkpoint for accurate post-compact file size.)" + }, + { + "ce": 0.00019966649415437132, + "key": "windows-exit-codes", + "rank_score": 0.7296368479728699, + "text": "project:fact - [tags: windows exit-codes rust process child] On Windows, process exit codes are 32-bit unsigned integers (DWORD). Rust's `ExitStatus::code()` returns `Option` — it's `None` if the process was killed by a signal (which Windows doesn't use; instead, TerminateProcess with a code). Conventional codes: 0=success, 1=generic error, 0xC0000005=access violation. Programs that call `std::process::exit(-1)` on Windows produce exit code 0xFFFFFFFF (4294967295), not -1. When checking for success in a subprocess chain, always check `status.success()` rather than `status.code() == Some(0)` to handle this portably. (context: Kimetsu update binary replacement — exit code handling.)" + }, + { + "ce": 0.0002948862675111741, + "key": "testing-time-dependent-flakes", + "rank_score": 0.7238357663154602, + "text": "project:fact - [tags: testing time flaky clock mock rust] Tests that depend on wall-clock time are inherently flaky under load (slow CI runners, GC pauses). Abstract time behind a trait (`Clock: Fn() -> SystemTime`) injected at construction, and supply a fake in tests. For tests checking that something happened \"within N seconds\", use a generous multiple of the expected duration (10x is not unreasonable for CI). `std::thread::sleep` in tests is a smell — prefer channel synchronization or a condvar instead of timing-based waits. If you must use sleep, set `KIMETSU_TEST_TIMEOUT_SCALE` to stretch timeouts in slow environments. (context: Kimetsu GC and TTL tests — time-dependent flakes on loaded CI.)" + } + ], + "delivered": [], + "excluded_gold": [ + { + "ce": null, + "key": "windows-update-process-locking" + } + ], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.6585901975631714 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "our notes about the extension API turned out wrong once we read the actual repo", + "relevant": [ + "pi-openclaw-extension-api" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.7192176580429077, + "key": "pi-openclaw-extension-api", + "rank_score": 0.9549975395202637, + "text": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`. External commands use `pi.exec()` but `node:child_process` spawn also works. Pi has NO MCP so Kimetsu integrates via TS extension + SKILL.md only. (context: Implementing Pi host target for Kimetsu plugin install/status/uninstall system.)\n\nAlso: [tags: kimetsu host-integration pi openclaw bridge] When integrating Kimetsu with an external host agent (Pi, OpenClaw, etc.), VERIFY the host's real plugin/extension API against its actual repo before writing embedded assets — docs-from-memory are frequently wrong. Concretely corrected during v1.0: Pi uses a default-export factory `export default function(pi)` (not `defineExtension`) with lifecycle events `session_start`/`agent_end`/`session_shutdown`; OpenClaw plugin entry is `index.ts` via `definePluginEntry` from `openclaw/plugin-sdk/plugin-entry` + an `openclaw.plugin.json` manifest, with snake_case hook events `agent_turn_prepare`/`agent_end`/`session_end` (NOT colon-delimited). Always make the embedded hook shell-out a silent no-op if the `kimetsu` binary isn't on PATH so a wrong guess never breaks the host. (context: Adding Pi + OpenClaw as BridgeTarget hosts in v1.0.0; the inferred extension/plugin APIs from docs were wrong and had to be corrected against the real repos.)" + }, + { + "ce": 0.0014331901911646128, + "key": "http-timeout-layering", + "rank_score": 0.5280137658119202, + "text": "project:fact - [tags: http reqwest timeout connect read total rust] reqwest has three distinct timeout knobs: `connect_timeout`, `read_timeout`, and `timeout` (total). They compose: if all three are set, the request fails at whichever fires first. For LLM API calls with streaming responses, `read_timeout` must be larger than the slowest expected token (often 30-60s) while `connect_timeout` can be tight (3-5s). `timeout` should be your SLA ceiling. If you set only `timeout`, a slow connect eats into the overall budget. For kimetsu-remote, set both `connect_timeout(5s)` and `timeout(120s)` — the LLM call is the bottleneck. (context: Kimetsu provider timeouts — request timing out during streaming.)" + }, + { + "ce": 0.0004094241885468364, + "key": "onnx-tokenizer-mismatch", + "rank_score": 0.49107715487480164, + "text": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly — specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings — cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo. Validate by checking a reference embedding against the HuggingFace Python output. (context: Kimetsu custom ONNX reranker loading — wrong tokenizer produced degraded retrieval.)" + }, + { + "ce": 0.00004532406819635071, + "key": "kimetsu-mrr-metric", + "rank_score": 0.4869976043701172, + "text": "project:fact - [tags: kimetsu bench mrr recall metrics evaluation] kimetsu bench reports MRR (Mean Reciprocal Rank) and Recall@K. MRR is 1/rank_of_first_relevant_result, averaged across cases; it penalizes models that rank the correct answer 2nd or 3rd. Recall@K is the fraction of cases where at least one relevant answer appears in the top K. For multi-answer cases, recall@K considers a case satisfied if ANY relevant key appears in top K. MRR is the primary metric for knowledge retrieval because users read the first result first. A 0.01 MRR difference on a 100-case dataset corresponds to about 1 case changing from rank-2 to rank-1. Noise of ~2-3 cases is expected run-to-run. (context: Kimetsu benchmark metric interpretation.)" + }, + { + "ce": 0.0001560735545353964, + "key": "harbor-terminal-bench-subprocess-isolation", + "rank_score": 0.45213741064071655, + "text": "project:fact - [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd). Worker re-derives auth internally from .env so the OAuth token never lands in argv; it writes {run,grade} JSON the parent reads back. One Harbor invocation per process always works (baseline-alone passed). (context: kbench multi-trial sweeps crashed on every trial after the 1st; diagnosed as Harbor/pyiceberg os.getcwd staleness on WSL2.)" + }, + { + "ce": 0.00004010665725218132, + "key": "kimetsu-proactive-hooks", + "rank_score": 0.4535027742385864, + "text": "project:fact - [tags: kimetsu proactive hooks context injection] kimetsu's proactive context injection runs before each agent turn (pre-turn hook) and injects relevant memories into the system prompt prefix. The hook invocation adds latency to the first token: embedding inference + vector search + reranking + context formatting. On a cold start, this can be 1-3 seconds. The hook is optional — disable with `KIMETSU_PROACTIVE=0`. The semantic floor (min cosine similarity) filters noise capsules before injection; setting the floor too low injects irrelevant memories and wastes context window tokens. The proactive hook does NOT trigger the distiller — that runs post-session only. (context: Kimetsu proactive context injection — latency and floor tuning.)" + } + ], + "delivered": [ + "pi-openclaw-extension-api" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.6580018997192383 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "half the benchmark trials die right after the first one finishes", + "relevant": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.011913119815289974, + "key": "harbor-terminal-bench-subprocess-isolation", + "rank_score": 0.9549975395202637, + "text": "project:fact - [2026-09-05] [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd). Worker re-derives auth internally from .env so the OAuth token never lands in argv; it writes {run,grade} JSON the parent reads back. One Harbor invocation per process always works (baseline-alone passed). (context: kbench multi-trial sweeps crashed on every trial after the 1st; diagnosed as Harbor/pyiceberg os.getcwd staleness on WSL2.)" + }, + { + "ce": 0.00011162945884279907, + "key": "sqlite-busy-timeout-wal", + "rank_score": 0.8020118474960327, + "text": "project:fact - [2026-09-05] [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch. Set the timeout before any transaction, not inside one — it is a connection-level property. (context: Kimetsu brain writer and reader processes sharing the same SQLite brain database.)" + }, + { + "ce": 0.00003375009691808373, + "key": "sqlite-page-size", + "rank_score": 0.6440929770469666, + "text": "project:fact - [2026-09-05] [tags: sqlite page_size performance rusqlite] SQLite's default page_size is 4096 bytes. For a write-heavy brain database with large BLOB payloads (embedding vectors), raising page_size to 16384 reduces fragmentation and improves sequential scan throughput. `PRAGMA page_size = 16384;` must be set BEFORE the first table is created — changing it on an existing database requires a VACUUM afterward to rebuild all pages. Verify it took effect with `PRAGMA page_size;` after VACUUM. rusqlite's `Connection::open` runs no implicit PRAGMA, so set this in the connection init path. (context: Tuning the kimetsu brain SQLite schema for embedding vector storage.)" + }, + { + "ce": 0.000717772520147264, + "key": "tokio-select-cancellation", + "rank_score": 0.705362856388092, + "text": "project:fact - [2026-09-05] [tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded. For correctness, cancelled futures must be cancellation-safe: holding no partially committed state. `tokio::sync::watch::Receiver::changed()` is cancellation-safe; `tokio::sync::mpsc::Sender::send()` is NOT (the item is lost). In kimetsu shutdown, use a `CancellationToken` and `select!` branches that are all cancellation-safe. (context: Kimetsu remote graceful shutdown — race between incoming requests and shutdown signal.)" + }, + { + "ce": 0.00012509847874753177, + "key": "kimetsu-bench-remote-embedder-singleton", + "rank_score": 0.7052021622657776, + "text": "project:fact - [2026-09-05] [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval. Workaround: run ONE `--embedders` value per invocation and kill the remote process between runs. The local bench path is not affected (each combo is process-isolated via `--single` child spawn). (context: Kimetsu brain bench --remote known issue — multi-embedder contamination.)" + }, + { + "ce": 0.0008994417148642242, + "key": "kimetsu-mrr-metric", + "rank_score": 0.8344508409500122, + "text": "project:fact - [2026-09-05] [tags: kimetsu bench mrr recall metrics evaluation] kimetsu bench reports MRR (Mean Reciprocal Rank) and Recall@K. MRR is 1/rank_of_first_relevant_result, averaged across cases; it penalizes models that rank the correct answer 2nd or 3rd. Recall@K is the fraction of cases where at least one relevant answer appears in the top K. For multi-answer cases, recall@K considers a case satisfied if ANY relevant key appears in top K. MRR is the primary metric for knowledge retrieval because users read the first result first. A 0.01 MRR difference on a 100-case dataset corresponds to about 1 case changing from rank-2 to rank-1. Noise of ~2-3 cases is expected run-to-run. (context: Kimetsu benchmark metric interpretation.)" + } + ], + "delivered": [], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.5999535918235779 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "I need this parser visible to tests on every OS even though only one OS calls it", + "relevant": [ + "cfg-cross-platform-dead-code" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.5910465121269226, + "key": "cfg-cross-platform-dead-code", + "rank_score": 0.9549975395202637, + "text": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform. (context: Adding parse_unix_ps to kimetsu-cli/src/process.rs — used only on Unix at runtime but needed on Windows for cross-platform unit tests.)" + }, + { + "ce": 0.0037626102566719055, + "key": "windows-update-process-locking", + "rank_score": 0.7882997393608093, + "text": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics — mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code. (context: Q2 — kimetsu update preflight for locked binary on Windows)" + }, + { + "ce": 0.0036146650090813637, + "key": "gc-trace-env-guard-placement", + "rank_score": 0.7358978390693665, + "text": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site. (context: QQ4 — runs auto-GC on run creation. Env guard placement decision when wiring opportunistic GC into TraceWriter::create.)" + }, + { + "ce": 0.0004118347424082458, + "key": "ci-matrix-explosion", + "rank_score": 0.7179288864135742, + "text": "project:fact - [tags: ci github-actions matrix jobs resources] A CI matrix combining OS (3) x Rust toolchain (3) x features (2) = 18 jobs. Each spawns a runner; at $0.008/min for Ubuntu and $0.016/min for Windows, a 10-minute build costs $2.40 per push. Reduce: test the full matrix only on PRs to main; on feature branches, test only Linux+stable. Use `fail-fast: false` to see all failures, not just the first. Combine related checks (clippy + test) in one job when they share build artifacts. For Windows-specific tests, run only the OS-specific job to reduce cost. (context: Kimetsu CI matrix cost optimization.)" + }, + { + "ce": 0.0005892408080399036, + "key": "ci-cache-keys", + "rank_score": 0.6936484575271606, + "text": "project:fact - [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key — macOS and Windows have incompatible artifact formats. Separate the registry cache from the build cache: the registry (downloaded crates) changes rarely, the build cache changes every push. Bust the build cache on major dependency changes by adding a manual cache version suffix to the key. (context: Kimetsu CI — cache invalidation strategy.)" + }, + { + "ce": 0.0010891815181821585, + "key": "aws-sigv4-bedrock-blocking", + "rank_score": 0.6595454812049866, + "text": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed. aws-smithy-runtime-api required as a companion to supply Identity. (context: Implementing BedrockProvider for Kimetsu with blocking reqwest + SigV4 signing, no tokio/aws-sdk)" + } + ], + "delivered": [ + "cfg-cross-platform-dead-code" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.6943885087966919 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "the config file content refuses to parse even though the TOML looks valid", + "relevant": [ + "toml-value-parse" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.8025842905044556, + "key": "toml-value-parse", + "rank_score": 0.9549974799156189, + "text": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table. (context: Implementing config get/set with toml::Value navigation; str.parse() failed with 'unexpected content' error on document strings.)" + }, + { + "ce": 0.0006555195432156324, + "key": "onnx-dim-mismatch", + "rank_score": 0.5481277704238892, + "text": "project:fact - [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results — the ANN index shape mismatch isn't always caught at runtime. kimetsu detects this by storing `embedder_id` in the brain schema and refusing to query if the configured embedder differs from what was used at ingest time. Mitigation: re-ingest all memories with the new model, or keep per-memory vector dim metadata. (context: Kimetsu embedder migration — detecting dimension mismatch at startup.)" + }, + { + "ce": 0.0016798857832327485, + "key": "cargo-target-dir-sharing", + "rank_score": 0.5431336164474487, + "text": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps — use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination. (context: Kimetsu development on Windows with Windows Defender causing intermittent link failures.)" + }, + { + "ce": 0.0006971292896196246, + "key": "aws-sigv4-bedrock-blocking", + "rank_score": 0.5184964537620544, + "text": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed. aws-smithy-runtime-api required as a companion to supply Identity. (context: Implementing BedrockProvider for Kimetsu with blocking reqwest + SigV4 signing, no tokio/aws-sdk)" + }, + { + "ce": 0.0001510000292910263, + "key": "git-line-endings-windows", + "rank_score": 0.5287421941757202, + "text": "project:fact - [tags: git line-endings windows crlf autocrlf] On Windows, `core.autocrlf=true` (git's default for Windows installs) converts LF to CRLF on checkout and CRLF to LF on commit. This causes spurious diffs when files are edited on Windows then committed — the content is identical but the line endings differ in the index vs the working tree. Fix: set `core.autocrlf=false` and `.gitattributes` with `* text=auto eol=lf` for the repo. For Rust projects, all source files should be LF; only Windows batch scripts need CRLF. Warn: AV scanners that modify newly written files can re-introduce CRLF in files Rust writes. (context: Kimetsu CI — spurious diffs from Windows CRLF conversion.)" + }, + { + "ce": 0.0006523485062643886, + "key": "cargo-profile-override", + "rank_score": 0.5179646611213684, + "text": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug. The downside: rebuild time increases for that crate. For overflow-checks, `overflow-checks = false` per package speeds up hot loops. Never disable overflow-checks in release for business-critical data-mutating code. `[profile.release] strip = \"debuginfo\"` reduces binary size with minimal impact on stack traces. (context: Kimetsu dev experience — embedding inference was 10x slower in debug builds.)" + } + ], + "delivered": [ + "toml-value-parse" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7848173379898071 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "the remote server must refresh its checkout before answering file queries", + "relevant": [ + "remote-ingest-split-roots" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.7482901811599731, + "key": "remote-ingest-split-roots", + "rank_score": 0.9549974203109741, + "text": "project:fact - [2026-09-05] [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races. Re-enable kimetsu_brain_ingest_repo in the tool allowlist only when ingest is configured, and INTERCEPT that tools/call in the remote handler (clone+ingest_repo_at_root) before the normal dispatch (which would walk the wrong dir). Hermetic test: git init a temp repo, register url=local path, ingest, then context retrieves the file capsule via FTS (noop embedder). (context: R3c: server-side ingest for kimetsu-remote — cloning repos so file-capsule retrieval works without a local checkout.)" + }, + { + "ce": 0.00030421308474615216, + "key": "sqlite-busy-timeout-wal", + "rank_score": 0.5937632918357849, + "text": "project:fact - [2026-09-05] [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch. Set the timeout before any transaction, not inside one — it is a connection-level property. (context: Kimetsu brain writer and reader processes sharing the same SQLite brain database.)" + }, + { + "ce": 0.04614301025867462, + "key": "git-sparse-checkout", + "rank_score": 0.8096758723258972, + "text": "project:fact - [2026-09-05] [tags: git sparse-checkout partial-clone bandwidth] `git sparse-checkout init --cone` combined with `git clone --filter=blob:none` (partial clone) fetches only the commit graph and tree objects, not blobs. Individual blobs are fetched on demand when accessed. This cuts clone time for large repos from minutes to seconds. For kimetsu server-side ingest, use `git clone --depth 1 --filter=blob:none` for the initial checkout, then `git sparse-checkout set ` to limit the working tree to indexed directories. On `git fetch --depth 1 origin main` for refresh, blobs in the sparse set are updated lazily. (context: Kimetsu remote ingest — reducing bandwidth and disk usage for large repo checkouts.)" + }, + { + "ce": 0.11483196169137955, + "key": "tokio-shutdown-ordering", + "rank_score": 0.8168694376945496, + "text": "project:fact - [2026-09-05] [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries — the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks. `axum::Server::with_graceful_shutdown` handles steps 1-2; you must handle 3-5 manually. (context: Kimetsu remote server graceful shutdown implementation.)" + }, + { + "ce": 0.00004288875061320141, + "key": "kimetsu-daemon-lifecycle", + "rank_score": 0.6114457249641418, + "text": "project:fact - [2026-09-05] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required. The server PID is not stored anywhere; use `kimetsu doctor` to enumerate running MCP server processes via OS APIs. On Windows, the server binary may be locked by AV after first launch — `kimetsu update` must stop all running server processes before replacing the binary. (context: Kimetsu daemon lifecycle — process management for updates.)" + }, + { + "ce": 0.00010132618626812473, + "key": "kimetsu-eval-fixture-shape", + "rank_score": 0.6638619899749756, + "text": "project:fact - [2026-09-05] [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` — a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases). Keys must be unique across the dataset. The bench currently does not validate keys at load — it fails later with an `unwrap()` on a missing HashMap entry. (context: Kimetsu bench dataset shape and validation.)" + } + ], + "delivered": [ + "remote-ingest-split-roots" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.6733524799346924 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "tests must not climb to a parent git repository when resolving project paths", + "relevant": [ + "init-project-git-boundary" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.1220366507768631, + "key": "remote-ingest-split-roots", + "rank_score": 0.457448273897171, + "text": "project:fact - [2026-09-05] [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races. Re-enable kimetsu_brain_ingest_repo in the tool allowlist only when ingest is configured, and INTERCEPT that tools/call in the remote handler (clone+ingest_repo_at_root) before the normal dispatch (which would walk the wrong dir). Hermetic test: git init a temp repo, register url=local path, ingest, then context retrieves the file capsule via FTS (noop embedder). (context: R3c: server-side ingest for kimetsu-remote — cloning repos so file-capsule retrieval works without a local checkout.)" + }, + { + "ce": 0.0009661921067163348, + "key": "bridge-target-enum-seams", + "rank_score": 0.4586432874202728, + "text": "project:fact - [2026-09-05] [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors. (context: Adding BridgeTarget::OpenClaw host to Kimetsu bridge.rs and main.rs in Workstream C)" + }, + { + "ce": 0.9997298121452332, + "key": "init-project-git-boundary", + "rank_score": 0.9549974203109741, + "text": "project:fact - [2026-09-05] [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain. (context: QQ3 — kimetsu setup integration test failed because init_project climbed git tree to real ~/.kimetsu instead of temp workspace)" + }, + { + "ce": 0.004338625352829695, + "key": "windows-update-process-locking", + "rank_score": 0.49086371064186096, + "text": "project:fact - [2026-09-05] [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics — mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code. (context: Q2 — kimetsu update preflight for locked binary on Windows)" + }, + { + "ce": 0.0007567324209958315, + "key": "cargo-patch-section", + "rank_score": 0.45709311962127686, + "text": "project:fact - [2026-09-05] [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace — including transitive deps — that depend on `my-crate`. Remove the patch before publishing. Using `[replace]` is deprecated since Cargo 0.47; always use `[patch]`. When patching a crate pinned via an exact version specifier, the patch must satisfy that exact version. Use `cargo tree` to confirm the patch is applied. (context: Kimetsu patching upstream rusqlite for a Windows-specific WAL fix.)" + }, + { + "ce": 0.001768387039192021, + "key": "ci-secrets-masking", + "rank_score": 0.5543885827064514, + "text": "project:fact - [2026-09-05] [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output — but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable. Never reconstruct secrets from parts in step output. For kimetsu bench `--remote` CI runs, `KIMETSU_REMOTE_TOKEN` must be in the repository secrets, not in the workflow YAML. Use `${{ secrets.KIMETSU_REMOTE_TOKEN }}` in env — never `echo ${{ secrets.KIMETSU_REMOTE_TOKEN }}` in a run step. (context: Kimetsu CI remote benchmark — token handling.)" + } + ], + "delivered": [ + "init-project-git-boundary" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7706935405731201 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "how do I test request signing deterministically when timestamps change every run?", + "relevant": [ + "bedrock-kimetsu-provider" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.011558383703231812, + "key": "aws-sigv4-bedrock-blocking", + "rank_score": 0.6793603301048279, + "text": "project:fact - [2026-09-05] [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed. aws-smithy-runtime-api required as a companion to supply Identity. (context: Implementing BedrockProvider for Kimetsu with blocking reqwest + SigV4 signing, no tokio/aws-sdk)" + }, + { + "ce": 0.003987978212535381, + "key": "cargo-build-script-rerun", + "rank_score": 0.7318968176841736, + "text": "project:fact - [2026-09-05] [tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory. If the build script generates code from a schema file, emit `rerun-if-changed=schema.json`. If there are NO inputs (e.g. the script only inspects env vars), emit `cargo:rerun-if-changed=` with an empty string to suppress re-runs entirely. Missing this directive is the most common cause of unexpectedly slow incremental builds. (context: kimetsu-cli build.rs for embedding version stamps.)" + }, + { + "ce": 0.00022875890135765076, + "key": "cargo-dev-dep-leak", + "rank_score": 0.6416505575180054, + "text": "project:fact - [2026-09-05] [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates. Run `cargo tree --features ` to trace which crate activated an unexpected feature. (context: Kimetsu testing infra — a dev-dep was activating the embeddings feature in non-test builds.)" + }, + { + "ce": 0.000475120497867465, + "key": "tokio-select-cancellation", + "rank_score": 0.6360791921615601, + "text": "project:fact - [2026-09-05] [tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded. For correctness, cancelled futures must be cancellation-safe: holding no partially committed state. `tokio::sync::watch::Receiver::changed()` is cancellation-safe; `tokio::sync::mpsc::Sender::send()` is NOT (the item is lost). In kimetsu shutdown, use a `CancellationToken` and `select!` branches that are all cancellation-safe. (context: Kimetsu remote graceful shutdown — race between incoming requests and shutdown signal.)" + }, + { + "ce": 0.2091846615076065, + "key": "testing-snapshot-churn", + "rank_score": 0.9549975991249084, + "text": "project:fact - [2026-09-05] [tags: testing snapshot insta assert churn rust] Snapshot tests (e.g. with the `insta` crate) fail whenever the output changes, even for intended changes. In CI, they fail loudly; locally, `cargo insta review` walks you through accepting or rejecting changes. Snapshot churn becomes a problem when output includes timestamps, process IDs, or randomly-ordered maps. Redact these before snapshotting: use `insta::with_settings!({redactions: [\".timestamp\" => \"[TIMESTAMP]\"]})`. For JSON output, sort maps and arrays before comparing. Keep snapshot files in `src/snapshots/` and always commit them — an untracked snapshot file causes the next CI run to fail with a different error than expected. (context: Kimetsu CLI output snapshot tests — reducing churn.)" + }, + { + "ce": 0.00009054136171471328, + "key": "ci-cache-keys", + "rank_score": 0.6517179012298584, + "text": "project:fact - [2026-09-05] [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key — macOS and Windows have incompatible artifact formats. Separate the registry cache from the build cache: the registry (downloaded crates) changes rarely, the build cache changes every push. Bust the build cache on major dependency changes by adding a manual cache version suffix to the key. (context: Kimetsu CI — cache invalidation strategy.)" + } + ], + "delivered": [], + "excluded_gold": [ + { + "ce": null, + "key": "bedrock-kimetsu-provider" + } + ], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7194211483001709 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "adding a new variant to the host target enum - which places will I forget to update?", + "relevant": [ + "bridge-target-enum-seams" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.885578989982605, + "key": "bridge-target-enum-seams", + "rank_score": 0.9549973607063293, + "text": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors. (context: Adding BridgeTarget::OpenClaw host to Kimetsu bridge.rs and main.rs in Workstream C)" + }, + { + "ce": 0.00006537056469824165, + "key": "windows-file-locking-av", + "rank_score": 0.7841724753379822, + "text": "project:fact - [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine. For CI, use GitHub-hosted Windows runners which don't have real-time AV. Alternatively, build to a different directory with `CARGO_TARGET_DIR=C:\\tmp\\target`. The error is non-deterministic — it only appears when AV scanning races with the link step. (context: Kimetsu development on Windows — intermittent linker errors.)" + }, + { + "ce": 0.0018848482286557555, + "key": "windows-update-process-locking", + "rank_score": 0.7427529096603394, + "text": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics — mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code. (context: Q2 — kimetsu update preflight for locked binary on Windows)" + }, + { + "ce": 0.012925393879413605, + "key": "pi-openclaw-extension-api", + "rank_score": 0.5947896838188171, + "text": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`. External commands use `pi.exec()` but `node:child_process` spawn also works. Pi has NO MCP so Kimetsu integrates via TS extension + SKILL.md only. (context: Implementing Pi host target for Kimetsu plugin install/status/uninstall system.)\n\nAlso: [tags: kimetsu host-integration pi openclaw bridge] When integrating Kimetsu with an external host agent (Pi, OpenClaw, etc.), VERIFY the host's real plugin/extension API against its actual repo before writing embedded assets — docs-from-memory are frequently wrong. Concretely corrected during v1.0: Pi uses a default-export factory `export default function(pi)` (not `defineExtension`) with lifecycle events `session_start`/`agent_end`/`session_shutdown`; OpenClaw plugin entry is `index.ts` via `definePluginEntry` from `openclaw/plugin-sdk/plugin-entry` + an `openclaw.plugin.json` manifest, with snake_case hook events `agent_turn_prepare`/`agent_end`/`session_end` (NOT colon-delimited). Always make the embedded hook shell-out a silent no-op if the `kimetsu` binary isn't on PATH so a wrong guess never breaks the host. (context: Adding Pi + OpenClaw as BridgeTarget hosts in v1.0.0; the inferred extension/plugin APIs from docs were wrong and had to be corrected against the real repos.)" + }, + { + "ce": 0.00012292563042137772, + "key": "toml-value-parse", + "rank_score": 0.596611738204956, + "text": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table. (context: Implementing config get/set with toml::Value navigation; str.parse() failed with 'unexpected content' error on document strings.)" + }, + { + "ce": 0.00012579717440530658, + "key": "mcp-schema-validation", + "rank_score": 0.6033823490142822, + "text": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array — omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error. Use `serde(default)` for optional fields. When adding a new tool, check the schema in the tools/list response manually by piping a JSON-RPC tools/list request to `./kimetsu mcp`. (context: Kimetsu MCP tool schema — required field validation.)" + } + ], + "delivered": [ + "bridge-target-enum-seams" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7972773313522339 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "how do I enable GPU acceleration for kimetsu embedding inference", + "relevant": [], + "stages": [ + { + "candidates": [ + { + "ce": 0.15472111105918884, + "key": "onnx-batch-padding", + "rank_score": 0.9549974203109741, + "text": "project:fact - [tags: onnx batch padding attention-mask embeddings] When running batch inference with an ONNX model, all inputs in the batch must be padded to the same sequence length. The `attention_mask` tensor marks which tokens are real (1) and which are padding (0). Failing to pass `attention_mask` causes the model to average-pool over padding tokens, producing systematically lower-norm embeddings. With ORT (ort crate), construct the mask as a 2-D i64 tensor `[batch, seq_len]` with 1s for real tokens and 0s for padding. For variable-length batches, pad to `max(lengths)` in the batch, not to `model.max_length`. (context: Kimetsu embedding batch inference with ORT — missing attention mask caused MRR degradation.)" + }, + { + "ce": 0.025743583217263222, + "key": "cargo-dev-dep-leak", + "rank_score": 0.942438542842865, + "text": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates. Run `cargo tree --features ` to trace which crate activated an unexpected feature. (context: Kimetsu testing infra — a dev-dep was activating the embeddings feature in non-test builds.)" + }, + { + "ce": 0.0017559804255142808, + "key": "sqlite-foreign-keys-default-off", + "rank_score": 0.8685887455940247, + "text": "project:fact - [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting — every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing. Check your schema with `PRAGMA foreign_key_list(table_name);` and your current setting with `PRAGMA foreign_keys;`. rusqlite does not enable foreign keys automatically. (context: Kimetsu brain schema — memory_tags table has FK to memories table, discovered ON DELETE CASCADE wasn't firing.)" + }, + { + "ce": 0.10450010746717453, + "key": "kimetsu-proactive-hooks", + "rank_score": 0.8514703512191772, + "text": "project:fact - [tags: kimetsu proactive hooks context injection] kimetsu's proactive context injection runs before each agent turn (pre-turn hook) and injects relevant memories into the system prompt prefix. The hook invocation adds latency to the first token: embedding inference + vector search + reranking + context formatting. On a cold start, this can be 1-3 seconds. The hook is optional — disable with `KIMETSU_PROACTIVE=0`. The semantic floor (min cosine similarity) filters noise capsules before injection; setting the floor too low injects irrelevant memories and wastes context window tokens. The proactive hook does NOT trigger the distiller — that runs post-session only. (context: Kimetsu proactive context injection — latency and floor tuning.)" + }, + { + "ce": 0.39945241808891296, + "key": "cargo-profile-override", + "rank_score": 0.8490205407142639, + "text": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug. The downside: rebuild time increases for that crate. For overflow-checks, `overflow-checks = false` per package speeds up hot loops. Never disable overflow-checks in release for business-critical data-mutating code. `[profile.release] strip = \"debuginfo\"` reduces binary size with minimal impact on stack traces. (context: Kimetsu dev experience — embedding inference was 10x slower in debug builds.)" + }, + { + "ce": 0.33488237857818604, + "key": "mcp-tool-timeouts", + "rank_score": 0.8301034569740295, + "text": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking — in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize — keep it in a process-global `OnceLock`). The reranker adds another 200-800ms; jina-tiny is the fastest. If tool calls are still slow, log the per-stage latency with `tracing::info!` at DEBUG level and profile under load. (context: Kimetsu MCP tool latency optimization.)" + } + ], + "delivered": [ + "cargo-profile-override", + "mcp-tool-timeouts" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7514079213142395 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "how do I throttle kimetsu API spend per month", + "relevant": [], + "stages": [ + { + "candidates": [ + { + "ce": 0.0019505221862345934, + "key": "cargo-dev-dep-leak", + "rank_score": 0.9549973607063293, + "text": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates. Run `cargo tree --features ` to trace which crate activated an unexpected feature. (context: Kimetsu testing infra — a dev-dep was activating the embeddings feature in non-test builds.)" + }, + { + "ce": 0.0959700345993042, + "key": "pi-openclaw-extension-api", + "rank_score": 0.820023775100708, + "text": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`. External commands use `pi.exec()` but `node:child_process` spawn also works. Pi has NO MCP so Kimetsu integrates via TS extension + SKILL.md only. (context: Implementing Pi host target for Kimetsu plugin install/status/uninstall system.)\n\nAlso: [tags: kimetsu host-integration pi openclaw bridge] When integrating Kimetsu with an external host agent (Pi, OpenClaw, etc.), VERIFY the host's real plugin/extension API against its actual repo before writing embedded assets — docs-from-memory are frequently wrong. Concretely corrected during v1.0: Pi uses a default-export factory `export default function(pi)` (not `defineExtension`) with lifecycle events `session_start`/`agent_end`/`session_shutdown`; OpenClaw plugin entry is `index.ts` via `definePluginEntry` from `openclaw/plugin-sdk/plugin-entry` + an `openclaw.plugin.json` manifest, with snake_case hook events `agent_turn_prepare`/`agent_end`/`session_end` (NOT colon-delimited). Always make the embedded hook shell-out a silent no-op if the `kimetsu` binary isn't on PATH so a wrong guess never breaks the host. (context: Adding Pi + OpenClaw as BridgeTarget hosts in v1.0.0; the inferred extension/plugin APIs from docs were wrong and had to be corrected against the real repos.)" + }, + { + "ce": 0.006083905231207609, + "key": "kimetsu-daemon-lifecycle", + "rank_score": 0.816368818283081, + "text": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required. The server PID is not stored anywhere; use `kimetsu doctor` to enumerate running MCP server processes via OS APIs. On Windows, the server binary may be locked by AV after first launch — `kimetsu update` must stop all running server processes before replacing the binary. (context: Kimetsu daemon lifecycle — process management for updates.)" + }, + { + "ce": 0.046272579580545425, + "key": "aws-sigv4-bedrock-blocking", + "rank_score": 0.8069052696228027, + "text": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed. aws-smithy-runtime-api required as a companion to supply Identity. (context: Implementing BedrockProvider for Kimetsu with blocking reqwest + SigV4 signing, no tokio/aws-sdk)" + }, + { + "ce": 0.018963346257805824, + "key": "http-retry-idempotency", + "rank_score": 0.728375256061554, + "text": "project:fact - [tags: http retry idempotency post put reqwest] Only retry idempotent requests automatically. GET, HEAD, PUT, DELETE are idempotent. POST is NOT — retrying a POST may create duplicate resources. For LLM API calls (POST), implement retry with idempotency keys: include a stable `X-Idempotency-Key: ` header; the provider deduplicates. For transient 429 (rate limit) responses, back off with jitter: `min(base * 2^attempt, cap) + rand(0, base)`. For 5xx, retry at most 3 times. Never retry on 4xx (except 429). In kimetsu, retry logic lives in the provider layer, not the distiller. (context: Kimetsu LLM provider retry strategy.)" + }, + { + "ce": 0.01527151558548212, + "key": "kimetsu-distiller-config", + "rank_score": 0.7205646634101868, + "text": "project:fact - [tags: kimetsu distiller harvest config provider] The kimetsu distiller (auto-harvester) uses a SEPARATE provider configuration from the main agent: `distiller.provider`, `distiller.model`, `distiller.api_key`. This allows running the agent on an expensive model (Claude Opus) while harvesting with a cheap model (Claude Haiku). If `distiller.provider` is not set, it inherits `provider`. The distiller runs as a background task triggered by the post-session hook; it reads the session transcript and emits `kimetsu_brain_record` calls. Distiller timeouts are longer (300s) than normal tool calls (60s) because transcript processing can be slow. (context: Kimetsu distiller provider configuration — agent vs harvester model separation.)" + } + ], + "delivered": [], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.677783727645874 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "can the kimetsu brain database be stored in S3 instead of on disk", + "relevant": [], + "stages": [ + { + "candidates": [ + { + "ce": 0.6605784893035889, + "key": "aws-presigned-urls", + "rank_score": 0.9549975395202637, + "text": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time — clock skew > 15 minutes causes `RequestTimeTooSkewed`. kimetsu could use presigned URLs to serve brain exports from S3 without exposing credentials to the client. (context: Kimetsu potential S3 export feature — presigned URL generation.)" + }, + { + "ce": 0.01737244799733162, + "key": "sqlite-page-size", + "rank_score": 0.711969792842865, + "text": "project:fact - [tags: sqlite page_size performance rusqlite] SQLite's default page_size is 4096 bytes. For a write-heavy brain database with large BLOB payloads (embedding vectors), raising page_size to 16384 reduces fragmentation and improves sequential scan throughput. `PRAGMA page_size = 16384;` must be set BEFORE the first table is created — changing it on an existing database requires a VACUUM afterward to rebuild all pages. Verify it took effect with `PRAGMA page_size;` after VACUUM. rusqlite's `Connection::open` runs no implicit PRAGMA, so set this in the connection init path. (context: Tuning the kimetsu brain SQLite schema for embedding vector storage.)" + }, + { + "ce": 0.008005553856492043, + "key": "init-project-git-boundary", + "rank_score": 0.6622833609580994, + "text": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain. (context: QQ3 — kimetsu setup integration test failed because init_project climbed git tree to real ~/.kimetsu instead of temp workspace)" + }, + { + "ce": 0.007874228991568089, + "key": "sqlite-wal-network-drive", + "rank_score": 0.641007125377655, + "text": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db. Fallback: `PRAGMA journal_mode=DELETE;` is safe over SMB at the cost of lower concurrency. Detect network drives at startup with `GetFileAttributes` checking FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS or using `PathIsNetworkPath`. (context: Users running kimetsu with the brain database on a mapped network drive.)" + }, + { + "ce": 0.007572549395263195, + "key": "sqlite-prepared-stmt-cache", + "rank_score": 0.6275213360786438, + "text": "project:fact - [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8). The cache key is the SQL string verbatim, so template strings with interpolated values defeat caching — use `?1, ?2` placeholders instead. Calling `prepare_cached` in a tight loop is effectively free after warmup. (context: Kimetsu brain high-throughput ingest path — replacing prepare() with prepare_cached() cut ingest time by ~30%.)" + }, + { + "ce": 0.0025658023077994585, + "key": "sqlite-busy-timeout-wal", + "rank_score": 0.5762315392494202, + "text": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch. Set the timeout before any transaction, not inside one — it is a connection-level property. (context: Kimetsu brain writer and reader processes sharing the same SQLite brain database.)" + } + ], + "delivered": [ + "aws-presigned-urls" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7151318788528442 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "how do I plug a custom tokenizer into the FTS index", + "relevant": [], + "stages": [ + { + "candidates": [ + { + "ce": 0.8707897067070007, + "key": "sqlite-fts5-tokenizer", + "rank_score": 0.9549973011016846, + "text": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon. If you switch tokenizers on an existing FTS5 table, you MUST rebuild the shadow tables: `INSERT INTO tbl(tbl) VALUES('rebuild');` — a schema-only change leaves the inverted index unusable. The `porter` stemmer is available as `tokenize='porter unicode61'` but aggressively strips suffixes and hurts precision on technical terms. (context: Kimetsu brain FTS5 index tuning for Rust identifier retrieval.)" + }, + { + "ce": 0.07484621554613113, + "key": "onnx-tokenizer-mismatch", + "rank_score": 0.6753090023994446, + "text": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly — specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings — cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo. Validate by checking a reference embedding against the HuggingFace Python output. (context: Kimetsu custom ONNX reranker loading — wrong tokenizer produced degraded retrieval.)" + }, + { + "ce": 0.18984374403953552, + "key": "kimetsu-query-stemming", + "rank_score": 0.6216950416564941, + "text": "project:fact - [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression. The lexical floor (`min_lexical_coverage`) requires at least N stemmed query tokens to match in any retrieved document — this prevents high-semantic-score but lexically-unrelated documents from dominating. Stemming is applied only when the query has >= 3 tokens; short queries skip it. (context: Kimetsu retrieval — query-side stemming implementation.)" + }, + { + "ce": 0.008686942979693413, + "key": "pi-openclaw-extension-api", + "rank_score": 0.5780782103538513, + "text": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`. External commands use `pi.exec()` but `node:child_process` spawn also works. Pi has NO MCP so Kimetsu integrates via TS extension + SKILL.md only. (context: Implementing Pi host target for Kimetsu plugin install/status/uninstall system.)\n\nAlso: [tags: kimetsu host-integration pi openclaw bridge] When integrating Kimetsu with an external host agent (Pi, OpenClaw, etc.), VERIFY the host's real plugin/extension API against its actual repo before writing embedded assets — docs-from-memory are frequently wrong. Concretely corrected during v1.0: Pi uses a default-export factory `export default function(pi)` (not `defineExtension`) with lifecycle events `session_start`/`agent_end`/`session_shutdown`; OpenClaw plugin entry is `index.ts` via `definePluginEntry` from `openclaw/plugin-sdk/plugin-entry` + an `openclaw.plugin.json` manifest, with snake_case hook events `agent_turn_prepare`/`agent_end`/`session_end` (NOT colon-delimited). Always make the embedded hook shell-out a silent no-op if the `kimetsu` binary isn't on PATH so a wrong guess never breaks the host. (context: Adding Pi + OpenClaw as BridgeTarget hosts in v1.0.0; the inferred extension/plugin APIs from docs were wrong and had to be corrected against the real repos.)" + }, + { + "ce": 0.0004218780086375773, + "key": "cargo-dev-dep-leak", + "rank_score": 0.5371543169021606, + "text": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates. Run `cargo tree --features ` to trace which crate activated an unexpected feature. (context: Kimetsu testing infra — a dev-dep was activating the embeddings feature in non-test builds.)" + }, + { + "ce": 0.0006244456162676215, + "key": "http-tls-roots", + "rank_score": 0.48200133442878723, + "text": "project:fact - [tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle — the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle. Alternatively, add the custom root with `add_root_certificate`. On Linux, the system CA bundle is at `/etc/ssl/certs/ca-certificates.crt`; on Windows it's in the Windows Certificate Store. (context: Kimetsu on a corporate Windows machine with a custom proxy CA.)" + } + ], + "delivered": [ + "sqlite-fts5-tokenizer" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7935139536857605 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "what should I check when kimetsu behaves differently on Windows than on Linux?", + "relevant": [ + "process-start-time-cross-platform", + "cfg-cross-platform-dead-code", + "sqlite-vacuum-wal-checkpoint", + "windows-update-process-locking" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.0001264903839910403, + "key": "cargo-lockfile-drift", + "rank_score": 0.9549973011016846, + "text": "project:fact - [2026-09-05] [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this — it errors on any lockfile diff. For library crates, `Cargo.lock` is normally gitignored, but for workspace roots with binary crates it should be committed. Use `cargo update --precise ` to pin a specific dep version without touching unrelated entries. (context: Kimetsu workspace lockfile drift after adding kimetsu-remote crate.)" + }, + { + "ce": 0.02787019871175289, + "key": "windows-exit-codes", + "rank_score": 0.7535445690155029, + "text": "project:fact - [2026-09-05] [tags: windows exit-codes rust process child] On Windows, process exit codes are 32-bit unsigned integers (DWORD). Rust's `ExitStatus::code()` returns `Option` — it's `None` if the process was killed by a signal (which Windows doesn't use; instead, TerminateProcess with a code). Conventional codes: 0=success, 1=generic error, 0xC0000005=access violation. Programs that call `std::process::exit(-1)` on Windows produce exit code 0xFFFFFFFF (4294967295), not -1. When checking for success in a subprocess chain, always check `status.success()` rather than `status.code() == Some(0)` to handle this portably. (context: Kimetsu update binary replacement — exit code handling.)" + }, + { + "ce": 0.0014269561506807804, + "key": "git-line-endings-windows", + "rank_score": 0.810790479183197, + "text": "project:fact - [2026-09-05] [tags: git line-endings windows crlf autocrlf] On Windows, `core.autocrlf=true` (git's default for Windows installs) converts LF to CRLF on checkout and CRLF to LF on commit. This causes spurious diffs when files are edited on Windows then committed — the content is identical but the line endings differ in the index vs the working tree. Fix: set `core.autocrlf=false` and `.gitattributes` with `* text=auto eol=lf` for the repo. For Rust projects, all source files should be LF; only Windows batch scripts need CRLF. Warn: AV scanners that modify newly written files can re-introduce CRLF in files Rust writes. (context: Kimetsu CI — spurious diffs from Windows CRLF conversion.)" + }, + { + "ce": 0.030121121555566788, + "key": "http-tls-roots", + "rank_score": 0.7294023036956787, + "text": "project:fact - [2026-09-05] [tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle — the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle. Alternatively, add the custom root with `add_root_certificate`. On Linux, the system CA bundle is at `/etc/ssl/certs/ca-certificates.crt`; on Windows it's in the Windows Certificate Store. (context: Kimetsu on a corporate Windows machine with a custom proxy CA.)" + }, + { + "ce": 0.00994731206446886, + "key": "testing-temp-dirs-ci", + "rank_score": 0.7783405780792236, + "text": "project:fact - [2026-09-05] [tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure. On Windows, `env::temp_dir()` returns `C:\\Users\\\\AppData\\Local\\Temp` — ensure the test binary has write permissions there. Avoid using the workspace root as a temp dir — tests should never write to the source tree. (context: Kimetsu test infrastructure — temp directory discipline.)" + }, + { + "ce": 0.05426826700568199, + "key": "ci-matrix-explosion", + "rank_score": 0.8166996240615845, + "text": "project:fact - [2026-09-05] [tags: ci github-actions matrix jobs resources] A CI matrix combining OS (3) x Rust toolchain (3) x features (2) = 18 jobs. Each spawns a runner; at $0.008/min for Ubuntu and $0.016/min for Windows, a 10-minute build costs $2.40 per push. Reduce: test the full matrix only on PRs to main; on feature branches, test only Linux+stable. Use `fail-fast: false` to see all failures, not just the first. Combine related checks (clippy + test) in one job when they share build artifacts. For Windows-specific tests, run only the OS-specific job to reduce cost. (context: Kimetsu CI matrix cost optimization.)" + } + ], + "delivered": [], + "excluded_gold": [ + { + "ce": null, + "key": "windows-update-process-locking" + }, + { + "ce": null, + "key": "sqlite-vacuum-wal-checkpoint" + }, + { + "ce": null, + "key": "cfg-cross-platform-dead-code" + }, + { + "ce": null, + "key": "process-start-time-cross-platform" + } + ], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7164012789726257 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "what are the moving parts of the kimetsu remote deployment story?", + "relevant": [ + "remote-ingest-split-roots", + "remote-mcp-host-wiring" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.0004875503364019096, + "key": "sqlite-json1-extract", + "rank_score": 0.9549973011016846, + "text": "project:fact - [tags: sqlite json1 json_extract rusqlite] SQLite's json1 extension (built in since 3.38.0) lets you index and query JSONB columns with `json_extract(col, '$.field')`. To create a partial index over a JSON field: `CREATE INDEX idx ON memories (json_extract(metadata, '$.scope')) WHERE json_extract(metadata, '$.scope') IS NOT NULL;`. Use `json_each` for array fields. On older SQLite builds (rusqlite links whatever the system provides), check for json1 with `SELECT json('{}');` — an error means it's absent. Always prefer column storage over JSON blobs for frequently queried fields. (context: Kimetsu brain querying metadata scopes without migrating a separate column.)" + }, + { + "ce": 0.08497442305088043, + "key": "kimetsu-write-tools-gate", + "rank_score": 0.9549396634101868, + "text": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level — disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients. The feature was introduced to prevent malicious prompts from poisoning the brain. (context: Kimetsu write-tools gate — config-driven security for remote deployments.)" + }, + { + "ce": 0.0026425954420119524, + "key": "onnx-dim-mismatch", + "rank_score": 0.8340495824813843, + "text": "project:fact - [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results — the ANN index shape mismatch isn't always caught at runtime. kimetsu detects this by storing `embedder_id` in the brain schema and refusing to query if the configured embedder differs from what was used at ingest time. Mitigation: re-ingest all memories with the new model, or keep per-memory vector dim metadata. (context: Kimetsu embedder migration — detecting dimension mismatch at startup.)" + }, + { + "ce": 0.04643947631120682, + "key": "tokio-select-cancellation", + "rank_score": 0.7871651649475098, + "text": "project:fact - [tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded. For correctness, cancelled futures must be cancellation-safe: holding no partially committed state. `tokio::sync::watch::Receiver::changed()` is cancellation-safe; `tokio::sync::mpsc::Sender::send()` is NOT (the item is lost). In kimetsu shutdown, use a `CancellationToken` and `select!` branches that are all cancellation-safe. (context: Kimetsu remote graceful shutdown — race between incoming requests and shutdown signal.)" + }, + { + "ce": 0.008009310811758041, + "key": "git-sparse-checkout", + "rank_score": 0.7856689095497131, + "text": "project:fact - [tags: git sparse-checkout partial-clone bandwidth] `git sparse-checkout init --cone` combined with `git clone --filter=blob:none` (partial clone) fetches only the commit graph and tree objects, not blobs. Individual blobs are fetched on demand when accessed. This cuts clone time for large repos from minutes to seconds. For kimetsu server-side ingest, use `git clone --depth 1 --filter=blob:none` for the initial checkout, then `git sparse-checkout set ` to limit the working tree to indexed directories. On `git fetch --depth 1 origin main` for refresh, blobs in the sparse set are updated lazily. (context: Kimetsu remote ingest — reducing bandwidth and disk usage for large repo checkouts.)" + }, + { + "ce": 0.00805254839360714, + "key": "ci-secrets-masking", + "rank_score": 0.7779524922370911, + "text": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output — but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable. Never reconstruct secrets from parts in step output. For kimetsu bench `--remote` CI runs, `KIMETSU_REMOTE_TOKEN` must be in the repository secrets, not in the workflow YAML. Use `${{ secrets.KIMETSU_REMOTE_TOKEN }}` in env — never `echo ${{ secrets.KIMETSU_REMOTE_TOKEN }}` in a run step. (context: Kimetsu CI remote benchmark — token handling.)" + } + ], + "delivered": [], + "excluded_gold": [ + { + "ce": null, + "key": "remote-mcp-host-wiring" + }, + { + "ce": null, + "key": "remote-ingest-split-roots" + } + ], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7213218212127686 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "which lessons cover guarding behavior behind environment variables?", + "relevant": [ + "gc-trace-env-guard-placement", + "mutex-deadlock-user-brain-disabled" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.002962717553600669, + "key": "http-proxy-env", + "rank_score": 0.9549973607063293, + "text": "project:fact - [tags: http proxy environment reqwest rust corporate] reqwest respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` environment variables by default (with `default-tls` or `rustls-tls`). In a corporate network, these may redirect traffic through an intercepting proxy that breaks mTLS or adds latency. To disable proxy usage entirely: `reqwest::ClientBuilder::no_proxy()`. On Windows, reqwest does NOT use the system proxy settings (IE/WinInet) — you must set env vars explicitly. `NO_PROXY=127.0.0.1,localhost` prevents proxying loopback traffic (important for kimetsu-remote local dev). (context: Kimetsu provider calls failing behind corporate proxy on Windows.)" + }, + { + "ce": 0.0010203025303781033, + "key": "onnx-model-cache-paths", + "rank_score": 0.7400883436203003, + "text": "project:fact - [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use. In kimetsu, `KIMETSU_EMBEDDER_CACHE` overrides the path and is forwarded when spawning child bench processes — without forwarding it, each child re-downloads the model. (context: Kimetsu brain bench on CI — model cache path handling in child processes.)" + }, + { + "ce": 0.0001333318796241656, + "key": "testing-time-dependent-flakes", + "rank_score": 0.6728026270866394, + "text": "project:fact - [tags: testing time flaky clock mock rust] Tests that depend on wall-clock time are inherently flaky under load (slow CI runners, GC pauses). Abstract time behind a trait (`Clock: Fn() -> SystemTime`) injected at construction, and supply a fake in tests. For tests checking that something happened \"within N seconds\", use a generous multiple of the expected duration (10x is not unreasonable for CI). `std::thread::sleep` in tests is a smell — prefer channel synchronization or a condvar instead of timing-based waits. If you must use sleep, set `KIMETSU_TEST_TIMEOUT_SCALE` to stretch timeouts in slow environments. (context: Kimetsu GC and TTL tests — time-dependent flakes on loaded CI.)" + }, + { + "ce": 0.00006251245213206857, + "key": "onnx-batch-padding", + "rank_score": 0.6083888411521912, + "text": "project:fact - [tags: onnx batch padding attention-mask embeddings] When running batch inference with an ONNX model, all inputs in the batch must be padded to the same sequence length. The `attention_mask` tensor marks which tokens are real (1) and which are padding (0). Failing to pass `attention_mask` causes the model to average-pool over padding tokens, producing systematically lower-norm embeddings. With ORT (ort crate), construct the mask as a 2-D i64 tensor `[batch, seq_len]` with 1s for real tokens and 0s for padding. For variable-length batches, pad to `max(lengths)` in the batch, not to `model.max_length`. (context: Kimetsu embedding batch inference with ORT — missing attention mask caused MRR degradation.)" + }, + { + "ce": 0.00018172990530729294, + "key": "cargo-feature-unification-embeddings", + "rank_score": 0.5355795621871948, + "text": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli). Diagnostic tell: a test that passes alone but fails only under `cargo test --workspace` AND a brand-new crate was just added = suspect feature unification flipping a sibling crate's behavior. (context: Building the kimetsu-remote crate (HTTP MCP server); its default embeddings feature broke 3 kimetsu-chat retrieval tests only under the full workspace test.)" + }, + { + "ce": 0.00006411396316252649, + "key": "mcp-env-propagation", + "rank_score": 0.5233708024024963, + "text": "project:fact - [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment — changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate. For kimetsu hooks (pre-commit, post-commit), the hook script inherits the shell's env at hook invocation time, not the server's. If `KIMETSU_BRAIN_DIR` needs to vary per project, set it in the project's `.env` file and source it in the hook script. (context: Kimetsu env propagation from hooks to MCP server.)" + } + ], + "delivered": [], + "excluded_gold": [ + { + "ce": null, + "key": "gc-trace-env-guard-placement" + }, + { + "ce": null, + "key": "mutex-deadlock-user-brain-disabled" + } + ], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.5835065841674805 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "SQLite BUSY error under concurrent writes", + "relevant": [ + "sqlite-busy-timeout-wal", + "sqlite-vacuum-wal-checkpoint" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.8938739895820618, + "key": "sqlite-busy-timeout-wal", + "rank_score": 0.954997181892395, + "text": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch. Set the timeout before any transaction, not inside one — it is a connection-level property. (context: Kimetsu brain writer and reader processes sharing the same SQLite brain database.)" + }, + { + "ce": 0.007792162708938122, + "key": "sqlite-fts5-tokenizer", + "rank_score": 0.5198225378990173, + "text": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon. If you switch tokenizers on an existing FTS5 table, you MUST rebuild the shadow tables: `INSERT INTO tbl(tbl) VALUES('rebuild');` — a schema-only change leaves the inverted index unusable. The `porter` stemmer is available as `tokenize='porter unicode61'` but aggressively strips suffixes and hurts precision on technical terms. (context: Kimetsu brain FTS5 index tuning for Rust identifier retrieval.)" + }, + { + "ce": 0.00018060034199152142, + "key": "onnx-model-cache-paths", + "rank_score": 0.5256999135017395, + "text": "project:fact - [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use. In kimetsu, `KIMETSU_EMBEDDER_CACHE` overrides the path and is forwarded when spawning child bench processes — without forwarding it, each child re-downloads the model. (context: Kimetsu brain bench on CI — model cache path handling in child processes.)" + }, + { + "ce": 0.0022410419769585133, + "key": "tokio-blocking-in-async", + "rank_score": 0.5139820575714111, + "text": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking — never call rusqlite directly from an async fn without spawn_blocking. fastembed inference is also blocking (ONNX Runtime is synchronous). The threshold: any operation taking more than 100 microseconds that can't be made async belongs in spawn_blocking. Ignoring this causes tail-latency spikes and request timeouts under load in kimetsu-remote. (context: Kimetsu remote server — SQLite and embedding calls from async handlers.)" + }, + { + "ce": 0.07965622842311859, + "key": "tokio-shutdown-ordering", + "rank_score": 0.5160802602767944, + "text": "project:fact - [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries — the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks. `axum::Server::with_graceful_shutdown` handles steps 1-2; you must handle 3-5 manually. (context: Kimetsu remote server graceful shutdown implementation.)" + }, + { + "ce": 0.08431826531887054, + "key": "sqlite-json1-extract", + "rank_score": 0.5036094784736633, + "text": "project:fact - [tags: sqlite json1 json_extract rusqlite] SQLite's json1 extension (built in since 3.38.0) lets you index and query JSONB columns with `json_extract(col, '$.field')`. To create a partial index over a JSON field: `CREATE INDEX idx ON memories (json_extract(metadata, '$.scope')) WHERE json_extract(metadata, '$.scope') IS NOT NULL;`. Use `json_each` for array fields. On older SQLite builds (rusqlite links whatever the system provides), check for json1 with `SELECT json('{}');` — an error means it's absent. Always prefer column storage over JSON blobs for frequently queried fields. (context: Kimetsu brain querying metadata scopes without migrating a separate column.)" + } + ], + "delivered": [ + "sqlite-busy-timeout-wal" + ], + "excluded_gold": [ + { + "ce": null, + "key": "sqlite-vacuum-wal-checkpoint" + } + ], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7636588215827942 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "SQLite WAL mode breaks when the database is on a network share", + "relevant": [ + "sqlite-wal-network-drive" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.0597701333463192, + "key": "sqlite-vacuum-wal-checkpoint", + "rank_score": 0.5214540362358093, + "text": "project:fact - [2026-09-05] [tags: rust sqlite vacuum rusqlite windows] When implementing SQLite VACUUM in rusqlite: VACUUM cannot run inside a transaction. rusqlite's Connection does not hold an implicit transaction, so `conn.execute_batch(\"VACUUM;\")` works directly. After VACUUM, run `PRAGMA wal_checkpoint(TRUNCATE);` before measuring file size — on Windows the WAL file can hold significant space that isn't reflected in the main db file until the checkpoint runs. (context: Implementing kimetsu brain compact (Q8) — SQLite VACUUM + WAL checkpoint for accurate post-compact file size.)" + }, + { + "ce": 0.9912887811660767, + "key": "sqlite-busy-timeout-wal", + "rank_score": 0.801898181438446, + "text": "project:fact - [2026-09-05] [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch. Set the timeout before any transaction, not inside one — it is a connection-level property. (context: Kimetsu brain writer and reader processes sharing the same SQLite brain database.)" + }, + { + "ce": 0.9984472393989563, + "key": "sqlite-wal-network-drive", + "rank_score": 0.954997181892395, + "text": "project:fact - [2026-09-05] [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db. Fallback: `PRAGMA journal_mode=DELETE;` is safe over SMB at the cost of lower concurrency. Detect network drives at startup with `GetFileAttributes` checking FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS or using `PathIsNetworkPath`. (context: Users running kimetsu with the brain database on a mapped network drive.)" + }, + { + "ce": 0.0022751474753022194, + "key": "sqlite-page-size", + "rank_score": 0.5192393660545349, + "text": "project:fact - [2026-09-05] [tags: sqlite page_size performance rusqlite] SQLite's default page_size is 4096 bytes. For a write-heavy brain database with large BLOB payloads (embedding vectors), raising page_size to 16384 reduces fragmentation and improves sequential scan throughput. `PRAGMA page_size = 16384;` must be set BEFORE the first table is created — changing it on an existing database requires a VACUUM afterward to rebuild all pages. Verify it took effect with `PRAGMA page_size;` after VACUUM. rusqlite's `Connection::open` runs no implicit PRAGMA, so set this in the connection init path. (context: Tuning the kimetsu brain SQLite schema for embedding vector storage.)" + }, + { + "ce": 0.032531801611185074, + "key": "windows-unc-paths", + "rank_score": 0.7170491814613342, + "text": "project:fact - [2026-09-05] [tags: windows unc-paths rust std::fs] Windows UNC paths (`\\\\server\\share\\...`) are not supported by most Rust `std::fs` operations unless passed through the extended-length prefix `\\\\?\\UNC\\server\\share\\...`. `std::path::Path::new(\"\\\\\\\\server\\\\share\")` works for basic operations but breaks with `canonicalize()` which returns the verbatim prefix form. When walking directory trees that may start on UNC paths, use the `dunce` crate to strip the verbatim prefix before comparing or displaying paths. Never `cd` into a UNC path in a subprocess started with `std::process::Command` — the subprocess may not inherit it correctly on older Windows. (context: Kimetsu ingest walking paths on network-mounted project directories.)" + }, + { + "ce": 0.0003809015324804932, + "key": "http-proxy-env", + "rank_score": 0.5152295231819153, + "text": "project:fact - [2026-09-05] [tags: http proxy environment reqwest rust corporate] reqwest respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` environment variables by default (with `default-tls` or `rustls-tls`). In a corporate network, these may redirect traffic through an intercepting proxy that breaks mTLS or adds latency. To disable proxy usage entirely: `reqwest::ClientBuilder::no_proxy()`. On Windows, reqwest does NOT use the system proxy settings (IE/WinInet) — you must set env vars explicitly. `NO_PROXY=127.0.0.1,localhost` prevents proxying loopback traffic (important for kimetsu-remote local dev). (context: Kimetsu provider calls failing behind corporate proxy on Windows.)" + } + ], + "delivered": [ + "sqlite-wal-network-drive", + "sqlite-busy-timeout-wal" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.756793200969696 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "my SQLite WAL database causes SQLITE_IOERR_LOCK on a mapped drive", + "relevant": [ + "sqlite-wal-network-drive" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.999721109867096, + "key": "sqlite-wal-network-drive", + "rank_score": 0.954997181892395, + "text": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db. Fallback: `PRAGMA journal_mode=DELETE;` is safe over SMB at the cost of lower concurrency. Detect network drives at startup with `GetFileAttributes` checking FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS or using `PathIsNetworkPath`. (context: Users running kimetsu with the brain database on a mapped network drive.)" + }, + { + "ce": 0.6342206001281738, + "key": "sqlite-busy-timeout-wal", + "rank_score": 0.5852744579315186, + "text": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch. Set the timeout before any transaction, not inside one — it is a connection-level property. (context: Kimetsu brain writer and reader processes sharing the same SQLite brain database.)" + }, + { + "ce": 0.0023472425527870655, + "key": "cargo-patch-section", + "rank_score": 0.5006773471832275, + "text": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace — including transitive deps — that depend on `my-crate`. Remove the patch before publishing. Using `[replace]` is deprecated since Cargo 0.47; always use `[patch]`. When patching a crate pinned via an exact version specifier, the patch must satisfy that exact version. Use `cargo tree` to confirm the patch is applied. (context: Kimetsu patching upstream rusqlite for a Windows-specific WAL fix.)" + }, + { + "ce": 0.0010601059766486287, + "key": "sqlite-page-size", + "rank_score": 0.4739128053188324, + "text": "project:fact - [tags: sqlite page_size performance rusqlite] SQLite's default page_size is 4096 bytes. For a write-heavy brain database with large BLOB payloads (embedding vectors), raising page_size to 16384 reduces fragmentation and improves sequential scan throughput. `PRAGMA page_size = 16384;` must be set BEFORE the first table is created — changing it on an existing database requires a VACUUM afterward to rebuild all pages. Verify it took effect with `PRAGMA page_size;` after VACUUM. rusqlite's `Connection::open` runs no implicit PRAGMA, so set this in the connection init path. (context: Tuning the kimetsu brain SQLite schema for embedding vector storage.)" + }, + { + "ce": 0.01613534800708294, + "key": "sqlite-vacuum-wal-checkpoint", + "rank_score": 0.46667948365211487, + "text": "project:fact - [tags: rust sqlite vacuum rusqlite windows] When implementing SQLite VACUUM in rusqlite: VACUUM cannot run inside a transaction. rusqlite's Connection does not hold an implicit transaction, so `conn.execute_batch(\"VACUUM;\")` works directly. After VACUUM, run `PRAGMA wal_checkpoint(TRUNCATE);` before measuring file size — on Windows the WAL file can hold significant space that isn't reflected in the main db file until the checkpoint runs. (context: Implementing kimetsu brain compact (Q8) — SQLite VACUUM + WAL checkpoint for accurate post-compact file size.)" + }, + { + "ce": 0.0006728425505571067, + "key": "sqlite-foreign-keys-default-off", + "rank_score": 0.43711724877357483, + "text": "project:fact - [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting — every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing. Check your schema with `PRAGMA foreign_key_list(table_name);` and your current setting with `PRAGMA foreign_keys;`. rusqlite does not enable foreign keys automatically. (context: Kimetsu brain schema — memory_tags table has FK to memories table, discovered ON DELETE CASCADE wasn't firing.)" + } + ], + "delivered": [ + "sqlite-wal-network-drive", + "sqlite-busy-timeout-wal" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7533890008926392 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "FTS5 tokenizer configuration for Rust identifiers with underscores", + "relevant": [ + "sqlite-fts5-tokenizer" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9998878240585327, + "key": "sqlite-fts5-tokenizer", + "rank_score": 0.9549971222877502, + "text": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon. If you switch tokenizers on an existing FTS5 table, you MUST rebuild the shadow tables: `INSERT INTO tbl(tbl) VALUES('rebuild');` — a schema-only change leaves the inverted index unusable. The `porter` stemmer is available as `tokenize='porter unicode61'` but aggressively strips suffixes and hurts precision on technical terms. (context: Kimetsu brain FTS5 index tuning for Rust identifier retrieval.)" + }, + { + "ce": 0.0007524462998844683, + "key": "onnx-tokenizer-mismatch", + "rank_score": 0.4826098680496216, + "text": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly — specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings — cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo. Validate by checking a reference embedding against the HuggingFace Python output. (context: Kimetsu custom ONNX reranker loading — wrong tokenizer produced degraded retrieval.)" + }, + { + "ce": 0.5220575332641602, + "key": "kimetsu-query-stemming", + "rank_score": 0.46068504452705383, + "text": "project:fact - [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression. The lexical floor (`min_lexical_coverage`) requires at least N stemmed query tokens to match in any retrieved document — this prevents high-semantic-score but lexically-unrelated documents from dominating. Stemming is applied only when the query has >= 3 tokens; short queries skip it. (context: Kimetsu retrieval — query-side stemming implementation.)" + }, + { + "ce": 0.000061002687289146706, + "key": "kimetsu-distiller-config", + "rank_score": 0.43653663992881775, + "text": "project:fact - [tags: kimetsu distiller harvest config provider] The kimetsu distiller (auto-harvester) uses a SEPARATE provider configuration from the main agent: `distiller.provider`, `distiller.model`, `distiller.api_key`. This allows running the agent on an expensive model (Claude Opus) while harvesting with a cheap model (Claude Haiku). If `distiller.provider` is not set, it inherits `provider`. The distiller runs as a background task triggered by the post-session hook; it reads the session transcript and emits `kimetsu_brain_record` calls. Distiller timeouts are longer (300s) than normal tool calls (60s) because transcript processing can be slow. (context: Kimetsu distiller provider configuration — agent vs harvester model separation.)" + }, + { + "ce": 0.00003888343053404242, + "key": "aws-region-resolution", + "rank_score": 0.43710875511169434, + "text": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time. For cross-region inference (e.g. us-west-2 for Claude Opus), set `AWS_REGION=us-west-2`; do NOT rely on the Bedrock endpoint prefix being region-agnostic. (context: Kimetsu Bedrock provider region configuration.)" + }, + { + "ce": 0.00012254281318746507, + "key": "mcp-tool-naming", + "rank_score": 0.397331178188324, + "text": "project:fact - [tags: mcp tool naming convention kimetsu] MCP tool names must be valid identifiers for all host agents. Claude Code restricts tool names to `[a-zA-Z0-9_-]` and max 64 chars. Use `snake_case` (kimetsu_brain_context, kimetsu_brain_record) — hyphen is technically allowed but some hosts reject it. Avoid dots (not allowed). Namespace with a prefix (`kimetsu_brain_`) to prevent collisions with other MCP servers. When a tool name changes, update ALL host config files (`.mcp.json`, `openclaw.json`, skill markdown) — mismatched names cause silent failures where the host skips the tool. (context: Kimetsu MCP tool naming convention enforcement.)" + } + ], + "delivered": [ + "sqlite-fts5-tokenizer", + "kimetsu-query-stemming" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8333450555801392 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "I switched the FTS5 tokenizer but search stopped returning results", + "relevant": [ + "sqlite-fts5-tokenizer" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.943705141544342, + "key": "sqlite-fts5-tokenizer", + "rank_score": 0.9549971222877502, + "text": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon. If you switch tokenizers on an existing FTS5 table, you MUST rebuild the shadow tables: `INSERT INTO tbl(tbl) VALUES('rebuild');` — a schema-only change leaves the inverted index unusable. The `porter` stemmer is available as `tokenize='porter unicode61'` but aggressively strips suffixes and hurts precision on technical terms. (context: Kimetsu brain FTS5 index tuning for Rust identifier retrieval.)" + }, + { + "ce": 0.2714236080646515, + "key": "kimetsu-query-stemming", + "rank_score": 0.6238319277763367, + "text": "project:fact - [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression. The lexical floor (`min_lexical_coverage`) requires at least N stemmed query tokens to match in any retrieved document — this prevents high-semantic-score but lexically-unrelated documents from dominating. Stemming is applied only when the query has >= 3 tokens; short queries skip it. (context: Kimetsu retrieval — query-side stemming implementation.)" + }, + { + "ce": 0.0006543444469571114, + "key": "onnx-tokenizer-mismatch", + "rank_score": 0.5055172443389893, + "text": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly — specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings — cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo. Validate by checking a reference embedding against the HuggingFace Python output. (context: Kimetsu custom ONNX reranker loading — wrong tokenizer produced degraded retrieval.)" + }, + { + "ce": 0.00023919351224321872, + "key": "mcp-tool-timeouts", + "rank_score": 0.4834287166595459, + "text": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking — in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize — keep it in a process-global `OnceLock`). The reranker adds another 200-800ms; jina-tiny is the fastest. If tool calls are still slow, log the per-stage latency with `tracing::info!` at DEBUG level and profile under load. (context: Kimetsu MCP tool latency optimization.)" + }, + { + "ce": 0.0004671503556892276, + "key": "onnx-dim-mismatch", + "rank_score": 0.47524288296699524, + "text": "project:fact - [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results — the ANN index shape mismatch isn't always caught at runtime. kimetsu detects this by storing `embedder_id` in the brain schema and refusing to query if the configured embedder differs from what was used at ingest time. Mitigation: re-ingest all memories with the new model, or keep per-memory vector dim metadata. (context: Kimetsu embedder migration — detecting dimension mismatch at startup.)" + }, + { + "ce": 0.0013064700178802013, + "key": "http-streaming-bodies", + "rank_score": 0.44122427701950073, + "text": "project:fact - [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding — a chunk may split across frame boundaries. In kimetsu's proxy path, accumulate bytes until `\n\n` (SSE frame delimiter) before parsing the JSON data field. Never assume one `.chunk()` call = one SSE event. (context: Kimetsu remote proxy — streaming LLM responses to the client.)" + } + ], + "delivered": [ + "sqlite-fts5-tokenizer" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7919635772705078 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "optimal SQLite page size for storing embedding vectors", + "relevant": [ + "sqlite-page-size" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9990623593330383, + "key": "sqlite-page-size", + "rank_score": 0.9549971222877502, + "text": "project:fact - [tags: sqlite page_size performance rusqlite] SQLite's default page_size is 4096 bytes. For a write-heavy brain database with large BLOB payloads (embedding vectors), raising page_size to 16384 reduces fragmentation and improves sequential scan throughput. `PRAGMA page_size = 16384;` must be set BEFORE the first table is created — changing it on an existing database requires a VACUUM afterward to rebuild all pages. Verify it took effect with `PRAGMA page_size;` after VACUUM. rusqlite's `Connection::open` runs no implicit PRAGMA, so set this in the connection init path. (context: Tuning the kimetsu brain SQLite schema for embedding vector storage.)" + }, + { + "ce": 0.045568935573101044, + "key": "onnx-dim-mismatch", + "rank_score": 0.570976197719574, + "text": "project:fact - [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results — the ANN index shape mismatch isn't always caught at runtime. kimetsu detects this by storing `embedder_id` in the brain schema and refusing to query if the configured embedder differs from what was used at ingest time. Mitigation: re-ingest all memories with the new model, or keep per-memory vector dim metadata. (context: Kimetsu embedder migration — detecting dimension mismatch at startup.)" + }, + { + "ce": 0.019247185438871384, + "key": "onnx-cosine-vs-dot", + "rank_score": 0.5612027645111084, + "text": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing — double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g. E5, GTE with separate query/passage prefixes), the query and document encoders must use different prefix strings. Check the model card's `Similarity function` field. usearch/qdrant: prefer `MetricKind::Cos` over `Dot` for passage vectors that may not be perfectly normalized. (context: Kimetsu embedding storage — similarity metric selection.)" + }, + { + "ce": 0.0030976582784205675, + "key": "sqlite-prepared-stmt-cache", + "rank_score": 0.5026178956031799, + "text": "project:fact - [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8). The cache key is the SQL string verbatim, so template strings with interpolated values defeat caching — use `?1, ?2` placeholders instead. Calling `prepare_cached` in a tight loop is effectively free after warmup. (context: Kimetsu brain high-throughput ingest path — replacing prepare() with prepare_cached() cut ingest time by ~30%.)" + }, + { + "ce": 0.0012218038318678737, + "key": "sqlite-vacuum-wal-checkpoint", + "rank_score": 0.4995468258857727, + "text": "project:fact - [tags: rust sqlite vacuum rusqlite windows] When implementing SQLite VACUUM in rusqlite: VACUUM cannot run inside a transaction. rusqlite's Connection does not hold an implicit transaction, so `conn.execute_batch(\"VACUUM;\")` works directly. After VACUUM, run `PRAGMA wal_checkpoint(TRUNCATE);` before measuring file size — on Windows the WAL file can hold significant space that isn't reflected in the main db file until the checkpoint runs. (context: Implementing kimetsu brain compact (Q8) — SQLite VACUUM + WAL checkpoint for accurate post-compact file size.)" + }, + { + "ce": 0.000392510904930532, + "key": "windows-console-encoding", + "rank_score": 0.4507027864456177, + "text": "project:fact - [tags: windows console encoding utf8 rust] Windows console code page defaults to the system ANSI code page (usually CP1252 or CP932), not UTF-8. Rust's `println!` writes UTF-8 bytes which display as mojibake in a non-UTF-8 console. Fix at process startup: call `SetConsoleOutputCP(65001)` via `winapi` or `windows-sys`, or set `PYTHONUTF8=1`/`RUST_LOG` before launch. In PowerShell, `[Console]::OutputEncoding = [System.Text.Encoding]::UTF8` fixes the session. For binary piped output (MCP stdio protocol), write raw bytes — don't use the console code page. (context: Kimetsu MCP server — Unicode memory text was garbled on non-UTF8 Windows terminals.)" + } + ], + "delivered": [ + "sqlite-page-size" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7627132534980774 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "ON DELETE CASCADE in SQLite does nothing — foreign keys not enforced", + "relevant": [ + "sqlite-foreign-keys-default-off" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999727010726929, + "key": "sqlite-foreign-keys-default-off", + "rank_score": 0.9549971222877502, + "text": "project:fact - [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting — every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing. Check your schema with `PRAGMA foreign_key_list(table_name);` and your current setting with `PRAGMA foreign_keys;`. rusqlite does not enable foreign keys automatically. (context: Kimetsu brain schema — memory_tags table has FK to memories table, discovered ON DELETE CASCADE wasn't firing.)" + }, + { + "ce": 0.0034371905494481325, + "key": "git-hooks-bypass", + "rank_score": 0.4673461318016052, + "text": "project:fact - [tags: git hooks bypass pre-commit skip] `git commit --no-verify` skips ALL hooks (pre-commit and commit-msg). Never use this in shared team repos where hooks enforce quality gates (lint, tests, memory harvest). Instead, fix the failing hook. If the hook itself is broken, fix the hook script. For emergency commits where hooks aren't relevant (e.g. updating a gitignore to untrack already-committed files), document the `--no-verify` use in the commit message. In CI, hooks run only if explicitly invoked — `git commit` in a CI pipeline with no hooks configured does nothing for quality enforcement. (context: Kimetsu pre-commit hook enforcing memory harvest.)" + }, + { + "ce": 0.006892515812069178, + "key": "kimetsu-eval-fixture-shape", + "rank_score": 0.42436274886131287, + "text": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` — a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases). Keys must be unique across the dataset. The bench currently does not validate keys at load — it fails later with an `unwrap()` on a missing HashMap entry. (context: Kimetsu bench dataset shape and validation.)" + }, + { + "ce": 0.011505578644573689, + "key": "http-retry-idempotency", + "rank_score": 0.38770976662635803, + "text": "project:fact - [tags: http retry idempotency post put reqwest] Only retry idempotent requests automatically. GET, HEAD, PUT, DELETE are idempotent. POST is NOT — retrying a POST may create duplicate resources. For LLM API calls (POST), implement retry with idempotency keys: include a stable `X-Idempotency-Key: ` header; the provider deduplicates. For transient 429 (rate limit) responses, back off with jitter: `min(base * 2^attempt, cap) + rand(0, base)`. For 5xx, retry at most 3 times. Never retry on 4xx (except 429). In kimetsu, retry logic lives in the provider layer, not the distiller. (context: Kimetsu LLM provider retry strategy.)" + }, + { + "ce": 0.035377033054828644, + "key": "sqlite-wal-network-drive", + "rank_score": 0.38352495431900024, + "text": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db. Fallback: `PRAGMA journal_mode=DELETE;` is safe over SMB at the cost of lower concurrency. Detect network drives at startup with `GetFileAttributes` checking FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS or using `PathIsNetworkPath`. (context: Users running kimetsu with the brain database on a mapped network drive.)" + }, + { + "ce": 0.01964552327990532, + "key": "sqlite-partial-index", + "rank_score": 0.3862054944038391, + "text": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query — the planner uses the partial index only when the WHERE clause matches. Verify index usage with `EXPLAIN QUERY PLAN SELECT ...`. Partial indexes are not supported before SQLite 3.8.0; rusqlite's bundled SQLite is always current, but system SQLite on old Debian/Ubuntu may not be. (context: Optimizing kimetsu brain retrieval query over the active-memories subset.)" + } + ], + "delivered": [ + "sqlite-foreign-keys-default-off" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8320366740226746 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "indexing a JSON metadata column in SQLite without a schema migration", + "relevant": [ + "sqlite-json1-extract" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9998306035995483, + "key": "sqlite-json1-extract", + "rank_score": 0.9549971222877502, + "text": "project:fact - [tags: sqlite json1 json_extract rusqlite] SQLite's json1 extension (built in since 3.38.0) lets you index and query JSONB columns with `json_extract(col, '$.field')`. To create a partial index over a JSON field: `CREATE INDEX idx ON memories (json_extract(metadata, '$.scope')) WHERE json_extract(metadata, '$.scope') IS NOT NULL;`. Use `json_each` for array fields. On older SQLite builds (rusqlite links whatever the system provides), check for json1 with `SELECT json('{}');` — an error means it's absent. Always prefer column storage over JSON blobs for frequently queried fields. (context: Kimetsu brain querying metadata scopes without migrating a separate column.)" + }, + { + "ce": 0.04043366014957428, + "key": "onnx-dim-mismatch", + "rank_score": 0.6771757006645203, + "text": "project:fact - [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results — the ANN index shape mismatch isn't always caught at runtime. kimetsu detects this by storing `embedder_id` in the brain schema and refusing to query if the configured embedder differs from what was used at ingest time. Mitigation: re-ingest all memories with the new model, or keep per-memory vector dim metadata. (context: Kimetsu embedder migration — detecting dimension mismatch at startup.)" + }, + { + "ce": 0.33043068647384644, + "key": "testing-fixture-drift", + "rank_score": 0.6623726487159729, + "text": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code. For kimetsu, `EvalFixture::from_memories(memories)` constructs a dataset from the exported format — use it in tests instead of hardcoded JSON. Tag fixture files with the schema version they were generated against in a comment. (context: Kimetsu eval fixture drift after schema migration.)" + }, + { + "ce": 0.03084111586213112, + "key": "sqlite-fts5-tokenizer", + "rank_score": 0.5354147553443909, + "text": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon. If you switch tokenizers on an existing FTS5 table, you MUST rebuild the shadow tables: `INSERT INTO tbl(tbl) VALUES('rebuild');` — a schema-only change leaves the inverted index unusable. The `porter` stemmer is available as `tokenize='porter unicode61'` but aggressively strips suffixes and hurts precision on technical terms. (context: Kimetsu brain FTS5 index tuning for Rust identifier retrieval.)" + }, + { + "ce": 0.04229852557182312, + "key": "sqlite-partial-index", + "rank_score": 0.5851545333862305, + "text": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query — the planner uses the partial index only when the WHERE clause matches. Verify index usage with `EXPLAIN QUERY PLAN SELECT ...`. Partial indexes are not supported before SQLite 3.8.0; rusqlite's bundled SQLite is always current, but system SQLite on old Debian/Ubuntu may not be. (context: Optimizing kimetsu brain retrieval query over the active-memories subset.)" + }, + { + "ce": 0.003959252964705229, + "key": "mcp-schema-validation", + "rank_score": 0.5341337323188782, + "text": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array — omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error. Use `serde(default)` for optional fields. When adding a new tool, check the schema in the tools/list response manually by piping a JSON-RPC tools/list request to `./kimetsu mcp`. (context: Kimetsu MCP tool schema — required field validation.)" + } + ], + "delivered": [ + "sqlite-json1-extract", + "testing-fixture-drift" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.851775586605072 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "prepare() vs prepare_cached() in rusqlite hot insert loop", + "relevant": [ + "sqlite-prepared-stmt-cache" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999525547027588, + "key": "sqlite-prepared-stmt-cache", + "rank_score": 0.9549970626831055, + "text": "project:fact - [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8). The cache key is the SQL string verbatim, so template strings with interpolated values defeat caching — use `?1, ?2` placeholders instead. Calling `prepare_cached` in a tight loop is effectively free after warmup. (context: Kimetsu brain high-throughput ingest path — replacing prepare() with prepare_cached() cut ingest time by ~30%.)" + }, + { + "ce": 0.018887091428041458, + "key": "cargo-profile-override", + "rank_score": 0.42348283529281616, + "text": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug. The downside: rebuild time increases for that crate. For overflow-checks, `overflow-checks = false` per package speeds up hot loops. Never disable overflow-checks in release for business-critical data-mutating code. `[profile.release] strip = \"debuginfo\"` reduces binary size with minimal impact on stack traces. (context: Kimetsu dev experience — embedding inference was 10x slower in debug builds.)" + }, + { + "ce": 0.14986008405685425, + "key": "gc-trace-env-guard-placement", + "rank_score": 0.3601483106613159, + "text": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site. (context: QQ4 — runs auto-GC on run creation. Env guard placement decision when wiring opportunistic GC into TraceWriter::create.)" + }, + { + "ce": 0.04873214662075043, + "key": "sqlite-fts5-tokenizer", + "rank_score": 0.37106895446777344, + "text": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon. If you switch tokenizers on an existing FTS5 table, you MUST rebuild the shadow tables: `INSERT INTO tbl(tbl) VALUES('rebuild');` — a schema-only change leaves the inverted index unusable. The `porter` stemmer is available as `tokenize='porter unicode61'` but aggressively strips suffixes and hurts precision on technical terms. (context: Kimetsu brain FTS5 index tuning for Rust identifier retrieval.)" + }, + { + "ce": 0.2888507544994354, + "key": "sqlite-vacuum-wal-checkpoint", + "rank_score": 0.37002748250961304, + "text": "project:fact - [tags: rust sqlite vacuum rusqlite windows] When implementing SQLite VACUUM in rusqlite: VACUUM cannot run inside a transaction. rusqlite's Connection does not hold an implicit transaction, so `conn.execute_batch(\"VACUUM;\")` works directly. After VACUUM, run `PRAGMA wal_checkpoint(TRUNCATE);` before measuring file size — on Windows the WAL file can hold significant space that isn't reflected in the main db file until the checkpoint runs. (context: Implementing kimetsu brain compact (Q8) — SQLite VACUUM + WAL checkpoint for accurate post-compact file size.)" + }, + { + "ce": 0.0006060208543203771, + "key": "kimetsu-rerank-pool", + "rank_score": 0.341839998960495, + "text": "project:fact - [tags: kimetsu reranker pool size ann retrieval] kimetsu's retrieval pipeline: ANN (approximate nearest neighbor) retrieves a pool of candidates, then the reranker reorders them, then the top-K are returned. The pool size (default 6 for production, 12 in bench) controls the recall-latency tradeoff: larger pool = higher recall = more reranker calls = more latency. For the jina-tiny reranker, pool 12 adds ~80ms vs pool 6. The bench uses pool 12 to maximize measurable recall differences between rerankers; production uses pool 6 for latency. Increasing pool size beyond 20 has diminishing recall returns on corpora < 1000 memories. (context: Kimetsu ANN pool size tuning for the retrieval benchmark.)" + } + ], + "delivered": [ + "sqlite-prepared-stmt-cache" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.9021148681640625 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "speed up bulk memory ingest by caching SQL statements", + "relevant": [ + "sqlite-prepared-stmt-cache" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.6780275106430054, + "key": "sqlite-prepared-stmt-cache", + "rank_score": 0.9549970626831055, + "text": "project:fact - [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8). The cache key is the SQL string verbatim, so template strings with interpolated values defeat caching — use `?1, ?2` placeholders instead. Calling `prepare_cached` in a tight loop is effectively free after warmup. (context: Kimetsu brain high-throughput ingest path — replacing prepare() with prepare_cached() cut ingest time by ~30%.)" + }, + { + "ce": 0.008608098141849041, + "key": "sqlite-partial-index", + "rank_score": 0.5974882245063782, + "text": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query — the planner uses the partial index only when the WHERE clause matches. Verify index usage with `EXPLAIN QUERY PLAN SELECT ...`. Partial indexes are not supported before SQLite 3.8.0; rusqlite's bundled SQLite is always current, but system SQLite on old Debian/Ubuntu may not be. (context: Optimizing kimetsu brain retrieval query over the active-memories subset.)" + }, + { + "ce": 0.003620142349973321, + "key": "sqlite-foreign-keys-default-off", + "rank_score": 0.5053421854972839, + "text": "project:fact - [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting — every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing. Check your schema with `PRAGMA foreign_key_list(table_name);` and your current setting with `PRAGMA foreign_keys;`. rusqlite does not enable foreign keys automatically. (context: Kimetsu brain schema — memory_tags table has FK to memories table, discovered ON DELETE CASCADE wasn't firing.)" + }, + { + "ce": 0.0004097551282029599, + "key": "onnx-dim-mismatch", + "rank_score": 0.49143868684768677, + "text": "project:fact - [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results — the ANN index shape mismatch isn't always caught at runtime. kimetsu detects this by storing `embedder_id` in the brain schema and refusing to query if the configured embedder differs from what was used at ingest time. Mitigation: re-ingest all memories with the new model, or keep per-memory vector dim metadata. (context: Kimetsu embedder migration — detecting dimension mismatch at startup.)" + }, + { + "ce": 0.00008414748299401253, + "key": "ci-cache-keys", + "rank_score": 0.47471997141838074, + "text": "project:fact - [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key — macOS and Windows have incompatible artifact formats. Separate the registry cache from the build cache: the registry (downloaded crates) changes rarely, the build cache changes every push. Bust the build cache on major dependency changes by adding a manual cache version suffix to the key. (context: Kimetsu CI — cache invalidation strategy.)" + }, + { + "ce": 0.000511709600687027, + "key": "remote-ingest-split-roots", + "rank_score": 0.45122626423835754, + "text": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races. Re-enable kimetsu_brain_ingest_repo in the tool allowlist only when ingest is configured, and INTERCEPT that tools/call in the remote handler (clone+ingest_repo_at_root) before the normal dispatch (which would walk the wrong dir). Hermetic test: git init a temp repo, register url=local path, ingest, then context retrieves the file capsule via FTS (noop embedder). (context: R3c: server-side ingest for kimetsu-remote — cloning repos so file-capsule retrieval works without a local checkout.)" + } + ], + "delivered": [ + "sqlite-prepared-stmt-cache" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7453320026397705 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "partial index on deleted_at IS NULL for faster active memory queries", + "relevant": [ + "sqlite-partial-index" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.999954104423523, + "key": "sqlite-partial-index", + "rank_score": 0.9549970030784607, + "text": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query — the planner uses the partial index only when the WHERE clause matches. Verify index usage with `EXPLAIN QUERY PLAN SELECT ...`. Partial indexes are not supported before SQLite 3.8.0; rusqlite's bundled SQLite is always current, but system SQLite on old Debian/Ubuntu may not be. (context: Optimizing kimetsu brain retrieval query over the active-memories subset.)" + }, + { + "ce": 0.5821903347969055, + "key": "sqlite-json1-extract", + "rank_score": 0.6265627145767212, + "text": "project:fact - [tags: sqlite json1 json_extract rusqlite] SQLite's json1 extension (built in since 3.38.0) lets you index and query JSONB columns with `json_extract(col, '$.field')`. To create a partial index over a JSON field: `CREATE INDEX idx ON memories (json_extract(metadata, '$.scope')) WHERE json_extract(metadata, '$.scope') IS NOT NULL;`. Use `json_each` for array fields. On older SQLite builds (rusqlite links whatever the system provides), check for json1 with `SELECT json('{}');` — an error means it's absent. Always prefer column storage over JSON blobs for frequently queried fields. (context: Kimetsu brain querying metadata scopes without migrating a separate column.)" + }, + { + "ce": 0.014457966201007366, + "key": "import-dedup-seen-ids", + "rank_score": 0.44630923867225647, + "text": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount — both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise. (context: Implementing brain export/import (Q5). First naive approach used a single `seen_ids` set local to the function; the dedup test caught it on the second-import assertion.)" + }, + { + "ce": 0.017854005098342896, + "key": "onnx-dim-mismatch", + "rank_score": 0.43475374579429626, + "text": "project:fact - [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results — the ANN index shape mismatch isn't always caught at runtime. kimetsu detects this by storing `embedder_id` in the brain schema and refusing to query if the configured embedder differs from what was used at ingest time. Mitigation: re-ingest all memories with the new model, or keep per-memory vector dim metadata. (context: Kimetsu embedder migration — detecting dimension mismatch at startup.)" + }, + { + "ce": 0.002409554785117507, + "key": "git-sparse-checkout", + "rank_score": 0.4249013364315033, + "text": "project:fact - [tags: git sparse-checkout partial-clone bandwidth] `git sparse-checkout init --cone` combined with `git clone --filter=blob:none` (partial clone) fetches only the commit graph and tree objects, not blobs. Individual blobs are fetched on demand when accessed. This cuts clone time for large repos from minutes to seconds. For kimetsu server-side ingest, use `git clone --depth 1 --filter=blob:none` for the initial checkout, then `git sparse-checkout set ` to limit the working tree to indexed directories. On `git fetch --depth 1 origin main` for refresh, blobs in the sparse set are updated lazily. (context: Kimetsu remote ingest — reducing bandwidth and disk usage for large repo checkouts.)" + }, + { + "ce": 0.0006743466365151107, + "key": "kimetsu-query-stemming", + "rank_score": 0.4117757976055145, + "text": "project:fact - [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression. The lexical floor (`min_lexical_coverage`) requires at least N stemmed query tokens to match in any retrieved document — this prevents high-semantic-score but lexically-unrelated documents from dominating. Stemming is applied only when the query has >= 3 tokens; short queries skip it. (context: Kimetsu retrieval — query-side stemming implementation.)" + } + ], + "delivered": [ + "sqlite-partial-index", + "sqlite-json1-extract" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7830176949501038 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "the brain query is slow because it scans all rows including soft-deleted ones", + "relevant": [ + "sqlite-partial-index" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.22094492614269257, + "key": "sqlite-partial-index", + "rank_score": 0.9549970030784607, + "text": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query — the planner uses the partial index only when the WHERE clause matches. Verify index usage with `EXPLAIN QUERY PLAN SELECT ...`. Partial indexes are not supported before SQLite 3.8.0; rusqlite's bundled SQLite is always current, but system SQLite on old Debian/Ubuntu may not be. (context: Optimizing kimetsu brain retrieval query over the active-memories subset.)" + }, + { + "ce": 0.00012654418242163956, + "key": "kimetsu-distiller-config", + "rank_score": 0.7074117660522461, + "text": "project:fact - [tags: kimetsu distiller harvest config provider] The kimetsu distiller (auto-harvester) uses a SEPARATE provider configuration from the main agent: `distiller.provider`, `distiller.model`, `distiller.api_key`. This allows running the agent on an expensive model (Claude Opus) while harvesting with a cheap model (Claude Haiku). If `distiller.provider` is not set, it inherits `provider`. The distiller runs as a background task triggered by the post-session hook; it reads the session transcript and emits `kimetsu_brain_record` calls. Distiller timeouts are longer (300s) than normal tool calls (60s) because transcript processing can be slow. (context: Kimetsu distiller provider configuration — agent vs harvester model separation.)" + }, + { + "ce": 0.00005579743083217181, + "key": "init-project-git-boundary", + "rank_score": 0.6841787099838257, + "text": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain. (context: QQ3 — kimetsu setup integration test failed because init_project climbed git tree to real ~/.kimetsu instead of temp workspace)" + }, + { + "ce": 0.00019889276882167906, + "key": "aws-presigned-urls", + "rank_score": 0.5838035941123962, + "text": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time — clock skew > 15 minutes causes `RequestTimeTooSkewed`. kimetsu could use presigned URLs to serve brain exports from S3 without exposing credentials to the client. (context: Kimetsu potential S3 export feature — presigned URL generation.)" + }, + { + "ce": 0.00014364917296916246, + "key": "sqlite-page-size", + "rank_score": 0.6021994948387146, + "text": "project:fact - [tags: sqlite page_size performance rusqlite] SQLite's default page_size is 4096 bytes. For a write-heavy brain database with large BLOB payloads (embedding vectors), raising page_size to 16384 reduces fragmentation and improves sequential scan throughput. `PRAGMA page_size = 16384;` must be set BEFORE the first table is created — changing it on an existing database requires a VACUUM afterward to rebuild all pages. Verify it took effect with `PRAGMA page_size;` after VACUUM. rusqlite's `Connection::open` runs no implicit PRAGMA, so set this in the connection init path. (context: Tuning the kimetsu brain SQLite schema for embedding vector storage.)" + }, + { + "ce": 0.00011211334640393034, + "key": "kimetsu-bench-remote-embedder-singleton", + "rank_score": 0.5751701593399048, + "text": "project:fact - [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval. Workaround: run ONE `--embedders` value per invocation and kill the remote process between runs. The local bench path is not affected (each combo is process-isolated via `--single` child spawn). (context: Kimetsu brain bench --remote known issue — multi-embedder contamination.)" + } + ], + "delivered": [], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.6671459674835205 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "Cargo.lock changed unexpectedly after adding a new workspace crate", + "relevant": [ + "cargo-lockfile-drift" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9927918910980225, + "key": "cargo-feature-unification-embeddings", + "rank_score": 0.7427299618721008, + "text": "project:fact - [2026-09-05] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli). Diagnostic tell: a test that passes alone but fails only under `cargo test --workspace` AND a brand-new crate was just added = suspect feature unification flipping a sibling crate's behavior. (context: Building the kimetsu-remote crate (HTTP MCP server); its default embeddings feature broke 3 kimetsu-chat retrieval tests only under the full workspace test.)" + }, + { + "ce": 0.9999154806137085, + "key": "cargo-lockfile-drift", + "rank_score": 0.9549970030784607, + "text": "project:fact - [2026-09-05] [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this — it errors on any lockfile diff. For library crates, `Cargo.lock` is normally gitignored, but for workspace roots with binary crates it should be committed. Use `cargo update --precise ` to pin a specific dep version without touching unrelated entries. (context: Kimetsu workspace lockfile drift after adding kimetsu-remote crate.)" + }, + { + "ce": 0.22703728079795837, + "key": "cargo-build-script-rerun", + "rank_score": 0.6365931034088135, + "text": "project:fact - [2026-09-05] [tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory. If the build script generates code from a schema file, emit `rerun-if-changed=schema.json`. If there are NO inputs (e.g. the script only inspects env vars), emit `cargo:rerun-if-changed=` with an empty string to suppress re-runs entirely. Missing this directive is the most common cause of unexpectedly slow incremental builds. (context: kimetsu-cli build.rs for embedding version stamps.)" + }, + { + "ce": 0.41104960441589355, + "key": "cargo-target-dir-sharing", + "rank_score": 0.6741681098937988, + "text": "project:fact - [2026-09-05] [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps — use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination. (context: Kimetsu development on Windows with Windows Defender causing intermittent link failures.)" + }, + { + "ce": 0.521369457244873, + "key": "cargo-patch-section", + "rank_score": 0.6113047003746033, + "text": "project:fact - [2026-09-05] [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace — including transitive deps — that depend on `my-crate`. Remove the patch before publishing. Using `[replace]` is deprecated since Cargo 0.47; always use `[patch]`. When patching a crate pinned via an exact version specifier, the patch must satisfy that exact version. Use `cargo tree` to confirm the patch is applied. (context: Kimetsu patching upstream rusqlite for a Windows-specific WAL fix.)" + }, + { + "ce": 0.06732518970966339, + "key": "ci-cache-keys", + "rank_score": 0.6615551114082336, + "text": "project:fact - [2026-09-05] [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key — macOS and Windows have incompatible artifact formats. Separate the registry cache from the build cache: the registry (downloaded crates) changes rarely, the build cache changes every push. Bust the build cache on major dependency changes by adding a manual cache version suffix to the key. (context: Kimetsu CI — cache invalidation strategy.)" + } + ], + "delivered": [ + "cargo-lockfile-drift", + "cargo-feature-unification-embeddings", + "cargo-patch-section", + "cargo-target-dir-sharing" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.840505838394165 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "how do I prevent CI from accepting a modified lockfile silently?", + "relevant": [ + "cargo-lockfile-drift" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.770147979259491, + "key": "cargo-lockfile-drift", + "rank_score": 0.9549969434738159, + "text": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this — it errors on any lockfile diff. For library crates, `Cargo.lock` is normally gitignored, but for workspace roots with binary crates it should be committed. Use `cargo update --precise ` to pin a specific dep version without touching unrelated entries. (context: Kimetsu workspace lockfile drift after adding kimetsu-remote crate.)" + }, + { + "ce": 0.0003805447486229241, + "key": "git-line-endings-windows", + "rank_score": 0.6510795950889587, + "text": "project:fact - [tags: git line-endings windows crlf autocrlf] On Windows, `core.autocrlf=true` (git's default for Windows installs) converts LF to CRLF on checkout and CRLF to LF on commit. This causes spurious diffs when files are edited on Windows then committed — the content is identical but the line endings differ in the index vs the working tree. Fix: set `core.autocrlf=false` and `.gitattributes` with `* text=auto eol=lf` for the repo. For Rust projects, all source files should be LF; only Windows batch scripts need CRLF. Warn: AV scanners that modify newly written files can re-introduce CRLF in files Rust writes. (context: Kimetsu CI — spurious diffs from Windows CRLF conversion.)" + }, + { + "ce": 0.08044571429491043, + "key": "testing-snapshot-churn", + "rank_score": 0.5952745676040649, + "text": "project:fact - [tags: testing snapshot insta assert churn rust] Snapshot tests (e.g. with the `insta` crate) fail whenever the output changes, even for intended changes. In CI, they fail loudly; locally, `cargo insta review` walks you through accepting or rejecting changes. Snapshot churn becomes a problem when output includes timestamps, process IDs, or randomly-ordered maps. Redact these before snapshotting: use `insta::with_settings!({redactions: [\".timestamp\" => \"[TIMESTAMP]\"]})`. For JSON output, sort maps and arrays before comparing. Keep snapshot files in `src/snapshots/` and always commit them — an untracked snapshot file causes the next CI run to fail with a different error than expected. (context: Kimetsu CLI output snapshot tests — reducing churn.)" + }, + { + "ce": 0.0013651131885126233, + "key": "testing-serial-vs-parallel", + "rank_score": 0.5599169135093689, + "text": "project:fact - [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`). `cargo nextest` runs each test in a separate process by default, avoiding the problem entirely at the cost of longer startup time. For kimetsu, prefer nextest in CI and accept that `test_env_lock` exists only for `cargo test` compatibility. (context: Kimetsu test suite — env-var mutation in parallel tests.)" + }, + { + "ce": 0.0005568407359533012, + "key": "tokio-shutdown-ordering", + "rank_score": 0.5494720935821533, + "text": "project:fact - [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries — the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks. `axum::Server::with_graceful_shutdown` handles steps 1-2; you must handle 3-5 manually. (context: Kimetsu remote server graceful shutdown implementation.)" + }, + { + "ce": 0.0007432989659719169, + "key": "kimetsu-query-stemming", + "rank_score": 0.5304966568946838, + "text": "project:fact - [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression. The lexical floor (`min_lexical_coverage`) requires at least N stemmed query tokens to match in any retrieved document — this prevents high-semantic-score but lexically-unrelated documents from dominating. Stemming is applied only when the query has >= 3 tokens; short queries skip it. (context: Kimetsu retrieval — query-side stemming implementation.)" + } + ], + "delivered": [ + "cargo-lockfile-drift" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7438820004463196 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "build.rs reruns on every incremental build even when nothing changed", + "relevant": [ + "cargo-build-script-rerun" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.0009531525429338217, + "key": "bridge-target-enum-seams", + "rank_score": 0.4230843484401703, + "text": "project:fact - [2026-09-05] [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors. (context: Adding BridgeTarget::OpenClaw host to Kimetsu bridge.rs and main.rs in Workstream C)" + }, + { + "ce": 0.004460680298507214, + "key": "aws-sigv4-bedrock-blocking", + "rank_score": 0.4294230043888092, + "text": "project:fact - [2026-09-05] [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed. aws-smithy-runtime-api required as a companion to supply Identity. (context: Implementing BedrockProvider for Kimetsu with blocking reqwest + SigV4 signing, no tokio/aws-sdk)" + }, + { + "ce": 0.01431308127939701, + "key": "clap-version-build-flavor", + "rank_score": 0.4371913969516754, + "text": "project:fact - [2026-09-05] [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds. (context: QQ2: --version build flavor + plugin install self-check)" + }, + { + "ce": 0.00005526986933546141, + "key": "sqlite-foreign-keys-default-off", + "rank_score": 0.43251535296440125, + "text": "project:fact - [2026-09-05] [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting — every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing. Check your schema with `PRAGMA foreign_key_list(table_name);` and your current setting with `PRAGMA foreign_keys;`. rusqlite does not enable foreign keys automatically. (context: Kimetsu brain schema — memory_tags table has FK to memories table, discovered ON DELETE CASCADE wasn't firing.)" + }, + { + "ce": 0.9999358654022217, + "key": "cargo-build-script-rerun", + "rank_score": 0.9549969434738159, + "text": "project:fact - [2026-09-05] [tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory. If the build script generates code from a schema file, emit `rerun-if-changed=schema.json`. If there are NO inputs (e.g. the script only inspects env vars), emit `cargo:rerun-if-changed=` with an empty string to suppress re-runs entirely. Missing this directive is the most common cause of unexpectedly slow incremental builds. (context: kimetsu-cli build.rs for embedding version stamps.)" + }, + { + "ce": 0.0009050053777173162, + "key": "ci-cache-keys", + "rank_score": 0.5027300119400024, + "text": "project:fact - [2026-09-05] [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key — macOS and Windows have incompatible artifact formats. Separate the registry cache from the build cache: the registry (downloaded crates) changes rarely, the build cache changes every push. Bust the build cache on major dependency changes by adding a manual cache version suffix to the key. (context: Kimetsu CI — cache invalidation strategy.)" + } + ], + "delivered": [ + "cargo-build-script-rerun" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8208045363426208 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "incremental cargo build is slow because build script runs every time", + "relevant": [ + "cargo-build-script-rerun" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9998290538787842, + "key": "cargo-build-script-rerun", + "rank_score": 0.9549968838691711, + "text": "project:fact - [tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory. If the build script generates code from a schema file, emit `rerun-if-changed=schema.json`. If there are NO inputs (e.g. the script only inspects env vars), emit `cargo:rerun-if-changed=` with an empty string to suppress re-runs entirely. Missing this directive is the most common cause of unexpectedly slow incremental builds. (context: kimetsu-cli build.rs for embedding version stamps.)" + }, + { + "ce": 0.10662619024515152, + "key": "cargo-profile-override", + "rank_score": 0.562698483467102, + "text": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug. The downside: rebuild time increases for that crate. For overflow-checks, `overflow-checks = false` per package speeds up hot loops. Never disable overflow-checks in release for business-critical data-mutating code. `[profile.release] strip = \"debuginfo\"` reduces binary size with minimal impact on stack traces. (context: Kimetsu dev experience — embedding inference was 10x slower in debug builds.)" + }, + { + "ce": 0.0008178498246707022, + "key": "ci-cache-keys", + "rank_score": 0.5218434929847717, + "text": "project:fact - [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key — macOS and Windows have incompatible artifact formats. Separate the registry cache from the build cache: the registry (downloaded crates) changes rarely, the build cache changes every push. Bust the build cache on major dependency changes by adding a manual cache version suffix to the key. (context: Kimetsu CI — cache invalidation strategy.)" + }, + { + "ce": 0.00252797594293952, + "key": "kimetsu-distiller-config", + "rank_score": 0.510343611240387, + "text": "project:fact - [tags: kimetsu distiller harvest config provider] The kimetsu distiller (auto-harvester) uses a SEPARATE provider configuration from the main agent: `distiller.provider`, `distiller.model`, `distiller.api_key`. This allows running the agent on an expensive model (Claude Opus) while harvesting with a cheap model (Claude Haiku). If `distiller.provider` is not set, it inherits `provider`. The distiller runs as a background task triggered by the post-session hook; it reads the session transcript and emits `kimetsu_brain_record` calls. Distiller timeouts are longer (300s) than normal tool calls (60s) because transcript processing can be slow. (context: Kimetsu distiller provider configuration — agent vs harvester model separation.)" + }, + { + "ce": 0.28699058294296265, + "key": "cargo-incremental-cache-corruption", + "rank_score": 0.5188435316085815, + "text": "project:fact - [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase. Fix: `cargo clean` then rebuild. Adding `CARGO_INCREMENTAL=0` to CI matrices prevents this class of false failures. (context: Kimetsu development — spurious type mismatch errors after branch switches.)" + }, + { + "ce": 0.01702195592224598, + "key": "cargo-feature-unification-embeddings", + "rank_score": 0.5016396045684814, + "text": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli). Diagnostic tell: a test that passes alone but fails only under `cargo test --workspace` AND a brand-new crate was just added = suspect feature unification flipping a sibling crate's behavior. (context: Building the kimetsu-remote crate (HTTP MCP server); its default embeddings feature broke 3 kimetsu-chat retrieval tests only under the full workspace test.)" + } + ], + "delivered": [ + "cargo-build-script-rerun" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8351592421531677 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "a dev-dependency is activating an embeddings feature in my production build", + "relevant": [ + "cargo-dev-dep-leak" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.999002993106842, + "key": "cargo-dev-dep-leak", + "rank_score": 0.9549968838691711, + "text": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates. Run `cargo tree --features ` to trace which crate activated an unexpected feature. (context: Kimetsu testing infra — a dev-dep was activating the embeddings feature in non-test builds.)" + }, + { + "ce": 0.00009903631143970415, + "key": "cargo-patch-section", + "rank_score": 0.55467289686203, + "text": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace — including transitive deps — that depend on `my-crate`. Remove the patch before publishing. Using `[replace]` is deprecated since Cargo 0.47; always use `[patch]`. When patching a crate pinned via an exact version specifier, the patch must satisfy that exact version. Use `cargo tree` to confirm the patch is applied. (context: Kimetsu patching upstream rusqlite for a Windows-specific WAL fix.)" + }, + { + "ce": 0.00568907568231225, + "key": "clap-version-build-flavor", + "rank_score": 0.5253016352653503, + "text": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds. (context: QQ2: --version build flavor + plugin install self-check)" + }, + { + "ce": 0.04660333693027496, + "key": "cargo-feature-unification-embeddings", + "rank_score": 0.5161283612251282, + "text": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli). Diagnostic tell: a test that passes alone but fails only under `cargo test --workspace` AND a brand-new crate was just added = suspect feature unification flipping a sibling crate's behavior. (context: Building the kimetsu-remote crate (HTTP MCP server); its default embeddings feature broke 3 kimetsu-chat retrieval tests only under the full workspace test.)" + }, + { + "ce": 0.013925518840551376, + "key": "cargo-profile-override", + "rank_score": 0.46979257464408875, + "text": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug. The downside: rebuild time increases for that crate. For overflow-checks, `overflow-checks = false` per package speeds up hot loops. Never disable overflow-checks in release for business-critical data-mutating code. `[profile.release] strip = \"debuginfo\"` reduces binary size with minimal impact on stack traces. (context: Kimetsu dev experience — embedding inference was 10x slower in debug builds.)" + }, + { + "ce": 0.00012352890917100012, + "key": "windows-file-locking-av", + "rank_score": 0.41387930512428284, + "text": "project:fact - [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine. For CI, use GitHub-hosted Windows runners which don't have real-time AV. Alternatively, build to a different directory with `CARGO_TARGET_DIR=C:\\tmp\\target`. The error is non-deterministic — it only appears when AV scanning races with the link step. (context: Kimetsu development on Windows — intermittent linker errors.)" + } + ], + "delivered": [ + "cargo-dev-dep-leak" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7432968020439148 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "how do I prevent a test-only feature from bleeding into the non-test compilation?", + "relevant": [ + "cargo-dev-dep-leak" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.021924441680312157, + "key": "cargo-dev-dep-leak", + "rank_score": 0.9549968838691711, + "text": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates. Run `cargo tree --features ` to trace which crate activated an unexpected feature. (context: Kimetsu testing infra — a dev-dep was activating the embeddings feature in non-test builds.)" + }, + { + "ce": 0.023355506360530853, + "key": "cargo-feature-unification-embeddings", + "rank_score": 0.7986748218536377, + "text": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli). Diagnostic tell: a test that passes alone but fails only under `cargo test --workspace` AND a brand-new crate was just added = suspect feature unification flipping a sibling crate's behavior. (context: Building the kimetsu-remote crate (HTTP MCP server); its default embeddings feature broke 3 kimetsu-chat retrieval tests only under the full workspace test.)" + }, + { + "ce": 0.0005154661484993994, + "key": "ci-matrix-explosion", + "rank_score": 0.6216866374015808, + "text": "project:fact - [tags: ci github-actions matrix jobs resources] A CI matrix combining OS (3) x Rust toolchain (3) x features (2) = 18 jobs. Each spawns a runner; at $0.008/min for Ubuntu and $0.016/min for Windows, a 10-minute build costs $2.40 per push. Reduce: test the full matrix only on PRs to main; on feature branches, test only Linux+stable. Use `fail-fast: false` to see all failures, not just the first. Combine related checks (clippy + test) in one job when they share build artifacts. For Windows-specific tests, run only the OS-specific job to reduce cost. (context: Kimetsu CI matrix cost optimization.)" + }, + { + "ce": 0.01470158901065588, + "key": "cfg-cross-platform-dead-code", + "rank_score": 0.6094505786895752, + "text": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform. (context: Adding parse_unix_ps to kimetsu-cli/src/process.rs — used only on Unix at runtime but needed on Windows for cross-platform unit tests.)" + }, + { + "ce": 0.0015618830220773816, + "key": "aws-sigv4-bedrock-blocking", + "rank_score": 0.5832573771476746, + "text": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed. aws-smithy-runtime-api required as a companion to supply Identity. (context: Implementing BedrockProvider for Kimetsu with blocking reqwest + SigV4 signing, no tokio/aws-sdk)" + }, + { + "ce": 0.0013014698633924127, + "key": "mutex-deadlock-user-brain-disabled", + "rank_score": 0.5728944540023804, + "text": "project:fact - [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure — `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation. (context: New tests for Tier-1 perf work called test_env_lock().lock() inside with_user_brain_disabled closure, deadlocking all project::tests that ran after them in the same test binary.)" + } + ], + "delivered": [], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7129013538360596 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "linker errors in target/ caused by antivirus holding the exe file", + "relevant": [ + "cargo-target-dir-sharing", + "windows-file-locking-av" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9991186261177063, + "key": "windows-file-locking-av", + "rank_score": 0.9549968838691711, + "text": "project:fact - [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine. For CI, use GitHub-hosted Windows runners which don't have real-time AV. Alternatively, build to a different directory with `CARGO_TARGET_DIR=C:\\tmp\\target`. The error is non-deterministic — it only appears when AV scanning races with the link step. (context: Kimetsu development on Windows — intermittent linker errors.)" + }, + { + "ce": 0.2745526134967804, + "key": "cargo-target-dir-sharing", + "rank_score": 0.6523240208625793, + "text": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps — use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination. (context: Kimetsu development on Windows with Windows Defender causing intermittent link failures.)" + }, + { + "ce": 0.000053394447604659945, + "key": "sqlite-vacuum-wal-checkpoint", + "rank_score": 0.5364573001861572, + "text": "project:fact - [tags: rust sqlite vacuum rusqlite windows] When implementing SQLite VACUUM in rusqlite: VACUUM cannot run inside a transaction. rusqlite's Connection does not hold an implicit transaction, so `conn.execute_batch(\"VACUUM;\")` works directly. After VACUUM, run `PRAGMA wal_checkpoint(TRUNCATE);` before measuring file size — on Windows the WAL file can hold significant space that isn't reflected in the main db file until the checkpoint runs. (context: Implementing kimetsu brain compact (Q8) — SQLite VACUUM + WAL checkpoint for accurate post-compact file size.)" + }, + { + "ce": 0.0016802760073915124, + "key": "windows-long-paths", + "rank_score": 0.5059171319007874, + "text": "project:fact - [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe. On Windows 10 1607+ the LongPathsEnabled key is sufficient for most tools. `cargo build` itself works after the registry change; MSI installers may still fail on paths > 260 in the installer runtime. (context: Kimetsu CI on Windows Server 2019 — build failed with OS error 3 on deeply nested proc-macro paths.)" + }, + { + "ce": 0.00012321080430410802, + "key": "http-connection-pooling", + "rank_score": 0.38892292976379395, + "text": "project:fact - [tags: http reqwest connection-pool keep-alive rust] reqwest's `Client` holds a connection pool; always create ONE `Client` instance and clone it for each handler — cloning is cheap (Arc under the hood). Creating a `Client::new()` per request defeats connection pooling and causes TCP connection exhaustion under load. The default pool settings: max_idle_per_host=usize::MAX (unbounded), idle_timeout=90s. For a kimetsu outbound client (LLM provider), set `pool_max_idle_per_host(5)` to limit idle connections. On Windows, the underlying hyper+winapi stack may not reuse connections as aggressively as on Linux — set `connection_verbose(true)` on the builder to confirm reuse. (context: Kimetsu provider HTTP client — connection pooling best practices.)" + }, + { + "ce": 0.00012022700684610754, + "key": "testing-snapshot-churn", + "rank_score": 0.396930068731308, + "text": "project:fact - [tags: testing snapshot insta assert churn rust] Snapshot tests (e.g. with the `insta` crate) fail whenever the output changes, even for intended changes. In CI, they fail loudly; locally, `cargo insta review` walks you through accepting or rejecting changes. Snapshot churn becomes a problem when output includes timestamps, process IDs, or randomly-ordered maps. Redact these before snapshotting: use `insta::with_settings!({redactions: [\".timestamp\" => \"[TIMESTAMP]\"]})`. For JSON output, sort maps and arrays before comparing. Keep snapshot files in `src/snapshots/` and always commit them — an untracked snapshot file causes the next CI run to fail with a different error than expected. (context: Kimetsu CLI output snapshot tests — reducing churn.)" + } + ], + "delivered": [ + "windows-file-locking-av" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8219546675682068 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "Access is denied (os error 5) when linking on Windows — how do I fix this?", + "relevant": [ + "windows-file-locking-av" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.00120083789806813, + "key": "sqlite-wal-network-drive", + "rank_score": 0.525835394859314, + "text": "project:fact - [2026-09-05] [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db. Fallback: `PRAGMA journal_mode=DELETE;` is safe over SMB at the cost of lower concurrency. Detect network drives at startup with `GetFileAttributes` checking FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS or using `PathIsNetworkPath`. (context: Users running kimetsu with the brain database on a mapped network drive.)" + }, + { + "ce": 0.0022382375318557024, + "key": "windows-long-paths", + "rank_score": 0.535034716129303, + "text": "project:fact - [2026-09-05] [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe. On Windows 10 1607+ the LongPathsEnabled key is sufficient for most tools. `cargo build` itself works after the registry change; MSI installers may still fail on paths > 260 in the installer runtime. (context: Kimetsu CI on Windows Server 2019 — build failed with OS error 3 on deeply nested proc-macro paths.)" + }, + { + "ce": 0.9980727434158325, + "key": "windows-file-locking-av", + "rank_score": 0.9549968838691711, + "text": "project:fact - [2026-09-05] [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine. For CI, use GitHub-hosted Windows runners which don't have real-time AV. Alternatively, build to a different directory with `CARGO_TARGET_DIR=C:\\tmp\\target`. The error is non-deterministic — it only appears when AV scanning races with the link step. (context: Kimetsu development on Windows — intermittent linker errors.)" + }, + { + "ce": 0.007669401355087757, + "key": "windows-junctions-vs-symlinks", + "rank_score": 0.5061127543449402, + "text": "project:fact - [2026-09-05] [tags: windows junctions symlinks rust std::fs] On Windows, directory junctions (NTFS reparse points) behave like symlinks for directory traversal but `std::fs::symlink_metadata` returns `FileType::is_symlink() = false` for junctions (only true for regular symlinks). Use `std::fs::read_link` — it succeeds for both junction and symlink. `walkdir` crate's `follow_links` follows both, but its `is_symlink()` method correctly reports only actual symlinks. Creating symlinks requires SeCreateSymbolicLinkPrivilege (admin or Developer Mode). Creating junctions requires no special privilege. Use junctions for internal tooling that doesn't need to cross volumes. (context: Kimetsu path handling for brain symlink detection on Windows.)" + }, + { + "ce": 0.012990143150091171, + "key": "http-tls-roots", + "rank_score": 0.53460294008255, + "text": "project:fact - [2026-09-05] [tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle — the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle. Alternatively, add the custom root with `add_root_certificate`. On Linux, the system CA bundle is at `/etc/ssl/certs/ca-certificates.crt`; on Windows it's in the Windows Certificate Store. (context: Kimetsu on a corporate Windows machine with a custom proxy CA.)" + }, + { + "ce": 0.030993828549981117, + "key": "ci-cache-keys", + "rank_score": 0.5967901945114136, + "text": "project:fact - [2026-09-05] [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key — macOS and Windows have incompatible artifact formats. Separate the registry cache from the build cache: the registry (downloaded crates) changes rarely, the build cache changes every push. Bust the build cache on major dependency changes by adding a manual cache version suffix to the key. (context: Kimetsu CI — cache invalidation strategy.)" + } + ], + "delivered": [ + "windows-file-locking-av" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7543023824691772 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "incremental build broke with a type mismatch after switching branches", + "relevant": [ + "cargo-incremental-cache-corruption" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.0022598879877477884, + "key": "clap-version-build-flavor", + "rank_score": 0.4276600182056427, + "text": "project:fact - [2026-09-05] [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds. (context: QQ2: --version build flavor + plugin install self-check)" + }, + { + "ce": 0.01778547093272209, + "key": "cargo-build-script-rerun", + "rank_score": 0.4690442681312561, + "text": "project:fact - [2026-09-05] [tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory. If the build script generates code from a schema file, emit `rerun-if-changed=schema.json`. If there are NO inputs (e.g. the script only inspects env vars), emit `cargo:rerun-if-changed=` with an empty string to suppress re-runs entirely. Missing this directive is the most common cause of unexpectedly slow incremental builds. (context: kimetsu-cli build.rs for embedding version stamps.)" + }, + { + "ce": 0.9954663515090942, + "key": "cargo-incremental-cache-corruption", + "rank_score": 0.763997495174408, + "text": "project:fact - [2026-09-05] [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase. Fix: `cargo clean` then rebuild. Adding `CARGO_INCREMENTAL=0` to CI matrices prevents this class of false failures. (context: Kimetsu development — spurious type mismatch errors after branch switches.)" + }, + { + "ce": 0.025738010182976723, + "key": "onnx-dim-mismatch", + "rank_score": 0.4830358922481537, + "text": "project:fact - [2026-09-05] [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results — the ANN index shape mismatch isn't always caught at runtime. kimetsu detects this by storing `embedder_id` in the brain schema and refusing to query if the configured embedder differs from what was used at ingest time. Mitigation: re-ingest all memories with the new model, or keep per-memory vector dim metadata. (context: Kimetsu embedder migration — detecting dimension mismatch at startup.)" + }, + { + "ce": 0.00013734796084463596, + "key": "git-submodule-pinning", + "rank_score": 0.429722398519516, + "text": "project:fact - [2026-09-05] [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip — this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version. If a submodule is the kimetsu-bench repo inside the main repo, pin the bench SHA after validating the dataset change. Use `git diff HEAD -- bench` to see the pinned SHA change before committing. (context: Kimetsu bench as a git submodule of the main repo.)" + }, + { + "ce": 0.0005467567243613303, + "key": "tokio-select-cancellation", + "rank_score": 0.3990602195262909, + "text": "project:fact - [2026-09-05] [tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded. For correctness, cancelled futures must be cancellation-safe: holding no partially committed state. `tokio::sync::watch::Receiver::changed()` is cancellation-safe; `tokio::sync::mpsc::Sender::send()` is NOT (the item is lost). In kimetsu shutdown, use a `CancellationToken` and `select!` branches that are all cancellation-safe. (context: Kimetsu remote graceful shutdown — race between incoming requests and shutdown signal.)" + } + ], + "delivered": [ + "cargo-incremental-cache-corruption" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7454535961151123 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "cargo reports a type error that references a type not in the codebase", + "relevant": [ + "cargo-incremental-cache-corruption" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.997864305973053, + "key": "cargo-incremental-cache-corruption", + "rank_score": 0.7639974355697632, + "text": "project:fact - [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase. Fix: `cargo clean` then rebuild. Adding `CARGO_INCREMENTAL=0` to CI matrices prevents this class of false failures. (context: Kimetsu development — spurious type mismatch errors after branch switches.)" + }, + { + "ce": 0.00008920997061068192, + "key": "mcp-transcript-paths", + "rank_score": 0.5448154807090759, + "text": "project:fact - [tags: mcp transcript paths kimetsu hooks runs] kimetsu writes run transcripts to `/.kimetsu/runs//`. The post-session hook reads the latest run's transcript to trigger memory harvest. On Windows, the path uses backslashes internally but the MCP JSON must use forward slashes or the host may reject path-type arguments. `std::path::Path::display()` produces backslashes on Windows — use `.to_string_lossy().replace('\\\\', \"/\")` when serializing paths for MCP protocol. The transcript path is included in the `kimetsu_brain_context` response under the `run_dir` field for the distiller's reference. (context: Kimetsu transcript path handling in MCP responses on Windows.)" + }, + { + "ce": 0.0009943329496309161, + "key": "aws-retry-throttling", + "rank_score": 0.49514612555503845, + "text": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with ±25% jitter. Do NOT retry `ValidationException` or `AccessDeniedException` — these are permanent errors. `ModelStreamErrorException` during streaming may be retryable. Log the `x-amzn-requestid` header from failed responses for AWS support debugging. (context: Kimetsu Bedrock provider retry logic.)" + }, + { + "ce": 0.00006463916361099109, + "key": "kimetsu-capsule-budgets", + "rank_score": 0.4646901786327362, + "text": "project:fact - [tags: kimetsu capsule tokens budget retrieval] kimetsu retrieval enforces a token budget per capsule type: memory capsules are capped at 6000 tokens total (across all retrieved memories), file capsules at 3000 tokens. When a memory is large and would exceed the budget, it is truncated at a sentence boundary. The budget is enforced AFTER reranking — reranking may reorder results so that a truncated high-ranked memory displaces a full lower-ranked one. `noise_caps` in the bench output counts capsules that scored below the noise floor — they consume budget without contributing signal. Lower noise_caps = tighter retrieval. (context: Kimetsu capsule budget enforcement and noise floor interaction.)" + }, + { + "ce": 0.0003356271190568805, + "key": "aws-sigv4-bedrock-blocking", + "rank_score": 0.4225093722343445, + "text": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed. aws-smithy-runtime-api required as a companion to supply Identity. (context: Implementing BedrockProvider for Kimetsu with blocking reqwest + SigV4 signing, no tokio/aws-sdk)" + }, + { + "ce": 0.009546617977321148, + "key": "windows-junctions-vs-symlinks", + "rank_score": 0.41638630628585815, + "text": "project:fact - [tags: windows junctions symlinks rust std::fs] On Windows, directory junctions (NTFS reparse points) behave like symlinks for directory traversal but `std::fs::symlink_metadata` returns `FileType::is_symlink() = false` for junctions (only true for regular symlinks). Use `std::fs::read_link` — it succeeds for both junction and symlink. `walkdir` crate's `follow_links` follows both, but its `is_symlink()` method correctly reports only actual symlinks. Creating symlinks requires SeCreateSymbolicLinkPrivilege (admin or Developer Mode). Creating junctions requires no special privilege. Use junctions for internal tooling that doesn't need to cross volumes. (context: Kimetsu path handling for brain symlink detection on Windows.)" + } + ], + "delivered": [ + "cargo-incremental-cache-corruption" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7966704368591309 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "compile fastembed at O2 in debug builds to avoid slow embedding inference", + "relevant": [ + "cargo-profile-override" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999233484268188, + "key": "cargo-profile-override", + "rank_score": 0.9549967646598816, + "text": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug. The downside: rebuild time increases for that crate. For overflow-checks, `overflow-checks = false` per package speeds up hot loops. Never disable overflow-checks in release for business-critical data-mutating code. `[profile.release] strip = \"debuginfo\"` reduces binary size with minimal impact on stack traces. (context: Kimetsu dev experience — embedding inference was 10x slower in debug builds.)" + }, + { + "ce": 0.7583951950073242, + "key": "mcp-tool-timeouts", + "rank_score": 0.6638826727867126, + "text": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking — in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize — keep it in a process-global `OnceLock`). The reranker adds another 200-800ms; jina-tiny is the fastest. If tool calls are still slow, log the per-stage latency with `tracing::info!` at DEBUG level and profile under load. (context: Kimetsu MCP tool latency optimization.)" + }, + { + "ce": 0.02314961515367031, + "key": "onnx-batch-padding", + "rank_score": 0.46594417095184326, + "text": "project:fact - [tags: onnx batch padding attention-mask embeddings] When running batch inference with an ONNX model, all inputs in the batch must be padded to the same sequence length. The `attention_mask` tensor marks which tokens are real (1) and which are padding (0). Failing to pass `attention_mask` causes the model to average-pool over padding tokens, producing systematically lower-norm embeddings. With ORT (ort crate), construct the mask as a 2-D i64 tensor `[batch, seq_len]` with 1s for real tokens and 0s for padding. For variable-length batches, pad to `max(lengths)` in the batch, not to `model.max_length`. (context: Kimetsu embedding batch inference with ORT — missing attention mask caused MRR degradation.)" + }, + { + "ce": 0.002141421427950263, + "key": "onnx-model-cache-paths", + "rank_score": 0.4581427574157715, + "text": "project:fact - [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use. In kimetsu, `KIMETSU_EMBEDDER_CACHE` overrides the path and is forwarded when spawning child bench processes — without forwarding it, each child re-downloads the model. (context: Kimetsu brain bench on CI — model cache path handling in child processes.)" + }, + { + "ce": 0.019833113998174667, + "key": "tokio-blocking-in-async", + "rank_score": 0.4597815275192261, + "text": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking — never call rusqlite directly from an async fn without spawn_blocking. fastembed inference is also blocking (ONNX Runtime is synchronous). The threshold: any operation taking more than 100 microseconds that can't be made async belongs in spawn_blocking. Ignoring this causes tail-latency spikes and request timeouts under load in kimetsu-remote. (context: Kimetsu remote server — SQLite and embedding calls from async handlers.)" + }, + { + "ce": 0.03808702901005745, + "key": "cargo-feature-unification-embeddings", + "rank_score": 0.44945043325424194, + "text": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli). Diagnostic tell: a test that passes alone but fails only under `cargo test --workspace` AND a brand-new crate was just added = suspect feature unification flipping a sibling crate's behavior. (context: Building the kimetsu-remote crate (HTTP MCP server); its default embeddings feature broke 3 kimetsu-chat retrieval tests only under the full workspace test.)" + } + ], + "delivered": [ + "cargo-profile-override", + "mcp-tool-timeouts" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7015188336372375 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "override compilation profile for a single crate in a Cargo workspace", + "relevant": [ + "cargo-profile-override" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9970531463623047, + "key": "cargo-patch-section", + "rank_score": 0.9549967646598816, + "text": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace — including transitive deps — that depend on `my-crate`. Remove the patch before publishing. Using `[replace]` is deprecated since Cargo 0.47; always use `[patch]`. When patching a crate pinned via an exact version specifier, the patch must satisfy that exact version. Use `cargo tree` to confirm the patch is applied. (context: Kimetsu patching upstream rusqlite for a Windows-specific WAL fix.)" + }, + { + "ce": 0.9999361038208008, + "key": "cargo-profile-override", + "rank_score": 0.8862169981002808, + "text": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug. The downside: rebuild time increases for that crate. For overflow-checks, `overflow-checks = false` per package speeds up hot loops. Never disable overflow-checks in release for business-critical data-mutating code. `[profile.release] strip = \"debuginfo\"` reduces binary size with minimal impact on stack traces. (context: Kimetsu dev experience — embedding inference was 10x slower in debug builds.)" + }, + { + "ce": 0.9418804049491882, + "key": "cargo-target-dir-sharing", + "rank_score": 0.8657375574111938, + "text": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps — use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination. (context: Kimetsu development on Windows with Windows Defender causing intermittent link failures.)" + }, + { + "ce": 0.2980741262435913, + "key": "cargo-feature-unification-embeddings", + "rank_score": 0.7207715511322021, + "text": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli). Diagnostic tell: a test that passes alone but fails only under `cargo test --workspace` AND a brand-new crate was just added = suspect feature unification flipping a sibling crate's behavior. (context: Building the kimetsu-remote crate (HTTP MCP server); its default embeddings feature broke 3 kimetsu-chat retrieval tests only under the full workspace test.)" + }, + { + "ce": 0.6213672161102295, + "key": "cargo-lockfile-drift", + "rank_score": 0.7580103278160095, + "text": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this — it errors on any lockfile diff. For library crates, `Cargo.lock` is normally gitignored, but for workspace roots with binary crates it should be committed. Use `cargo update --precise ` to pin a specific dep version without touching unrelated entries. (context: Kimetsu workspace lockfile drift after adding kimetsu-remote crate.)" + }, + { + "ce": 0.2736225426197052, + "key": "cargo-dev-dep-leak", + "rank_score": 0.7135303616523743, + "text": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates. Run `cargo tree --features ` to trace which crate activated an unexpected feature. (context: Kimetsu testing infra — a dev-dep was activating the embeddings feature in non-test builds.)" + } + ], + "delivered": [ + "cargo-profile-override", + "cargo-patch-section", + "cargo-target-dir-sharing", + "cargo-lockfile-drift" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8045998811721802 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "[patch.crates-io] workspace dependency override", + "relevant": [ + "cargo-patch-section" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999796152114868, + "key": "cargo-patch-section", + "rank_score": 0.9549967050552368, + "text": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace — including transitive deps — that depend on `my-crate`. Remove the patch before publishing. Using `[replace]` is deprecated since Cargo 0.47; always use `[patch]`. When patching a crate pinned via an exact version specifier, the patch must satisfy that exact version. Use `cargo tree` to confirm the patch is applied. (context: Kimetsu patching upstream rusqlite for a Windows-specific WAL fix.)" + }, + { + "ce": 0.3068869113922119, + "key": "cargo-lockfile-drift", + "rank_score": 0.6288118362426758, + "text": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this — it errors on any lockfile diff. For library crates, `Cargo.lock` is normally gitignored, but for workspace roots with binary crates it should be committed. Use `cargo update --precise ` to pin a specific dep version without touching unrelated entries. (context: Kimetsu workspace lockfile drift after adding kimetsu-remote crate.)" + }, + { + "ce": 0.7515549063682556, + "key": "cargo-dev-dep-leak", + "rank_score": 0.5714206695556641, + "text": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates. Run `cargo tree --features ` to trace which crate activated an unexpected feature. (context: Kimetsu testing infra — a dev-dep was activating the embeddings feature in non-test builds.)" + }, + { + "ce": 0.11113515496253967, + "key": "cargo-target-dir-sharing", + "rank_score": 0.43650415539741516, + "text": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps — use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination. (context: Kimetsu development on Windows with Windows Defender causing intermittent link failures.)" + }, + { + "ce": 0.021420137956738472, + "key": "cargo-feature-unification-embeddings", + "rank_score": 0.44192126393318176, + "text": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli). Diagnostic tell: a test that passes alone but fails only under `cargo test --workspace` AND a brand-new crate was just added = suspect feature unification flipping a sibling crate's behavior. (context: Building the kimetsu-remote crate (HTTP MCP server); its default embeddings feature broke 3 kimetsu-chat retrieval tests only under the full workspace test.)" + }, + { + "ce": 0.6568455696105957, + "key": "cargo-profile-override", + "rank_score": 0.4035162627696991, + "text": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug. The downside: rebuild time increases for that crate. For overflow-checks, `overflow-checks = false` per package speeds up hot loops. Never disable overflow-checks in release for business-critical data-mutating code. `[profile.release] strip = \"debuginfo\"` reduces binary size with minimal impact on stack traces. (context: Kimetsu dev experience — embedding inference was 10x slower in debug builds.)" + } + ], + "delivered": [ + "cargo-patch-section", + "cargo-dev-dep-leak", + "cargo-profile-override", + "cargo-lockfile-drift" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8668897747993469 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "pin minimum supported Rust version in Cargo.toml", + "relevant": [ + "cargo-msrv" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9998986721038818, + "key": "cargo-msrv", + "rank_score": 0.9549967050552368, + "text": "project:fact - [tags: cargo rust msrv edition compatibility] Set `rust-version` in each `Cargo.toml` to declare the minimum supported Rust version (MSRV). Cargo enforces this with `--check`: `cargo check` fails if the toolchain is older than `rust-version`. Keep MSRV as old as your oldest supported deployment target. When bumping MSRV, update the CI matrix and the workspace root. Common trap: a transitive dep bumps its MSRV, pulling yours up silently — check with `cargo msrv` (cargo-msrv crate) or `cargo tree -e features | grep msrv`. Edition 2021 requires Rust >= 1.56.0. (context: Kimetsu workspace MSRV policy — ensuring it runs on the LTS toolchain.)" + }, + { + "ce": 0.24367043375968933, + "key": "cargo-patch-section", + "rank_score": 0.7447875142097473, + "text": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace — including transitive deps — that depend on `my-crate`. Remove the patch before publishing. Using `[replace]` is deprecated since Cargo 0.47; always use `[patch]`. When patching a crate pinned via an exact version specifier, the patch must satisfy that exact version. Use `cargo tree` to confirm the patch is applied. (context: Kimetsu patching upstream rusqlite for a Windows-specific WAL fix.)" + }, + { + "ce": 0.012054217047989368, + "key": "cargo-lockfile-drift", + "rank_score": 0.711948812007904, + "text": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this — it errors on any lockfile diff. For library crates, `Cargo.lock` is normally gitignored, but for workspace roots with binary crates it should be committed. Use `cargo update --precise ` to pin a specific dep version without touching unrelated entries. (context: Kimetsu workspace lockfile drift after adding kimetsu-remote crate.)" + }, + { + "ce": 0.0001138434890890494, + "key": "git-submodule-pinning", + "rank_score": 0.6056675910949707, + "text": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip — this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version. If a submodule is the kimetsu-bench repo inside the main repo, pin the bench SHA after validating the dataset change. Use `git diff HEAD -- bench` to see the pinned SHA change before committing. (context: Kimetsu bench as a git submodule of the main repo.)" + }, + { + "ce": 0.10346276313066483, + "key": "clap-version-build-flavor", + "rank_score": 0.5320826172828674, + "text": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds. (context: QQ2: --version build flavor + plugin install self-check)" + }, + { + "ce": 0.0015564716886729002, + "key": "testing-fixture-drift", + "rank_score": 0.4775216579437256, + "text": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code. For kimetsu, `EvalFixture::from_memories(memories)` constructs a dataset from the exported format — use it in tests instead of hardcoded JSON. Tag fixture files with the schema version they were generated against in a comment. (context: Kimetsu eval fixture drift after schema migration.)" + } + ], + "delivered": [ + "cargo-msrv" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8084843158721924 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "Windows path over 260 characters causes OS error 3 during Cargo build", + "relevant": [ + "windows-long-paths" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9998657703399658, + "key": "windows-long-paths", + "rank_score": 0.9549967050552368, + "text": "project:fact - [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe. On Windows 10 1607+ the LongPathsEnabled key is sufficient for most tools. `cargo build` itself works after the registry change; MSI installers may still fail on paths > 260 in the installer runtime. (context: Kimetsu CI on Windows Server 2019 — build failed with OS error 3 on deeply nested proc-macro paths.)" + }, + { + "ce": 0.6231384873390198, + "key": "windows-file-locking-av", + "rank_score": 0.7186020016670227, + "text": "project:fact - [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine. For CI, use GitHub-hosted Windows runners which don't have real-time AV. Alternatively, build to a different directory with `CARGO_TARGET_DIR=C:\\tmp\\target`. The error is non-deterministic — it only appears when AV scanning races with the link step. (context: Kimetsu development on Windows — intermittent linker errors.)" + }, + { + "ce": 0.08322722464799881, + "key": "ci-cache-keys", + "rank_score": 0.691805899143219, + "text": "project:fact - [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key — macOS and Windows have incompatible artifact formats. Separate the registry cache from the build cache: the registry (downloaded crates) changes rarely, the build cache changes every push. Bust the build cache on major dependency changes by adding a manual cache version suffix to the key. (context: Kimetsu CI — cache invalidation strategy.)" + }, + { + "ce": 0.0031271057669073343, + "key": "cargo-build-script-rerun", + "rank_score": 0.588608980178833, + "text": "project:fact - [tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory. If the build script generates code from a schema file, emit `rerun-if-changed=schema.json`. If there are NO inputs (e.g. the script only inspects env vars), emit `cargo:rerun-if-changed=` with an empty string to suppress re-runs entirely. Missing this directive is the most common cause of unexpectedly slow incremental builds. (context: kimetsu-cli build.rs for embedding version stamps.)" + }, + { + "ce": 0.021381016820669174, + "key": "cargo-target-dir-sharing", + "rank_score": 0.630227267742157, + "text": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps — use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination. (context: Kimetsu development on Windows with Windows Defender causing intermittent link failures.)" + }, + { + "ce": 0.004787543322890997, + "key": "cargo-patch-section", + "rank_score": 0.5824382305145264, + "text": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace — including transitive deps — that depend on `my-crate`. Remove the patch before publishing. Using `[replace]` is deprecated since Cargo 0.47; always use `[patch]`. When patching a crate pinned via an exact version specifier, the patch must satisfy that exact version. Use `cargo tree` to confirm the patch is applied. (context: Kimetsu patching upstream rusqlite for a Windows-specific WAL fix.)" + } + ], + "delivered": [ + "windows-long-paths", + "windows-file-locking-av" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8014876246452332 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "how do I enable long file paths for Cargo on Windows?", + "relevant": [ + "windows-long-paths" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999781847000122, + "key": "windows-long-paths", + "rank_score": 0.954996645450592, + "text": "project:fact - [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe. On Windows 10 1607+ the LongPathsEnabled key is sufficient for most tools. `cargo build` itself works after the registry change; MSI installers may still fail on paths > 260 in the installer runtime. (context: Kimetsu CI on Windows Server 2019 — build failed with OS error 3 on deeply nested proc-macro paths.)" + }, + { + "ce": 0.7387577891349792, + "key": "windows-registry-rust", + "rank_score": 0.7541147470474243, + "text": "project:fact - [tags: windows registry rust winreg read write] Reading and writing the Windows registry from Rust requires the `winreg` crate. Open a key with `RegKey::predef(HKEY_LOCAL_MACHINE).open_subkey_with_flags(path, KEY_READ)` — use `KEY_READ` for reads and `KEY_READ | KEY_WRITE` for writes (NOT `KEY_ALL_ACCESS`, which requires admin). To set a DWORD value: `key.set_value(\"LongPathsEnabled\", &1u32)`. Registry paths use backslash separators and are case-insensitive. Prefer reading env vars over registry for runtime config — registry reads are expensive (kernel transition) and inappropriate for hot paths. For kimetsu, registry access is limited to the `kimetsu doctor` check for long-path enablement. (context: Kimetsu doctor — checking LongPathsEnabled registry value on Windows.)" + }, + { + "ce": 0.0197498369961977, + "key": "cargo-dev-dep-leak", + "rank_score": 0.5659966468811035, + "text": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates. Run `cargo tree --features ` to trace which crate activated an unexpected feature. (context: Kimetsu testing infra — a dev-dep was activating the embeddings feature in non-test builds.)" + }, + { + "ce": 0.02570272609591484, + "key": "remote-ingest-split-roots", + "rank_score": 0.5394165515899658, + "text": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races. Re-enable kimetsu_brain_ingest_repo in the tool allowlist only when ingest is configured, and INTERCEPT that tools/call in the remote handler (clone+ingest_repo_at_root) before the normal dispatch (which would walk the wrong dir). Hermetic test: git init a temp repo, register url=local path, ingest, then context retrieves the file capsule via FTS (noop embedder). (context: R3c: server-side ingest for kimetsu-remote — cloning repos so file-capsule retrieval works without a local checkout.)" + }, + { + "ce": 0.000774132611695677, + "key": "sqlite-wal-network-drive", + "rank_score": 0.5251799821853638, + "text": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db. Fallback: `PRAGMA journal_mode=DELETE;` is safe over SMB at the cost of lower concurrency. Detect network drives at startup with `GetFileAttributes` checking FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS or using `PathIsNetworkPath`. (context: Users running kimetsu with the brain database on a mapped network drive.)" + }, + { + "ce": 0.047868430614471436, + "key": "cargo-target-dir-sharing", + "rank_score": 0.5340715050697327, + "text": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps — use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination. (context: Kimetsu development on Windows with Windows Defender causing intermittent link failures.)" + } + ], + "delivered": [ + "windows-long-paths", + "windows-registry-rust" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8513782620429993 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "intermittent sharing violation errors when Rust linker writes the exe on Windows", + "relevant": [ + "windows-file-locking-av" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.027952002361416817, + "key": "sqlite-busy-timeout-wal", + "rank_score": 0.4623258113861084, + "text": "project:fact - [2026-09-05] [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch. Set the timeout before any transaction, not inside one — it is a connection-level property. (context: Kimetsu brain writer and reader processes sharing the same SQLite brain database.)" + }, + { + "ce": 0.004712042864412069, + "key": "cargo-target-dir-sharing", + "rank_score": 0.5414409041404724, + "text": "project:fact - [2026-09-05] [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps — use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination. (context: Kimetsu development on Windows with Windows Defender causing intermittent link failures.)" + }, + { + "ce": 0.0023048410657793283, + "key": "windows-long-paths", + "rank_score": 0.4386613368988037, + "text": "project:fact - [2026-09-05] [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe. On Windows 10 1607+ the LongPathsEnabled key is sufficient for most tools. `cargo build` itself works after the registry change; MSI installers may still fail on paths > 260 in the installer runtime. (context: Kimetsu CI on Windows Server 2019 — build failed with OS error 3 on deeply nested proc-macro paths.)" + }, + { + "ce": 0.9999452829360962, + "key": "windows-file-locking-av", + "rank_score": 0.954996645450592, + "text": "project:fact - [2026-09-05] [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine. For CI, use GitHub-hosted Windows runners which don't have real-time AV. Alternatively, build to a different directory with `CARGO_TARGET_DIR=C:\\tmp\\target`. The error is non-deterministic — it only appears when AV scanning races with the link step. (context: Kimetsu development on Windows — intermittent linker errors.)" + }, + { + "ce": 0.1507275253534317, + "key": "windows-exit-codes", + "rank_score": 0.4774697721004486, + "text": "project:fact - [2026-09-05] [tags: windows exit-codes rust process child] On Windows, process exit codes are 32-bit unsigned integers (DWORD). Rust's `ExitStatus::code()` returns `Option` — it's `None` if the process was killed by a signal (which Windows doesn't use; instead, TerminateProcess with a code). Conventional codes: 0=success, 1=generic error, 0xC0000005=access violation. Programs that call `std::process::exit(-1)` on Windows produce exit code 0xFFFFFFFF (4294967295), not -1. When checking for success in a subprocess chain, always check `status.success()` rather than `status.code() == Some(0)` to handle this portably. (context: Kimetsu update binary replacement — exit code handling.)" + }, + { + "ce": 0.008183373138308525, + "key": "testing-temp-dirs-ci", + "rank_score": 0.413443386554718, + "text": "project:fact - [2026-09-05] [tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure. On Windows, `env::temp_dir()` returns `C:\\Users\\\\AppData\\Local\\Temp` — ensure the test binary has write permissions there. Avoid using the workspace root as a temp dir — tests should never write to the source tree. (context: Kimetsu test infrastructure — temp directory discipline.)" + } + ], + "delivered": [ + "windows-file-locking-av" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8282327055931091 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "Rust walkdir follows junctions differently from symlinks on Windows", + "relevant": [ + "windows-junctions-vs-symlinks" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999210834503174, + "key": "windows-junctions-vs-symlinks", + "rank_score": 0.954996645450592, + "text": "project:fact - [tags: windows junctions symlinks rust std::fs] On Windows, directory junctions (NTFS reparse points) behave like symlinks for directory traversal but `std::fs::symlink_metadata` returns `FileType::is_symlink() = false` for junctions (only true for regular symlinks). Use `std::fs::read_link` — it succeeds for both junction and symlink. `walkdir` crate's `follow_links` follows both, but its `is_symlink()` method correctly reports only actual symlinks. Creating symlinks requires SeCreateSymbolicLinkPrivilege (admin or Developer Mode). Creating junctions requires no special privilege. Use junctions for internal tooling that doesn't need to cross volumes. (context: Kimetsu path handling for brain symlink detection on Windows.)" + }, + { + "ce": 0.00028873002156615257, + "key": "bridge-target-enum-seams", + "rank_score": 0.3662479519844055, + "text": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors. (context: Adding BridgeTarget::OpenClaw host to Kimetsu bridge.rs and main.rs in Workstream C)" + }, + { + "ce": 0.0010488234693184495, + "key": "cfg-cross-platform-dead-code", + "rank_score": 0.3435054421424866, + "text": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform. (context: Adding parse_unix_ps to kimetsu-cli/src/process.rs — used only on Unix at runtime but needed on Windows for cross-platform unit tests.)" + }, + { + "ce": 0.00034819572465494275, + "key": "http-tls-roots", + "rank_score": 0.32060864567756653, + "text": "project:fact - [tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle — the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle. Alternatively, add the custom root with `add_root_certificate`. On Linux, the system CA bundle is at `/etc/ssl/certs/ca-certificates.crt`; on Windows it's in the Windows Certificate Store. (context: Kimetsu on a corporate Windows machine with a custom proxy CA.)" + }, + { + "ce": 0.005460457876324654, + "key": "windows-registry-rust", + "rank_score": 0.33297765254974365, + "text": "project:fact - [tags: windows registry rust winreg read write] Reading and writing the Windows registry from Rust requires the `winreg` crate. Open a key with `RegKey::predef(HKEY_LOCAL_MACHINE).open_subkey_with_flags(path, KEY_READ)` — use `KEY_READ` for reads and `KEY_READ | KEY_WRITE` for writes (NOT `KEY_ALL_ACCESS`, which requires admin). To set a DWORD value: `key.set_value(\"LongPathsEnabled\", &1u32)`. Registry paths use backslash separators and are case-insensitive. Prefer reading env vars over registry for runtime config — registry reads are expensive (kernel transition) and inappropriate for hot paths. For kimetsu, registry access is limited to the `kimetsu doctor` check for long-path enablement. (context: Kimetsu doctor — checking LongPathsEnabled registry value on Windows.)" + }, + { + "ce": 0.0022741227876394987, + "key": "git-line-endings-windows", + "rank_score": 0.34021061658859253, + "text": "project:fact - [tags: git line-endings windows crlf autocrlf] On Windows, `core.autocrlf=true` (git's default for Windows installs) converts LF to CRLF on checkout and CRLF to LF on commit. This causes spurious diffs when files are edited on Windows then committed — the content is identical but the line endings differ in the index vs the working tree. Fix: set `core.autocrlf=false` and `.gitattributes` with `* text=auto eol=lf` for the repo. For Rust projects, all source files should be LF; only Windows batch scripts need CRLF. Warn: AV scanners that modify newly written files can re-introduce CRLF in files Rust writes. (context: Kimetsu CI — spurious diffs from Windows CRLF conversion.)" + } + ], + "delivered": [ + "windows-junctions-vs-symlinks" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.828331708908081 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "UNC path canonicalize returns verbatim prefix — how do I strip it?", + "relevant": [ + "windows-unc-paths" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.999847412109375, + "key": "windows-unc-paths", + "rank_score": 0.9549965858459473, + "text": "project:fact - [tags: windows unc-paths rust std::fs] Windows UNC paths (`\\\\server\\share\\...`) are not supported by most Rust `std::fs` operations unless passed through the extended-length prefix `\\\\?\\UNC\\server\\share\\...`. `std::path::Path::new(\"\\\\\\\\server\\\\share\")` works for basic operations but breaks with `canonicalize()` which returns the verbatim prefix form. When walking directory trees that may start on UNC paths, use the `dunce` crate to strip the verbatim prefix before comparing or displaying paths. Never `cd` into a UNC path in a subprocess started with `std::process::Command` — the subprocess may not inherit it correctly on older Windows. (context: Kimetsu ingest walking paths on network-mounted project directories.)" + }, + { + "ce": 0.07483544945716858, + "key": "sqlite-fts5-tokenizer", + "rank_score": 0.44965752959251404, + "text": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon. If you switch tokenizers on an existing FTS5 table, you MUST rebuild the shadow tables: `INSERT INTO tbl(tbl) VALUES('rebuild');` — a schema-only change leaves the inverted index unusable. The `porter` stemmer is available as `tokenize='porter unicode61'` but aggressively strips suffixes and hurts precision on technical terms. (context: Kimetsu brain FTS5 index tuning for Rust identifier retrieval.)" + }, + { + "ce": 0.0024269497953355312, + "key": "onnx-prefix-instructions", + "rank_score": 0.41674479842185974, + "text": "project:fact - [tags: onnx embeddings prefix instruction e5 query passage] E5 and Instructor family models require a text prefix on BOTH query and passage sides to produce meaningful similarities: query prefix `\"query: \"`, passage prefix `\"passage: \"`. Omitting the prefix can drop MRR by 10-15 percentage points on out-of-domain datasets. Check the model's README for the exact prefix string — it varies by model family. In kimetsu, the embedder abstraction has `query_prefix` and `passage_prefix` fields; FallbackEmbedder uses `\"\"` for both. jina-v2-base-code and bge-small use `\"\"` prefixes. (context: Kimetsu embedder trait design — prefix handling for E5/Instructor models.)" + }, + { + "ce": 0.00047590647591277957, + "key": "sqlite-prepared-stmt-cache", + "rank_score": 0.42366984486579895, + "text": "project:fact - [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8). The cache key is the SQL string verbatim, so template strings with interpolated values defeat caching — use `?1, ?2` placeholders instead. Calling `prepare_cached` in a tight loop is effectively free after warmup. (context: Kimetsu brain high-throughput ingest path — replacing prepare() with prepare_cached() cut ingest time by ~30%.)" + }, + { + "ce": 0.00023601560678798705, + "key": "aws-region-resolution", + "rank_score": 0.4028558135032654, + "text": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time. For cross-region inference (e.g. us-west-2 for Claude Opus), set `AWS_REGION=us-west-2`; do NOT rely on the Bedrock endpoint prefix being region-agnostic. (context: Kimetsu Bedrock provider region configuration.)" + }, + { + "ce": 0.0038052769377827644, + "key": "remote-ingest-split-roots", + "rank_score": 0.38540637493133545, + "text": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races. Re-enable kimetsu_brain_ingest_repo in the tool allowlist only when ingest is configured, and INTERCEPT that tools/call in the remote handler (clone+ingest_repo_at_root) before the normal dispatch (which would walk the wrong dir). Hermetic test: git init a temp repo, register url=local path, ingest, then context retrieves the file capsule via FTS (noop embedder). (context: R3c: server-side ingest for kimetsu-remote — cloning repos so file-capsule retrieval works without a local checkout.)" + } + ], + "delivered": [ + "windows-unc-paths" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7989637851715088 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "UTF-8 memory text prints as mojibake in the Windows console", + "relevant": [ + "windows-console-encoding" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999754428863525, + "key": "windows-console-encoding", + "rank_score": 0.9549965858459473, + "text": "project:fact - [tags: windows console encoding utf8 rust] Windows console code page defaults to the system ANSI code page (usually CP1252 or CP932), not UTF-8. Rust's `println!` writes UTF-8 bytes which display as mojibake in a non-UTF-8 console. Fix at process startup: call `SetConsoleOutputCP(65001)` via `winapi` or `windows-sys`, or set `PYTHONUTF8=1`/`RUST_LOG` before launch. In PowerShell, `[Console]::OutputEncoding = [System.Text.Encoding]::UTF8` fixes the session. For binary piped output (MCP stdio protocol), write raw bytes — don't use the console code page. (context: Kimetsu MCP server — Unicode memory text was garbled on non-UTF8 Windows terminals.)" + }, + { + "ce": 0.0016297773690894246, + "key": "kimetsu-eval-fixture-shape", + "rank_score": 0.4097127914428711, + "text": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` — a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases). Keys must be unique across the dataset. The bench currently does not validate keys at load — it fails later with an `unwrap()` on a missing HashMap entry. (context: Kimetsu bench dataset shape and validation.)" + }, + { + "ce": 0.00013235220103524625, + "key": "testing-property-tests", + "rank_score": 0.40359604358673096, + "text": "project:fact - [tags: testing property-based proptest quickcheck rust] Property-based tests (proptest, quickcheck) find edge cases that example-based tests miss. For kimetsu's memory text normalization, proptest found that zero-width joiner characters and right-to-left marks caused hash collisions. Run proptest with `PROPTEST_CASES=10000` in CI for thorough coverage. Shrinking: when proptest finds a failure, it automatically shrinks the input to the minimal failing case — read the `Minimized failure` output, not the original random input. Use `prop_assume!` to skip inputs that violate preconditions rather than `if/return`. (context: Kimetsu brain text normalization — property test for dedup hash stability.)" + }, + { + "ce": 0.00028954874142073095, + "key": "import-dedup-seen-ids", + "rank_score": 0.3968864977359772, + "text": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount — both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise. (context: Implementing brain export/import (Q5). First naive approach used a single `seen_ids` set local to the function; the dedup test caught it on the second-import assertion.)" + }, + { + "ce": 0.00009938928997144103, + "key": "mcp-stdout-protocol", + "rank_score": 0.41356831789016724, + "text": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr. `println!` in the request handler is forbidden. Use `eprintln!` or `tracing` with a stderr subscriber. In tests of the MCP server, capture stdout as bytes and validate it parses as JSON-Lines. When debugging, set `KIMETSU_LOG=debug` which writes to stderr only. (context: Kimetsu MCP server stdout protocol hygiene.)" + }, + { + "ce": 0.00015562758198939264, + "key": "git-line-endings-windows", + "rank_score": 0.37272363901138306, + "text": "project:fact - [tags: git line-endings windows crlf autocrlf] On Windows, `core.autocrlf=true` (git's default for Windows installs) converts LF to CRLF on checkout and CRLF to LF on commit. This causes spurious diffs when files are edited on Windows then committed — the content is identical but the line endings differ in the index vs the working tree. Fix: set `core.autocrlf=false` and `.gitattributes` with `* text=auto eol=lf` for the repo. For Rust projects, all source files should be LF; only Windows batch scripts need CRLF. Warn: AV scanners that modify newly written files can re-introduce CRLF in files Rust writes. (context: Kimetsu CI — spurious diffs from Windows CRLF conversion.)" + } + ], + "delivered": [ + "windows-console-encoding" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8412300944328308 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "process exit code is 4294967295 instead of -1 on Windows", + "relevant": [ + "windows-exit-codes" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999797344207764, + "key": "windows-exit-codes", + "rank_score": 0.9549965858459473, + "text": "project:fact - [tags: windows exit-codes rust process child] On Windows, process exit codes are 32-bit unsigned integers (DWORD). Rust's `ExitStatus::code()` returns `Option` — it's `None` if the process was killed by a signal (which Windows doesn't use; instead, TerminateProcess with a code). Conventional codes: 0=success, 1=generic error, 0xC0000005=access violation. Programs that call `std::process::exit(-1)` on Windows produce exit code 0xFFFFFFFF (4294967295), not -1. When checking for success in a subprocess chain, always check `status.success()` rather than `status.code() == Some(0)` to handle this portably. (context: Kimetsu update binary replacement — exit code handling.)" + }, + { + "ce": 0.002058164682239294, + "key": "windows-update-process-locking", + "rank_score": 0.48916563391685486, + "text": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics — mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code. (context: Q2 — kimetsu update preflight for locked binary on Windows)" + }, + { + "ce": 0.0010284382151439786, + "key": "harbor-terminal-bench-subprocess-isolation", + "rank_score": 0.4544038772583008, + "text": "project:fact - [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd). Worker re-derives auth internally from .env so the OAuth token never lands in argv; it writes {run,grade} JSON the parent reads back. One Harbor invocation per process always works (baseline-alone passed). (context: kbench multi-trial sweeps crashed on every trial after the 1st; diagnosed as Harbor/pyiceberg os.getcwd staleness on WSL2.)" + }, + { + "ce": 0.00006561150803463534, + "key": "init-project-git-boundary", + "rank_score": 0.4504166841506958, + "text": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain. (context: QQ3 — kimetsu setup integration test failed because init_project climbed git tree to real ~/.kimetsu instead of temp workspace)" + }, + { + "ce": 0.0005306341918185353, + "key": "windows-console-encoding", + "rank_score": 0.46752268075942993, + "text": "project:fact - [tags: windows console encoding utf8 rust] Windows console code page defaults to the system ANSI code page (usually CP1252 or CP932), not UTF-8. Rust's `println!` writes UTF-8 bytes which display as mojibake in a non-UTF-8 console. Fix at process startup: call `SetConsoleOutputCP(65001)` via `winapi` or `windows-sys`, or set `PYTHONUTF8=1`/`RUST_LOG` before launch. In PowerShell, `[Console]::OutputEncoding = [System.Text.Encoding]::UTF8` fixes the session. For binary piped output (MCP stdio protocol), write raw bytes — don't use the console code page. (context: Kimetsu MCP server — Unicode memory text was garbled on non-UTF8 Windows terminals.)" + }, + { + "ce": 0.00010509601997910067, + "key": "testing-fixture-drift", + "rank_score": 0.45544007420539856, + "text": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code. For kimetsu, `EvalFixture::from_memories(memories)` constructs a dataset from the exported format — use it in tests instead of hardcoded JSON. Tag fixture files with the schema version they were generated against in a comment. (context: Kimetsu eval fixture drift after schema migration.)" + } + ], + "delivered": [ + "windows-exit-codes" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7689235210418701 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "tokenizer.json must match the ONNX model — what breaks if it doesn't?", + "relevant": [ + "onnx-tokenizer-mismatch" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999275207519531, + "key": "onnx-tokenizer-mismatch", + "rank_score": 0.9549965262413025, + "text": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly — specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings — cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo. Validate by checking a reference embedding against the HuggingFace Python output. (context: Kimetsu custom ONNX reranker loading — wrong tokenizer produced degraded retrieval.)" + }, + { + "ce": 0.06017953157424927, + "key": "onnx-dim-mismatch", + "rank_score": 0.6059280633926392, + "text": "project:fact - [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results — the ANN index shape mismatch isn't always caught at runtime. kimetsu detects this by storing `embedder_id` in the brain schema and refusing to query if the configured embedder differs from what was used at ingest time. Mitigation: re-ingest all memories with the new model, or keep per-memory vector dim metadata. (context: Kimetsu embedder migration — detecting dimension mismatch at startup.)" + }, + { + "ce": 0.007195906713604927, + "key": "sqlite-fts5-tokenizer", + "rank_score": 0.5692639350891113, + "text": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon. If you switch tokenizers on an existing FTS5 table, you MUST rebuild the shadow tables: `INSERT INTO tbl(tbl) VALUES('rebuild');` — a schema-only change leaves the inverted index unusable. The `porter` stemmer is available as `tokenize='porter unicode61'` but aggressively strips suffixes and hurts precision on technical terms. (context: Kimetsu brain FTS5 index tuning for Rust identifier retrieval.)" + }, + { + "ce": 0.012975814752280712, + "key": "onnx-model-cache-paths", + "rank_score": 0.5084353089332581, + "text": "project:fact - [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use. In kimetsu, `KIMETSU_EMBEDDER_CACHE` overrides the path and is forwarded when spawning child bench processes — without forwarding it, each child re-downloads the model. (context: Kimetsu brain bench on CI — model cache path handling in child processes.)" + }, + { + "ce": 0.00036760896909981966, + "key": "sqlite-json1-extract", + "rank_score": 0.5136041641235352, + "text": "project:fact - [tags: sqlite json1 json_extract rusqlite] SQLite's json1 extension (built in since 3.38.0) lets you index and query JSONB columns with `json_extract(col, '$.field')`. To create a partial index over a JSON field: `CREATE INDEX idx ON memories (json_extract(metadata, '$.scope')) WHERE json_extract(metadata, '$.scope') IS NOT NULL;`. Use `json_each` for array fields. On older SQLite builds (rusqlite links whatever the system provides), check for json1 with `SELECT json('{}');` — an error means it's absent. Always prefer column storage over JSON blobs for frequently queried fields. (context: Kimetsu brain querying metadata scopes without migrating a separate column.)" + }, + { + "ce": 0.048101942986249924, + "key": "onnx-quantization-drift", + "rank_score": 0.5321104526519775, + "text": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals — cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case. (context: Kimetsu embedding model selection — evaluating jina-v2 int8 vs fp32.)" + } + ], + "delivered": [ + "onnx-tokenizer-mismatch" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8679417967796326 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "embedding quality degraded after I swapped in the INT8 quantized model", + "relevant": [ + "onnx-quantization-drift" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.03874051198363304, + "key": "onnx-tokenizer-mismatch", + "rank_score": 0.5000597834587097, + "text": "project:fact - [2026-09-05] [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly — specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings — cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo. Validate by checking a reference embedding against the HuggingFace Python output. (context: Kimetsu custom ONNX reranker loading — wrong tokenizer produced degraded retrieval.)" + }, + { + "ce": 0.9965908527374268, + "key": "onnx-quantization-drift", + "rank_score": 0.9549965262413025, + "text": "project:fact - [2026-09-05] [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals — cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case. (context: Kimetsu embedding model selection — evaluating jina-v2 int8 vs fp32.)" + }, + { + "ce": 0.03031030483543873, + "key": "onnx-batch-padding", + "rank_score": 0.4814388155937195, + "text": "project:fact - [2026-09-05] [tags: onnx batch padding attention-mask embeddings] When running batch inference with an ONNX model, all inputs in the batch must be padded to the same sequence length. The `attention_mask` tensor marks which tokens are real (1) and which are padding (0). Failing to pass `attention_mask` causes the model to average-pool over padding tokens, producing systematically lower-norm embeddings. With ORT (ort crate), construct the mask as a 2-D i64 tensor `[batch, seq_len]` with 1s for real tokens and 0s for padding. For variable-length batches, pad to `max(lengths)` in the batch, not to `model.max_length`. (context: Kimetsu embedding batch inference with ORT — missing attention mask caused MRR degradation.)" + }, + { + "ce": 0.012031257152557373, + "key": "onnx-dim-mismatch", + "rank_score": 0.40724092721939087, + "text": "project:fact - [2026-09-05] [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results — the ANN index shape mismatch isn't always caught at runtime. kimetsu detects this by storing `embedder_id` in the brain schema and refusing to query if the configured embedder differs from what was used at ingest time. Mitigation: re-ingest all memories with the new model, or keep per-memory vector dim metadata. (context: Kimetsu embedder migration — detecting dimension mismatch at startup.)" + }, + { + "ce": 0.00007597710646223277, + "key": "git-hooks-bypass", + "rank_score": 0.4008396863937378, + "text": "project:fact - [2026-09-05] [tags: git hooks bypass pre-commit skip] `git commit --no-verify` skips ALL hooks (pre-commit and commit-msg). Never use this in shared team repos where hooks enforce quality gates (lint, tests, memory harvest). Instead, fix the failing hook. If the hook itself is broken, fix the hook script. For emergency commits where hooks aren't relevant (e.g. updating a gitignore to untrack already-committed files), document the `--no-verify` use in the commit message. In CI, hooks run only if explicitly invoked — `git commit` in a CI pipeline with no hooks configured does nothing for quality enforcement. (context: Kimetsu pre-commit hook enforcing memory harvest.)" + }, + { + "ce": 0.008673558011651039, + "key": "kimetsu-bench-remote-embedder-singleton", + "rank_score": 0.42026063799858093, + "text": "project:fact - [2026-09-05] [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval. Workaround: run ONE `--embedders` value per invocation and kill the remote process between runs. The local bench path is not affected (each combo is process-isolated via `--single` child spawn). (context: Kimetsu brain bench --remote known issue — multi-embedder contamination.)" + } + ], + "delivered": [ + "onnx-quantization-drift" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.830954909324646 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "missing attention mask causes low-norm embeddings in batch inference", + "relevant": [ + "onnx-batch-padding" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9998724460601807, + "key": "onnx-batch-padding", + "rank_score": 0.9549964666366577, + "text": "project:fact - [tags: onnx batch padding attention-mask embeddings] When running batch inference with an ONNX model, all inputs in the batch must be padded to the same sequence length. The `attention_mask` tensor marks which tokens are real (1) and which are padding (0). Failing to pass `attention_mask` causes the model to average-pool over padding tokens, producing systematically lower-norm embeddings. With ORT (ort crate), construct the mask as a 2-D i64 tensor `[batch, seq_len]` with 1s for real tokens and 0s for padding. For variable-length batches, pad to `max(lengths)` in the batch, not to `model.max_length`. (context: Kimetsu embedding batch inference with ORT — missing attention mask caused MRR degradation.)" + }, + { + "ce": 0.0001131100652855821, + "key": "onnx-ort-threading", + "rank_score": 0.43641364574432373, + "text": "project:fact - [tags: onnx ort thread-pool parallelism cpu] ORT (ONNX Runtime) creates its own inter-op and intra-op thread pools. In a multi-process bench setup, each child inherits these pools and they compete for CPU cores. Set `SessionOptionsBuilder::with_intra_threads(1).with_inter_threads(1)` if you're running many parallel bench processes — this sacrifices per-inference throughput for lower contention. In a single-threaded embedding pipeline, 2-4 intra-op threads are better. For benchmarking, set `ORT_NUM_THREADS=1` via env var to get deterministic single-threaded latency numbers. (context: Kimetsu brain bench multi-process parallelism — ORT thread contention causing inconsistent latency.)" + }, + { + "ce": 0.0004706119652837515, + "key": "kimetsu-proactive-hooks", + "rank_score": 0.40696051716804504, + "text": "project:fact - [tags: kimetsu proactive hooks context injection] kimetsu's proactive context injection runs before each agent turn (pre-turn hook) and injects relevant memories into the system prompt prefix. The hook invocation adds latency to the first token: embedding inference + vector search + reranking + context formatting. On a cold start, this can be 1-3 seconds. The hook is optional — disable with `KIMETSU_PROACTIVE=0`. The semantic floor (min cosine similarity) filters noise capsules before injection; setting the floor too low injects irrelevant memories and wastes context window tokens. The proactive hook does NOT trigger the distiller — that runs post-session only. (context: Kimetsu proactive context injection — latency and floor tuning.)" + }, + { + "ce": 0.0000757862871978432, + "key": "testing-property-tests", + "rank_score": 0.41743382811546326, + "text": "project:fact - [tags: testing property-based proptest quickcheck rust] Property-based tests (proptest, quickcheck) find edge cases that example-based tests miss. For kimetsu's memory text normalization, proptest found that zero-width joiner characters and right-to-left marks caused hash collisions. Run proptest with `PROPTEST_CASES=10000` in CI for thorough coverage. Shrinking: when proptest finds a failure, it automatically shrinks the input to the minimal failing case — read the `Minimized failure` output, not the original random input. Use `prop_assume!` to skip inputs that violate preconditions rather than `if/return`. (context: Kimetsu brain text normalization — property test for dedup hash stability.)" + }, + { + "ce": 0.0000859733481775038, + "key": "ci-secrets-masking", + "rank_score": 0.3987695872783661, + "text": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output — but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable. Never reconstruct secrets from parts in step output. For kimetsu bench `--remote` CI runs, `KIMETSU_REMOTE_TOKEN` must be in the repository secrets, not in the workflow YAML. Use `${{ secrets.KIMETSU_REMOTE_TOKEN }}` in env — never `echo ${{ secrets.KIMETSU_REMOTE_TOKEN }}` in a run step. (context: Kimetsu CI remote benchmark — token handling.)" + }, + { + "ce": 0.000468315789476037, + "key": "onnx-cosine-vs-dot", + "rank_score": 0.3963926434516907, + "text": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing — double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g. E5, GTE with separate query/passage prefixes), the query and document encoders must use different prefix strings. Check the model card's `Similarity function` field. usearch/qdrant: prefer `MetricKind::Cos` over `Dot` for passage vectors that may not be perfectly normalized. (context: Kimetsu embedding storage — similarity metric selection.)" + } + ], + "delivered": [ + "onnx-batch-padding" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8474288582801819 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "ONNX model download fails in a Docker container with no home directory", + "relevant": [ + "onnx-model-cache-paths" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9924855828285217, + "key": "onnx-model-cache-paths", + "rank_score": 0.9549964666366577, + "text": "project:fact - [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use. In kimetsu, `KIMETSU_EMBEDDER_CACHE` overrides the path and is forwarded when spawning child bench processes — without forwarding it, each child re-downloads the model. (context: Kimetsu brain bench on CI — model cache path handling in child processes.)" + }, + { + "ce": 0.03111337125301361, + "key": "onnx-batch-padding", + "rank_score": 0.49302253127098083, + "text": "project:fact - [tags: onnx batch padding attention-mask embeddings] When running batch inference with an ONNX model, all inputs in the batch must be padded to the same sequence length. The `attention_mask` tensor marks which tokens are real (1) and which are padding (0). Failing to pass `attention_mask` causes the model to average-pool over padding tokens, producing systematically lower-norm embeddings. With ORT (ort crate), construct the mask as a 2-D i64 tensor `[batch, seq_len]` with 1s for real tokens and 0s for padding. For variable-length batches, pad to `max(lengths)` in the batch, not to `model.max_length`. (context: Kimetsu embedding batch inference with ORT — missing attention mask caused MRR degradation.)" + }, + { + "ce": 0.0038487804122269154, + "key": "onnx-quantization-drift", + "rank_score": 0.4716246426105499, + "text": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals — cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case. (context: Kimetsu embedding model selection — evaluating jina-v2 int8 vs fp32.)" + }, + { + "ce": 0.06564066559076309, + "key": "onnx-tokenizer-mismatch", + "rank_score": 0.4863210618495941, + "text": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly — specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings — cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo. Validate by checking a reference embedding against the HuggingFace Python output. (context: Kimetsu custom ONNX reranker loading — wrong tokenizer produced degraded retrieval.)" + }, + { + "ce": 0.00011335872841300443, + "key": "testing-temp-dirs-ci", + "rank_score": 0.4267616868019104, + "text": "project:fact - [tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure. On Windows, `env::temp_dir()` returns `C:\\Users\\\\AppData\\Local\\Temp` — ensure the test binary has write permissions there. Avoid using the workspace root as a temp dir — tests should never write to the source tree. (context: Kimetsu test infrastructure — temp directory discipline.)" + }, + { + "ce": 0.02002401277422905, + "key": "onnx-dim-mismatch", + "rank_score": 0.43406763672828674, + "text": "project:fact - [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results — the ANN index shape mismatch isn't always caught at runtime. kimetsu detects this by storing `embedder_id` in the brain schema and refusing to query if the configured embedder differs from what was used at ingest time. Mitigation: re-ingest all memories with the new model, or keep per-memory vector dim metadata. (context: Kimetsu embedder migration — detecting dimension mismatch at startup.)" + } + ], + "delivered": [ + "onnx-model-cache-paths" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.6824284791946411 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "fastembed cache path environment variable for CI", + "relevant": [ + "onnx-model-cache-paths" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999436140060425, + "key": "onnx-model-cache-paths", + "rank_score": 0.9549964666366577, + "text": "project:fact - [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use. In kimetsu, `KIMETSU_EMBEDDER_CACHE` overrides the path and is forwarded when spawning child bench processes — without forwarding it, each child re-downloads the model. (context: Kimetsu brain bench on CI — model cache path handling in child processes.)" + }, + { + "ce": 0.005708366632461548, + "key": "http-proxy-env", + "rank_score": 0.5097562074661255, + "text": "project:fact - [tags: http proxy environment reqwest rust corporate] reqwest respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` environment variables by default (with `default-tls` or `rustls-tls`). In a corporate network, these may redirect traffic through an intercepting proxy that breaks mTLS or adds latency. To disable proxy usage entirely: `reqwest::ClientBuilder::no_proxy()`. On Windows, reqwest does NOT use the system proxy settings (IE/WinInet) — you must set env vars explicitly. `NO_PROXY=127.0.0.1,localhost` prevents proxying loopback traffic (important for kimetsu-remote local dev). (context: Kimetsu provider calls failing behind corporate proxy on Windows.)" + }, + { + "ce": 0.002754602814093232, + "key": "ci-cache-keys", + "rank_score": 0.4990924000740051, + "text": "project:fact - [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key — macOS and Windows have incompatible artifact formats. Separate the registry cache from the build cache: the registry (downloaded crates) changes rarely, the build cache changes every push. Bust the build cache on major dependency changes by adding a manual cache version suffix to the key. (context: Kimetsu CI — cache invalidation strategy.)" + }, + { + "ce": 0.002740468829870224, + "key": "sqlite-prepared-stmt-cache", + "rank_score": 0.4879706799983978, + "text": "project:fact - [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8). The cache key is the SQL string verbatim, so template strings with interpolated values defeat caching — use `?1, ?2` placeholders instead. Calling `prepare_cached` in a tight loop is effectively free after warmup. (context: Kimetsu brain high-throughput ingest path — replacing prepare() with prepare_cached() cut ingest time by ~30%.)" + }, + { + "ce": 0.0005798769416287541, + "key": "mcp-tool-timeouts", + "rank_score": 0.4435410499572754, + "text": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking — in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize — keep it in a process-global `OnceLock`). The reranker adds another 200-800ms; jina-tiny is the fastest. If tool calls are still slow, log the per-stage latency with `tracing::info!` at DEBUG level and profile under load. (context: Kimetsu MCP tool latency optimization.)" + }, + { + "ce": 0.009829169139266014, + "key": "ci-secrets-masking", + "rank_score": 0.4322015941143036, + "text": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output — but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable. Never reconstruct secrets from parts in step output. For kimetsu bench `--remote` CI runs, `KIMETSU_REMOTE_TOKEN` must be in the repository secrets, not in the workflow YAML. Use `${{ secrets.KIMETSU_REMOTE_TOKEN }}` in env — never `echo ${{ secrets.KIMETSU_REMOTE_TOKEN }}` in a run step. (context: Kimetsu CI remote benchmark — token handling.)" + } + ], + "delivered": [ + "onnx-model-cache-paths" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7834174633026123 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "cosine similarity vs dot product for L2-normalized embedding vectors", + "relevant": [ + "onnx-cosine-vs-dot" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999769926071167, + "key": "onnx-cosine-vs-dot", + "rank_score": 0.9549964070320129, + "text": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing — double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g. E5, GTE with separate query/passage prefixes), the query and document encoders must use different prefix strings. Check the model card's `Similarity function` field. usearch/qdrant: prefer `MetricKind::Cos` over `Dot` for passage vectors that may not be perfectly normalized. (context: Kimetsu embedding storage — similarity metric selection.)" + }, + { + "ce": 0.0020234286785125732, + "key": "kimetsu-proactive-hooks", + "rank_score": 0.45353275537490845, + "text": "project:fact - [tags: kimetsu proactive hooks context injection] kimetsu's proactive context injection runs before each agent turn (pre-turn hook) and injects relevant memories into the system prompt prefix. The hook invocation adds latency to the first token: embedding inference + vector search + reranking + context formatting. On a cold start, this can be 1-3 seconds. The hook is optional — disable with `KIMETSU_PROACTIVE=0`. The semantic floor (min cosine similarity) filters noise capsules before injection; setting the floor too low injects irrelevant memories and wastes context window tokens. The proactive hook does NOT trigger the distiller — that runs post-session only. (context: Kimetsu proactive context injection — latency and floor tuning.)" + }, + { + "ce": 0.05521874874830246, + "key": "onnx-tokenizer-mismatch", + "rank_score": 0.4740632176399231, + "text": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly — specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings — cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo. Validate by checking a reference embedding against the HuggingFace Python output. (context: Kimetsu custom ONNX reranker loading — wrong tokenizer produced degraded retrieval.)" + }, + { + "ce": 0.052236758172512054, + "key": "onnx-quantization-drift", + "rank_score": 0.47355207800865173, + "text": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals — cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case. (context: Kimetsu embedding model selection — evaluating jina-v2 int8 vs fp32.)" + }, + { + "ce": 0.000048714628064772114, + "key": "kimetsu-rerank-pool", + "rank_score": 0.3953549563884735, + "text": "project:fact - [tags: kimetsu reranker pool size ann retrieval] kimetsu's retrieval pipeline: ANN (approximate nearest neighbor) retrieves a pool of candidates, then the reranker reorders them, then the top-K are returned. The pool size (default 6 for production, 12 in bench) controls the recall-latency tradeoff: larger pool = higher recall = more reranker calls = more latency. For the jina-tiny reranker, pool 12 adds ~80ms vs pool 6. The bench uses pool 12 to maximize measurable recall differences between rerankers; production uses pool 6 for latency. Increasing pool size beyond 20 has diminishing recall returns on corpora < 1000 memories. (context: Kimetsu ANN pool size tuning for the retrieval benchmark.)" + }, + { + "ce": 0.00006958431913517416, + "key": "tokio-channel-backpressure", + "rank_score": 0.375570148229599, + "text": "project:fact - [tags: tokio mpsc channel backpressure async rust] `tokio::sync::mpsc::channel(N)` with a bounded buffer provides backpressure: senders block when the buffer is full. This prevents unbounded memory growth but can cause sender tasks to stall. Choosing N: too small causes frequent backpressure (throughput drops); too large defeats the purpose. For kimetsu's harvest pipeline, N=16 was a good balance — the harvester is I/O bound (LLM call), producers are fast (hook callbacks). Prefer bounded channels over unbounded in production code. `tokio::sync::mpsc::unbounded_channel()` is a footgun for bursty producers. (context: Kimetsu auto-harvester pipeline — bounded vs unbounded channel selection.)" + } + ], + "delivered": [ + "onnx-cosine-vs-dot" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8600253462791443 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "stored vectors have wrong dimension after switching embedding models", + "relevant": [ + "onnx-dim-mismatch" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.014875928871333599, + "key": "sqlite-page-size", + "rank_score": 0.5568302869796753, + "text": "project:fact - [2026-09-05] [tags: sqlite page_size performance rusqlite] SQLite's default page_size is 4096 bytes. For a write-heavy brain database with large BLOB payloads (embedding vectors), raising page_size to 16384 reduces fragmentation and improves sequential scan throughput. `PRAGMA page_size = 16384;` must be set BEFORE the first table is created — changing it on an existing database requires a VACUUM afterward to rebuild all pages. Verify it took effect with `PRAGMA page_size;` after VACUUM. rusqlite's `Connection::open` runs no implicit PRAGMA, so set this in the connection init path. (context: Tuning the kimetsu brain SQLite schema for embedding vector storage.)" + }, + { + "ce": 0.4106057286262512, + "key": "onnx-tokenizer-mismatch", + "rank_score": 0.6139137744903564, + "text": "project:fact - [2026-09-05] [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly — specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings — cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo. Validate by checking a reference embedding against the HuggingFace Python output. (context: Kimetsu custom ONNX reranker loading — wrong tokenizer produced degraded retrieval.)" + }, + { + "ce": 0.8149011731147766, + "key": "onnx-cosine-vs-dot", + "rank_score": 0.706167995929718, + "text": "project:fact - [2026-09-05] [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing — double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g. E5, GTE with separate query/passage prefixes), the query and document encoders must use different prefix strings. Check the model card's `Similarity function` field. usearch/qdrant: prefer `MetricKind::Cos` over `Dot` for passage vectors that may not be perfectly normalized. (context: Kimetsu embedding storage — similarity metric selection.)" + }, + { + "ce": 0.9999579191207886, + "key": "onnx-dim-mismatch", + "rank_score": 0.9549964070320129, + "text": "project:fact - [2026-09-05] [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results — the ANN index shape mismatch isn't always caught at runtime. kimetsu detects this by storing `embedder_id` in the brain schema and refusing to query if the configured embedder differs from what was used at ingest time. Mitigation: re-ingest all memories with the new model, or keep per-memory vector dim metadata. (context: Kimetsu embedder migration — detecting dimension mismatch at startup.)" + }, + { + "ce": 0.011478126049041748, + "key": "mcp-tool-timeouts", + "rank_score": 0.5438621640205383, + "text": "project:fact - [2026-09-05] [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking — in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize — keep it in a process-global `OnceLock`). The reranker adds another 200-800ms; jina-tiny is the fastest. If tool calls are still slow, log the per-stage latency with `tracing::info!` at DEBUG level and profile under load. (context: Kimetsu MCP tool latency optimization.)" + }, + { + "ce": 0.00007517827907577157, + "key": "kimetsu-memory-scopes", + "rank_score": 0.4401836693286896, + "text": "project:fact - [2026-09-05] [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available — if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope. The `kimetsu_brain_record` MCP tool inherits the scope from the server's launch context. When running kimetsu-remote, all memories are project-scoped to the registered repo-id. (context: Kimetsu memory scope system — project vs user isolation.)" + } + ], + "delivered": [ + "onnx-dim-mismatch", + "onnx-cosine-vs-dot", + "onnx-tokenizer-mismatch" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8295753598213196 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "E5 and Instructor models need a query prefix — what happens without it?", + "relevant": [ + "onnx-prefix-instructions" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999685287475586, + "key": "onnx-prefix-instructions", + "rank_score": 0.9549964070320129, + "text": "project:fact - [tags: onnx embeddings prefix instruction e5 query passage] E5 and Instructor family models require a text prefix on BOTH query and passage sides to produce meaningful similarities: query prefix `\"query: \"`, passage prefix `\"passage: \"`. Omitting the prefix can drop MRR by 10-15 percentage points on out-of-domain datasets. Check the model's README for the exact prefix string — it varies by model family. In kimetsu, the embedder abstraction has `query_prefix` and `passage_prefix` fields; FallbackEmbedder uses `\"\"` for both. jina-v2-base-code and bge-small use `\"\"` prefixes. (context: Kimetsu embedder trait design — prefix handling for E5/Instructor models.)" + }, + { + "ce": 0.5567834973335266, + "key": "onnx-cosine-vs-dot", + "rank_score": 0.6614930629730225, + "text": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing — double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g. E5, GTE with separate query/passage prefixes), the query and document encoders must use different prefix strings. Check the model card's `Similarity function` field. usearch/qdrant: prefer `MetricKind::Cos` over `Dot` for passage vectors that may not be perfectly normalized. (context: Kimetsu embedding storage — similarity metric selection.)" + }, + { + "ce": 0.0030232712160795927, + "key": "onnx-dim-mismatch", + "rank_score": 0.5354487299919128, + "text": "project:fact - [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results — the ANN index shape mismatch isn't always caught at runtime. kimetsu detects this by storing `embedder_id` in the brain schema and refusing to query if the configured embedder differs from what was used at ingest time. Mitigation: re-ingest all memories with the new model, or keep per-memory vector dim metadata. (context: Kimetsu embedder migration — detecting dimension mismatch at startup.)" + }, + { + "ce": 0.0008878273656591773, + "key": "sqlite-json1-extract", + "rank_score": 0.5126402974128723, + "text": "project:fact - [tags: sqlite json1 json_extract rusqlite] SQLite's json1 extension (built in since 3.38.0) lets you index and query JSONB columns with `json_extract(col, '$.field')`. To create a partial index over a JSON field: `CREATE INDEX idx ON memories (json_extract(metadata, '$.scope')) WHERE json_extract(metadata, '$.scope') IS NOT NULL;`. Use `json_each` for array fields. On older SQLite builds (rusqlite links whatever the system provides), check for json1 with `SELECT json('{}');` — an error means it's absent. Always prefer column storage over JSON blobs for frequently queried fields. (context: Kimetsu brain querying metadata scopes without migrating a separate column.)" + }, + { + "ce": 0.0005813135649077594, + "key": "onnx-model-cache-paths", + "rank_score": 0.4381890892982483, + "text": "project:fact - [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use. In kimetsu, `KIMETSU_EMBEDDER_CACHE` overrides the path and is forwarded when spawning child bench processes — without forwarding it, each child re-downloads the model. (context: Kimetsu brain bench on CI — model cache path handling in child processes.)" + }, + { + "ce": 0.0002984431921504438, + "key": "bedrock-kimetsu-provider", + "rank_score": 0.40922820568084717, + "text": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env. Wire \"bedrock\" into BOTH pipeline.rs provider matches AND the distiller (normalize_distiller_provider + instantiation); the distiller is configured independently so agent-on-Bedrock + harvester-on-direct-Claude works for free. Sign and send the SAME payload bytes; test signing determinism with a fixed SystemTime. (context: Workstream A: adding AWS Bedrock as a provider for the agent + auto-harvester in v1.0.0.)" + } + ], + "delivered": [ + "onnx-prefix-instructions", + "onnx-cosine-vs-dot" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8438615798950195 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "ORT thread pool contention when running multiple bench processes in parallel", + "relevant": [ + "onnx-ort-threading" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.11494239419698715, + "key": "onnx-batch-padding", + "rank_score": 0.47426462173461914, + "text": "project:fact - [2026-09-05] [tags: onnx batch padding attention-mask embeddings] When running batch inference with an ONNX model, all inputs in the batch must be padded to the same sequence length. The `attention_mask` tensor marks which tokens are real (1) and which are padding (0). Failing to pass `attention_mask` causes the model to average-pool over padding tokens, producing systematically lower-norm embeddings. With ORT (ort crate), construct the mask as a 2-D i64 tensor `[batch, seq_len]` with 1s for real tokens and 0s for padding. For variable-length batches, pad to `max(lengths)` in the batch, not to `model.max_length`. (context: Kimetsu embedding batch inference with ORT — missing attention mask caused MRR degradation.)" + }, + { + "ce": 0.9999724626541138, + "key": "onnx-ort-threading", + "rank_score": 0.9549964070320129, + "text": "project:fact - [2026-09-05] [tags: onnx ort thread-pool parallelism cpu] ORT (ONNX Runtime) creates its own inter-op and intra-op thread pools. In a multi-process bench setup, each child inherits these pools and they compete for CPU cores. Set `SessionOptionsBuilder::with_intra_threads(1).with_inter_threads(1)` if you're running many parallel bench processes — this sacrifices per-inference throughput for lower contention. In a single-threaded embedding pipeline, 2-4 intra-op threads are better. For benchmarking, set `ORT_NUM_THREADS=1` via env var to get deterministic single-threaded latency numbers. (context: Kimetsu brain bench multi-process parallelism — ORT thread contention causing inconsistent latency.)" + }, + { + "ce": 0.001988927833735943, + "key": "tokio-spawn-blocking", + "rank_score": 0.45640143752098083, + "text": "project:fact - [2026-09-05] [tags: tokio spawn_blocking thread-pool rust blocking] `tokio::task::spawn_blocking` places work on a dedicated blocking thread pool (default up to 512 threads, configurable via `Builder::max_blocking_threads`). Each call creates or reuses a thread — there's no true pooling, threads may be created on demand. For many short-duration blocking calls (e.g. per-query SQLite reads), thread creation overhead may dominate. Prefer batching: collect N queries, then one `spawn_blocking` to run them all. Alternatively, keep a persistent blocking task that reads from an mpsc channel. Profile with `tokio-console` if you suspect spawn_blocking overhead. (context: Kimetsu retrieval server — per-query spawn_blocking was adding ~0.3ms overhead.)" + }, + { + "ce": 0.0027053607627749443, + "key": "testing-serial-vs-parallel", + "rank_score": 0.4570074677467346, + "text": "project:fact - [2026-09-05] [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`). `cargo nextest` runs each test in a separate process by default, avoiding the problem entirely at the cost of longer startup time. For kimetsu, prefer nextest in CI and accept that `test_env_lock` exists only for `cargo test` compatibility. (context: Kimetsu test suite — env-var mutation in parallel tests.)" + }, + { + "ce": 0.0003327711019665003, + "key": "kimetsu-rerank-pool", + "rank_score": 0.42838284373283386, + "text": "project:fact - [2026-09-05] [tags: kimetsu reranker pool size ann retrieval] kimetsu's retrieval pipeline: ANN (approximate nearest neighbor) retrieves a pool of candidates, then the reranker reorders them, then the top-K are returned. The pool size (default 6 for production, 12 in bench) controls the recall-latency tradeoff: larger pool = higher recall = more reranker calls = more latency. For the jina-tiny reranker, pool 12 adds ~80ms vs pool 6. The bench uses pool 12 to maximize measurable recall differences between rerankers; production uses pool 6 for latency. Increasing pool size beyond 20 has diminishing recall returns on corpora < 1000 memories. (context: Kimetsu ANN pool size tuning for the retrieval benchmark.)" + }, + { + "ce": 0.015802541747689247, + "key": "kimetsu-bench-remote-embedder-singleton", + "rank_score": 0.43823331594467163, + "text": "project:fact - [2026-09-05] [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval. Workaround: run ONE `--embedders` value per invocation and kill the remote process between runs. The local bench path is not affected (each combo is process-isolated via `--single` child spawn). (context: Kimetsu brain bench --remote known issue — multi-embedder contamination.)" + } + ], + "delivered": [ + "onnx-ort-threading" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7965230345726013 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "git worktrees share the .kimetsu brain — how do I isolate test runs?", + "relevant": [ + "git-worktree-brain-isolation" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999784231185913, + "key": "git-worktree-brain-isolation", + "rank_score": 0.9549964070320129, + "text": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root — if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain. Always set `KIMETSU_BRAIN_DIR` or use `git_init_boundary` in tests to prevent this. (context: Kimetsu development with git worktrees — test isolation.)" + }, + { + "ce": 0.13282185792922974, + "key": "cargo-dev-dep-leak", + "rank_score": 0.636254608631134, + "text": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates. Run `cargo tree --features ` to trace which crate activated an unexpected feature. (context: Kimetsu testing infra — a dev-dep was activating the embeddings feature in non-test builds.)" + }, + { + "ce": 0.1763731837272644, + "key": "testing-serial-vs-parallel", + "rank_score": 0.5905643701553345, + "text": "project:fact - [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`). `cargo nextest` runs each test in a separate process by default, avoiding the problem entirely at the cost of longer startup time. For kimetsu, prefer nextest in CI and accept that `test_env_lock` exists only for `cargo test` compatibility. (context: Kimetsu test suite — env-var mutation in parallel tests.)" + }, + { + "ce": 0.3770565688610077, + "key": "kimetsu-bench-remote-embedder-singleton", + "rank_score": 0.5759706497192383, + "text": "project:fact - [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval. Workaround: run ONE `--embedders` value per invocation and kill the remote process between runs. The local bench path is not affected (each combo is process-isolated via `--single` child spawn). (context: Kimetsu brain bench --remote known issue — multi-embedder contamination.)" + }, + { + "ce": 0.05430165305733681, + "key": "git-hooks-bypass", + "rank_score": 0.5578304529190063, + "text": "project:fact - [tags: git hooks bypass pre-commit skip] `git commit --no-verify` skips ALL hooks (pre-commit and commit-msg). Never use this in shared team repos where hooks enforce quality gates (lint, tests, memory harvest). Instead, fix the failing hook. If the hook itself is broken, fix the hook script. For emergency commits where hooks aren't relevant (e.g. updating a gitignore to untrack already-committed files), document the `--no-verify` use in the commit message. In CI, hooks run only if explicitly invoked — `git commit` in a CI pipeline with no hooks configured does nothing for quality enforcement. (context: Kimetsu pre-commit hook enforcing memory harvest.)" + }, + { + "ce": 0.9857950210571289, + "key": "init-project-git-boundary", + "rank_score": 0.5244470834732056, + "text": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain. (context: QQ3 — kimetsu setup integration test failed because init_project climbed git tree to real ~/.kimetsu instead of temp workspace)" + } + ], + "delivered": [ + "git-worktree-brain-isolation", + "init-project-git-boundary", + "kimetsu-bench-remote-embedder-singleton" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8883571624755859 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "when is it safe to use --no-verify on git commit?", + "relevant": [ + "git-hooks-bypass" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9970122575759888, + "key": "git-hooks-bypass", + "rank_score": 0.9549963474273682, + "text": "project:fact - [2026-09-05] [tags: git hooks bypass pre-commit skip] `git commit --no-verify` skips ALL hooks (pre-commit and commit-msg). Never use this in shared team repos where hooks enforce quality gates (lint, tests, memory harvest). Instead, fix the failing hook. If the hook itself is broken, fix the hook script. For emergency commits where hooks aren't relevant (e.g. updating a gitignore to untrack already-committed files), document the `--no-verify` use in the commit message. In CI, hooks run only if explicitly invoked — `git commit` in a CI pipeline with no hooks configured does nothing for quality enforcement. (context: Kimetsu pre-commit hook enforcing memory harvest.)" + }, + { + "ce": 0.2692404091358185, + "key": "git-sparse-checkout", + "rank_score": 0.6114630699157715, + "text": "project:fact - [2026-09-05] [tags: git sparse-checkout partial-clone bandwidth] `git sparse-checkout init --cone` combined with `git clone --filter=blob:none` (partial clone) fetches only the commit graph and tree objects, not blobs. Individual blobs are fetched on demand when accessed. This cuts clone time for large repos from minutes to seconds. For kimetsu server-side ingest, use `git clone --depth 1 --filter=blob:none` for the initial checkout, then `git sparse-checkout set ` to limit the working tree to indexed directories. On `git fetch --depth 1 origin main` for refresh, blobs in the sparse set are updated lazily. (context: Kimetsu remote ingest — reducing bandwidth and disk usage for large repo checkouts.)" + }, + { + "ce": 0.004689557012170553, + "key": "git-line-endings-windows", + "rank_score": 0.634850025177002, + "text": "project:fact - [2026-09-05] [tags: git line-endings windows crlf autocrlf] On Windows, `core.autocrlf=true` (git's default for Windows installs) converts LF to CRLF on checkout and CRLF to LF on commit. This causes spurious diffs when files are edited on Windows then committed — the content is identical but the line endings differ in the index vs the working tree. Fix: set `core.autocrlf=false` and `.gitattributes` with `* text=auto eol=lf` for the repo. For Rust projects, all source files should be LF; only Windows batch scripts need CRLF. Warn: AV scanners that modify newly written files can re-introduce CRLF in files Rust writes. (context: Kimetsu CI — spurious diffs from Windows CRLF conversion.)" + }, + { + "ce": 0.03193778917193413, + "key": "git-submodule-pinning", + "rank_score": 0.6742921471595764, + "text": "project:fact - [2026-09-05] [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip — this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version. If a submodule is the kimetsu-bench repo inside the main repo, pin the bench SHA after validating the dataset change. Use `git diff HEAD -- bench` to see the pinned SHA change before committing. (context: Kimetsu bench as a git submodule of the main repo.)" + }, + { + "ce": 0.09809619933366776, + "key": "git-reflog-rescue", + "rank_score": 0.6453607678413391, + "text": "project:fact - [2026-09-05] [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone — they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only — remote reflog is not accessible via normal git commands. If you need the remote version, use `git fetch origin +refs/heads/main:refs/heads/main-backup` before a force push. In kimetsu bench development, always create a branch before destructive rebases. (context: Kimetsu bench dataset recovery after accidental hard reset.)" + }, + { + "ce": 0.006926193367689848, + "key": "tokio-select-cancellation", + "rank_score": 0.7761988043785095, + "text": "project:fact - [2026-09-05] [tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded. For correctness, cancelled futures must be cancellation-safe: holding no partially committed state. `tokio::sync::watch::Receiver::changed()` is cancellation-safe; `tokio::sync::mpsc::Sender::send()` is NOT (the item is lost). In kimetsu shutdown, use a `CancellationToken` and `select!` branches that are all cancellation-safe. (context: Kimetsu remote graceful shutdown — race between incoming requests and shutdown signal.)" + } + ], + "delivered": [ + "git-hooks-bypass" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8291763663291931 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "reduce clone size and bandwidth for server-side repo ingest", + "relevant": [ + "git-sparse-checkout" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9952669143676758, + "key": "git-sparse-checkout", + "rank_score": 0.9549963474273682, + "text": "project:fact - [tags: git sparse-checkout partial-clone bandwidth] `git sparse-checkout init --cone` combined with `git clone --filter=blob:none` (partial clone) fetches only the commit graph and tree objects, not blobs. Individual blobs are fetched on demand when accessed. This cuts clone time for large repos from minutes to seconds. For kimetsu server-side ingest, use `git clone --depth 1 --filter=blob:none` for the initial checkout, then `git sparse-checkout set ` to limit the working tree to indexed directories. On `git fetch --depth 1 origin main` for refresh, blobs in the sparse set are updated lazily. (context: Kimetsu remote ingest — reducing bandwidth and disk usage for large repo checkouts.)" + }, + { + "ce": 0.5591859817504883, + "key": "remote-ingest-split-roots", + "rank_score": 0.8337775468826294, + "text": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races. Re-enable kimetsu_brain_ingest_repo in the tool allowlist only when ingest is configured, and INTERCEPT that tools/call in the remote handler (clone+ingest_repo_at_root) before the normal dispatch (which would walk the wrong dir). Hermetic test: git init a temp repo, register url=local path, ingest, then context retrieves the file capsule via FTS (noop embedder). (context: R3c: server-side ingest for kimetsu-remote — cloning repos so file-capsule retrieval works without a local checkout.)" + }, + { + "ce": 0.00009631966531742364, + "key": "sqlite-page-size", + "rank_score": 0.5354727506637573, + "text": "project:fact - [tags: sqlite page_size performance rusqlite] SQLite's default page_size is 4096 bytes. For a write-heavy brain database with large BLOB payloads (embedding vectors), raising page_size to 16384 reduces fragmentation and improves sequential scan throughput. `PRAGMA page_size = 16384;` must be set BEFORE the first table is created — changing it on an existing database requires a VACUUM afterward to rebuild all pages. Verify it took effect with `PRAGMA page_size;` after VACUUM. rusqlite's `Connection::open` runs no implicit PRAGMA, so set this in the connection init path. (context: Tuning the kimetsu brain SQLite schema for embedding vector storage.)" + }, + { + "ce": 0.0001226338790729642, + "key": "sqlite-prepared-stmt-cache", + "rank_score": 0.5096704959869385, + "text": "project:fact - [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8). The cache key is the SQL string verbatim, so template strings with interpolated values defeat caching — use `?1, ?2` placeholders instead. Calling `prepare_cached` in a tight loop is effectively free after warmup. (context: Kimetsu brain high-throughput ingest path — replacing prepare() with prepare_cached() cut ingest time by ~30%.)" + }, + { + "ce": 0.00018906750483438373, + "key": "cargo-profile-override", + "rank_score": 0.4661652445793152, + "text": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug. The downside: rebuild time increases for that crate. For overflow-checks, `overflow-checks = false` per package speeds up hot loops. Never disable overflow-checks in release for business-critical data-mutating code. `[profile.release] strip = \"debuginfo\"` reduces binary size with minimal impact on stack traces. (context: Kimetsu dev experience — embedding inference was 10x slower in debug builds.)" + }, + { + "ce": 0.00006529355596285313, + "key": "kimetsu-rerank-pool", + "rank_score": 0.42525434494018555, + "text": "project:fact - [tags: kimetsu reranker pool size ann retrieval] kimetsu's retrieval pipeline: ANN (approximate nearest neighbor) retrieves a pool of candidates, then the reranker reorders them, then the top-K are returned. The pool size (default 6 for production, 12 in bench) controls the recall-latency tradeoff: larger pool = higher recall = more reranker calls = more latency. For the jina-tiny reranker, pool 12 adds ~80ms vs pool 6. The bench uses pool 12 to maximize measurable recall differences between rerankers; production uses pool 6 for latency. Increasing pool size beyond 20 has diminishing recall returns on corpora < 1000 memories. (context: Kimetsu ANN pool size tuning for the retrieval benchmark.)" + } + ], + "delivered": [ + "git-sparse-checkout", + "remote-ingest-split-roots" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7191969156265259 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "spurious diffs from Windows CRLF line ending conversion in git", + "relevant": [ + "git-line-endings-windows" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999781847000122, + "key": "git-line-endings-windows", + "rank_score": 0.9549962878227234, + "text": "project:fact - [tags: git line-endings windows crlf autocrlf] On Windows, `core.autocrlf=true` (git's default for Windows installs) converts LF to CRLF on checkout and CRLF to LF on commit. This causes spurious diffs when files are edited on Windows then committed — the content is identical but the line endings differ in the index vs the working tree. Fix: set `core.autocrlf=false` and `.gitattributes` with `* text=auto eol=lf` for the repo. For Rust projects, all source files should be LF; only Windows batch scripts need CRLF. Warn: AV scanners that modify newly written files can re-introduce CRLF in files Rust writes. (context: Kimetsu CI — spurious diffs from Windows CRLF conversion.)" + }, + { + "ce": 0.0001784852793207392, + "key": "ci-cache-keys", + "rank_score": 0.3828304708003998, + "text": "project:fact - [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key — macOS and Windows have incompatible artifact formats. Separate the registry cache from the build cache: the registry (downloaded crates) changes rarely, the build cache changes every push. Bust the build cache on major dependency changes by adding a manual cache version suffix to the key. (context: Kimetsu CI — cache invalidation strategy.)" + }, + { + "ce": 0.0008221939206123352, + "key": "git-submodule-pinning", + "rank_score": 0.36505812406539917, + "text": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip — this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version. If a submodule is the kimetsu-bench repo inside the main repo, pin the bench SHA after validating the dataset change. Use `git diff HEAD -- bench` to see the pinned SHA change before committing. (context: Kimetsu bench as a git submodule of the main repo.)" + }, + { + "ce": 0.00006791057239752263, + "key": "mcp-stdout-protocol", + "rank_score": 0.3684476315975189, + "text": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr. `println!` in the request handler is forbidden. Use `eprintln!` or `tracing` with a stderr subscriber. In tests of the MCP server, capture stdout as bytes and validate it parses as JSON-Lines. When debugging, set `KIMETSU_LOG=debug` which writes to stderr only. (context: Kimetsu MCP server stdout protocol hygiene.)" + }, + { + "ce": 0.0001497534103691578, + "key": "windows-file-locking-av", + "rank_score": 0.37205296754837036, + "text": "project:fact - [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine. For CI, use GitHub-hosted Windows runners which don't have real-time AV. Alternatively, build to a different directory with `CARGO_TARGET_DIR=C:\\tmp\\target`. The error is non-deterministic — it only appears when AV scanning races with the link step. (context: Kimetsu development on Windows — intermittent linker errors.)" + }, + { + "ce": 0.0007507860427722335, + "key": "remote-ingest-split-roots", + "rank_score": 0.3428329825401306, + "text": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races. Re-enable kimetsu_brain_ingest_repo in the tool allowlist only when ingest is configured, and INTERCEPT that tools/call in the remote handler (clone+ingest_repo_at_root) before the normal dispatch (which would walk the wrong dir). Hermetic test: git init a temp repo, register url=local path, ingest, then context retrieves the file capsule via FTS (noop embedder). (context: R3c: server-side ingest for kimetsu-remote — cloning repos so file-capsule retrieval works without a local checkout.)" + } + ], + "delivered": [ + "git-line-endings-windows" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8731517195701599 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "git submodule always gets the wrong commit in CI", + "relevant": [ + "git-submodule-pinning" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9990686774253845, + "key": "git-submodule-pinning", + "rank_score": 0.9549962878227234, + "text": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip — this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version. If a submodule is the kimetsu-bench repo inside the main repo, pin the bench SHA after validating the dataset change. Use `git diff HEAD -- bench` to see the pinned SHA change before committing. (context: Kimetsu bench as a git submodule of the main repo.)" + }, + { + "ce": 0.7485930919647217, + "key": "git-hooks-bypass", + "rank_score": 0.6315711140632629, + "text": "project:fact - [tags: git hooks bypass pre-commit skip] `git commit --no-verify` skips ALL hooks (pre-commit and commit-msg). Never use this in shared team repos where hooks enforce quality gates (lint, tests, memory harvest). Instead, fix the failing hook. If the hook itself is broken, fix the hook script. For emergency commits where hooks aren't relevant (e.g. updating a gitignore to untrack already-committed files), document the `--no-verify` use in the commit message. In CI, hooks run only if explicitly invoked — `git commit` in a CI pipeline with no hooks configured does nothing for quality enforcement. (context: Kimetsu pre-commit hook enforcing memory harvest.)" + }, + { + "ce": 0.1327298879623413, + "key": "git-line-endings-windows", + "rank_score": 0.5519919991493225, + "text": "project:fact - [tags: git line-endings windows crlf autocrlf] On Windows, `core.autocrlf=true` (git's default for Windows installs) converts LF to CRLF on checkout and CRLF to LF on commit. This causes spurious diffs when files are edited on Windows then committed — the content is identical but the line endings differ in the index vs the working tree. Fix: set `core.autocrlf=false` and `.gitattributes` with `* text=auto eol=lf` for the repo. For Rust projects, all source files should be LF; only Windows batch scripts need CRLF. Warn: AV scanners that modify newly written files can re-introduce CRLF in files Rust writes. (context: Kimetsu CI — spurious diffs from Windows CRLF conversion.)" + }, + { + "ce": 0.18188920617103577, + "key": "cargo-lockfile-drift", + "rank_score": 0.563281774520874, + "text": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this — it errors on any lockfile diff. For library crates, `Cargo.lock` is normally gitignored, but for workspace roots with binary crates it should be committed. Use `cargo update --precise ` to pin a specific dep version without touching unrelated entries. (context: Kimetsu workspace lockfile drift after adding kimetsu-remote crate.)" + }, + { + "ce": 0.03387662023305893, + "key": "git-reflog-rescue", + "rank_score": 0.575106680393219, + "text": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone — they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only — remote reflog is not accessible via normal git commands. If you need the remote version, use `git fetch origin +refs/heads/main:refs/heads/main-backup` before a force push. In kimetsu bench development, always create a branch before destructive rebases. (context: Kimetsu bench dataset recovery after accidental hard reset.)" + }, + { + "ce": 0.0011289744870737195, + "key": "onnx-tokenizer-mismatch", + "rank_score": 0.4950568675994873, + "text": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly — specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings — cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo. Validate by checking a reference embedding against the HuggingFace Python output. (context: Kimetsu custom ONNX reranker loading — wrong tokenizer produced degraded retrieval.)" + } + ], + "delivered": [ + "git-submodule-pinning", + "git-hooks-bypass" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7742003202438354 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "accidentally ran git reset --hard and lost commits — can I recover?", + "relevant": [ + "git-reflog-rescue" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999775886535645, + "key": "git-reflog-rescue", + "rank_score": 0.9549962878227234, + "text": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone — they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only — remote reflog is not accessible via normal git commands. If you need the remote version, use `git fetch origin +refs/heads/main:refs/heads/main-backup` before a force push. In kimetsu bench development, always create a branch before destructive rebases. (context: Kimetsu bench dataset recovery after accidental hard reset.)" + }, + { + "ce": 0.00043070322135463357, + "key": "tokio-select-cancellation", + "rank_score": 0.4463525414466858, + "text": "project:fact - [tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded. For correctness, cancelled futures must be cancellation-safe: holding no partially committed state. `tokio::sync::watch::Receiver::changed()` is cancellation-safe; `tokio::sync::mpsc::Sender::send()` is NOT (the item is lost). In kimetsu shutdown, use a `CancellationToken` and `select!` branches that are all cancellation-safe. (context: Kimetsu remote graceful shutdown — race between incoming requests and shutdown signal.)" + }, + { + "ce": 0.05161457508802414, + "key": "git-hooks-bypass", + "rank_score": 0.42260628938674927, + "text": "project:fact - [tags: git hooks bypass pre-commit skip] `git commit --no-verify` skips ALL hooks (pre-commit and commit-msg). Never use this in shared team repos where hooks enforce quality gates (lint, tests, memory harvest). Instead, fix the failing hook. If the hook itself is broken, fix the hook script. For emergency commits where hooks aren't relevant (e.g. updating a gitignore to untrack already-committed files), document the `--no-verify` use in the commit message. In CI, hooks run only if explicitly invoked — `git commit` in a CI pipeline with no hooks configured does nothing for quality enforcement. (context: Kimetsu pre-commit hook enforcing memory harvest.)" + }, + { + "ce": 0.00444014510139823, + "key": "remote-ingest-split-roots", + "rank_score": 0.4070316255092621, + "text": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races. Re-enable kimetsu_brain_ingest_repo in the tool allowlist only when ingest is configured, and INTERCEPT that tools/call in the remote handler (clone+ingest_repo_at_root) before the normal dispatch (which would walk the wrong dir). Hermetic test: git init a temp repo, register url=local path, ingest, then context retrieves the file capsule via FTS (noop embedder). (context: R3c: server-side ingest for kimetsu-remote — cloning repos so file-capsule retrieval works without a local checkout.)" + }, + { + "ce": 0.0005809612339362502, + "key": "git-line-endings-windows", + "rank_score": 0.4161628484725952, + "text": "project:fact - [tags: git line-endings windows crlf autocrlf] On Windows, `core.autocrlf=true` (git's default for Windows installs) converts LF to CRLF on checkout and CRLF to LF on commit. This causes spurious diffs when files are edited on Windows then committed — the content is identical but the line endings differ in the index vs the working tree. Fix: set `core.autocrlf=false` and `.gitattributes` with `* text=auto eol=lf` for the repo. For Rust projects, all source files should be LF; only Windows batch scripts need CRLF. Warn: AV scanners that modify newly written files can re-introduce CRLF in files Rust writes. (context: Kimetsu CI — spurious diffs from Windows CRLF conversion.)" + }, + { + "ce": 0.0015261481748893857, + "key": "testing-snapshot-churn", + "rank_score": 0.3709065914154053, + "text": "project:fact - [tags: testing snapshot insta assert churn rust] Snapshot tests (e.g. with the `insta` crate) fail whenever the output changes, even for intended changes. In CI, they fail loudly; locally, `cargo insta review` walks you through accepting or rejecting changes. Snapshot churn becomes a problem when output includes timestamps, process IDs, or randomly-ordered maps. Redact these before snapshotting: use `insta::with_settings!({redactions: [\".timestamp\" => \"[TIMESTAMP]\"]})`. For JSON output, sort maps and arrays before comparing. Keep snapshot files in `src/snapshots/` and always commit them — an untracked snapshot file causes the next CI run to fail with a different error than expected. (context: Kimetsu CLI output snapshot tests — reducing churn.)" + } + ], + "delivered": [ + "git-reflog-rescue" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8037190437316895 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "blocking SQLite call from an async tokio handler causes latency spikes", + "relevant": [ + "tokio-blocking-in-async" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999537467956543, + "key": "tokio-blocking-in-async", + "rank_score": 0.9549962282180786, + "text": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking — never call rusqlite directly from an async fn without spawn_blocking. fastembed inference is also blocking (ONNX Runtime is synchronous). The threshold: any operation taking more than 100 microseconds that can't be made async belongs in spawn_blocking. Ignoring this causes tail-latency spikes and request timeouts under load in kimetsu-remote. (context: Kimetsu remote server — SQLite and embedding calls from async handlers.)" + }, + { + "ce": 0.7837615609169006, + "key": "tokio-runtime-in-tests", + "rank_score": 0.6227405667304993, + "text": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests. For sync test code that calls async, use `tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async { ... })`. Never call `block_on` from inside an async function. (context: Kimetsu remote integration tests — nested runtime panic.)" + }, + { + "ce": 0.4022727310657501, + "key": "tokio-channel-backpressure", + "rank_score": 0.5620198845863342, + "text": "project:fact - [tags: tokio mpsc channel backpressure async rust] `tokio::sync::mpsc::channel(N)` with a bounded buffer provides backpressure: senders block when the buffer is full. This prevents unbounded memory growth but can cause sender tasks to stall. Choosing N: too small causes frequent backpressure (throughput drops); too large defeats the purpose. For kimetsu's harvest pipeline, N=16 was a good balance — the harvester is I/O bound (LLM call), producers are fast (hook callbacks). Prefer bounded channels over unbounded in production code. `tokio::sync::mpsc::unbounded_channel()` is a footgun for bursty producers. (context: Kimetsu auto-harvester pipeline — bounded vs unbounded channel selection.)" + }, + { + "ce": 0.41142529249191284, + "key": "tokio-spawn-blocking", + "rank_score": 0.5574167966842651, + "text": "project:fact - [tags: tokio spawn_blocking thread-pool rust blocking] `tokio::task::spawn_blocking` places work on a dedicated blocking thread pool (default up to 512 threads, configurable via `Builder::max_blocking_threads`). Each call creates or reuses a thread — there's no true pooling, threads may be created on demand. For many short-duration blocking calls (e.g. per-query SQLite reads), thread creation overhead may dominate. Prefer batching: collect N queries, then one `spawn_blocking` to run them all. Alternatively, keep a persistent blocking task that reads from an mpsc channel. Profile with `tokio-console` if you suspect spawn_blocking overhead. (context: Kimetsu retrieval server — per-query spawn_blocking was adding ~0.3ms overhead.)" + }, + { + "ce": 0.06374192237854004, + "key": "tokio-shutdown-ordering", + "rank_score": 0.4671923518180847, + "text": "project:fact - [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries — the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks. `axum::Server::with_graceful_shutdown` handles steps 1-2; you must handle 3-5 manually. (context: Kimetsu remote server graceful shutdown implementation.)" + }, + { + "ce": 0.0011688831727951765, + "key": "sqlite-busy-timeout-wal", + "rank_score": 0.43175339698791504, + "text": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch. Set the timeout before any transaction, not inside one — it is a connection-level property. (context: Kimetsu brain writer and reader processes sharing the same SQLite brain database.)" + } + ], + "delivered": [ + "tokio-blocking-in-async", + "tokio-runtime-in-tests", + "tokio-spawn-blocking", + "tokio-channel-backpressure" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8205004930496216 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "Cannot start a runtime from within a runtime in a tokio test", + "relevant": [ + "tokio-runtime-in-tests" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999808073043823, + "key": "tokio-runtime-in-tests", + "rank_score": 0.9549962282180786, + "text": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests. For sync test code that calls async, use `tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async { ... })`. Never call `block_on` from inside an async function. (context: Kimetsu remote integration tests — nested runtime panic.)" + }, + { + "ce": 0.7143720388412476, + "key": "tokio-blocking-in-async", + "rank_score": 0.5659168362617493, + "text": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking — never call rusqlite directly from an async fn without spawn_blocking. fastembed inference is also blocking (ONNX Runtime is synchronous). The threshold: any operation taking more than 100 microseconds that can't be made async belongs in spawn_blocking. Ignoring this causes tail-latency spikes and request timeouts under load in kimetsu-remote. (context: Kimetsu remote server — SQLite and embedding calls from async handlers.)" + }, + { + "ce": 0.1875762641429901, + "key": "bedrock-kimetsu-provider", + "rank_score": 0.5019359588623047, + "text": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env. Wire \"bedrock\" into BOTH pipeline.rs provider matches AND the distiller (normalize_distiller_provider + instantiation); the distiller is configured independently so agent-on-Bedrock + harvester-on-direct-Claude works for free. Sign and send the SAME payload bytes; test signing determinism with a fixed SystemTime. (context: Workstream A: adding AWS Bedrock as a provider for the agent + auto-harvester in v1.0.0.)" + }, + { + "ce": 0.004649110604077578, + "key": "onnx-dim-mismatch", + "rank_score": 0.46548354625701904, + "text": "project:fact - [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results — the ANN index shape mismatch isn't always caught at runtime. kimetsu detects this by storing `embedder_id` in the brain schema and refusing to query if the configured embedder differs from what was used at ingest time. Mitigation: re-ingest all memories with the new model, or keep per-memory vector dim metadata. (context: Kimetsu embedder migration — detecting dimension mismatch at startup.)" + }, + { + "ce": 0.0013898607576265931, + "key": "cfg-cross-platform-dead-code", + "rank_score": 0.4802683889865875, + "text": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform. (context: Adding parse_unix_ps to kimetsu-cli/src/process.rs — used only on Unix at runtime but needed on Windows for cross-platform unit tests.)" + }, + { + "ce": 0.017026560381054878, + "key": "cargo-dev-dep-leak", + "rank_score": 0.4511256814002991, + "text": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates. Run `cargo tree --features ` to trace which crate activated an unexpected feature. (context: Kimetsu testing infra — a dev-dep was activating the embeddings feature in non-test builds.)" + } + ], + "delivered": [ + "tokio-runtime-in-tests", + "tokio-blocking-in-async" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8270149230957031 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "tokio select cancels the other branch and loses the value in the channel", + "relevant": [ + "tokio-select-cancellation" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9996563196182251, + "key": "tokio-select-cancellation", + "rank_score": 0.9549961686134338, + "text": "project:fact - [tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded. For correctness, cancelled futures must be cancellation-safe: holding no partially committed state. `tokio::sync::watch::Receiver::changed()` is cancellation-safe; `tokio::sync::mpsc::Sender::send()` is NOT (the item is lost). In kimetsu shutdown, use a `CancellationToken` and `select!` branches that are all cancellation-safe. (context: Kimetsu remote graceful shutdown — race between incoming requests and shutdown signal.)" + }, + { + "ce": 0.035441868007183075, + "key": "tokio-channel-backpressure", + "rank_score": 0.4579736292362213, + "text": "project:fact - [tags: tokio mpsc channel backpressure async rust] `tokio::sync::mpsc::channel(N)` with a bounded buffer provides backpressure: senders block when the buffer is full. This prevents unbounded memory growth but can cause sender tasks to stall. Choosing N: too small causes frequent backpressure (throughput drops); too large defeats the purpose. For kimetsu's harvest pipeline, N=16 was a good balance — the harvester is I/O bound (LLM call), producers are fast (hook callbacks). Prefer bounded channels over unbounded in production code. `tokio::sync::mpsc::unbounded_channel()` is a footgun for bursty producers. (context: Kimetsu auto-harvester pipeline — bounded vs unbounded channel selection.)" + }, + { + "ce": 0.00014749039837624878, + "key": "toml-value-parse", + "rank_score": 0.433961421251297, + "text": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table. (context: Implementing config get/set with toml::Value navigation; str.parse() failed with 'unexpected content' error on document strings.)" + }, + { + "ce": 0.0005864715785719454, + "key": "git-submodule-pinning", + "rank_score": 0.3899242579936981, + "text": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip — this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version. If a submodule is the kimetsu-bench repo inside the main repo, pin the bench SHA after validating the dataset change. Use `git diff HEAD -- bench` to see the pinned SHA change before committing. (context: Kimetsu bench as a git submodule of the main repo.)" + }, + { + "ce": 0.0036569421645253897, + "key": "tokio-runtime-in-tests", + "rank_score": 0.39582133293151855, + "text": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests. For sync test code that calls async, use `tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async { ... })`. Never call `block_on` from inside an async function. (context: Kimetsu remote integration tests — nested runtime panic.)" + }, + { + "ce": 0.002218227367848158, + "key": "kimetsu-bench-remote-embedder-singleton", + "rank_score": 0.3858184218406677, + "text": "project:fact - [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval. Workaround: run ONE `--embedders` value per invocation and kill the remote process between runs. The local bench path is not affected (each combo is process-isolated via `--single` child spawn). (context: Kimetsu brain bench --remote known issue — multi-embedder contamination.)" + } + ], + "delivered": [ + "tokio-select-cancellation" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7838292121887207 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "mpsc channel backpressure causing senders to stall", + "relevant": [ + "tokio-channel-backpressure" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.999940037727356, + "key": "tokio-channel-backpressure", + "rank_score": 0.9549961686134338, + "text": "project:fact - [tags: tokio mpsc channel backpressure async rust] `tokio::sync::mpsc::channel(N)` with a bounded buffer provides backpressure: senders block when the buffer is full. This prevents unbounded memory growth but can cause sender tasks to stall. Choosing N: too small causes frequent backpressure (throughput drops); too large defeats the purpose. For kimetsu's harvest pipeline, N=16 was a good balance — the harvester is I/O bound (LLM call), producers are fast (hook callbacks). Prefer bounded channels over unbounded in production code. `tokio::sync::mpsc::unbounded_channel()` is a footgun for bursty producers. (context: Kimetsu auto-harvester pipeline — bounded vs unbounded channel selection.)" + }, + { + "ce": 0.013390065170824528, + "key": "tokio-select-cancellation", + "rank_score": 0.5047038793563843, + "text": "project:fact - [tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded. For correctness, cancelled futures must be cancellation-safe: holding no partially committed state. `tokio::sync::watch::Receiver::changed()` is cancellation-safe; `tokio::sync::mpsc::Sender::send()` is NOT (the item is lost). In kimetsu shutdown, use a `CancellationToken` and `select!` branches that are all cancellation-safe. (context: Kimetsu remote graceful shutdown — race between incoming requests and shutdown signal.)" + }, + { + "ce": 0.0011631015222519636, + "key": "tokio-spawn-blocking", + "rank_score": 0.41935619711875916, + "text": "project:fact - [tags: tokio spawn_blocking thread-pool rust blocking] `tokio::task::spawn_blocking` places work on a dedicated blocking thread pool (default up to 512 threads, configurable via `Builder::max_blocking_threads`). Each call creates or reuses a thread — there's no true pooling, threads may be created on demand. For many short-duration blocking calls (e.g. per-query SQLite reads), thread creation overhead may dominate. Prefer batching: collect N queries, then one `spawn_blocking` to run them all. Alternatively, keep a persistent blocking task that reads from an mpsc channel. Profile with `tokio-console` if you suspect spawn_blocking overhead. (context: Kimetsu retrieval server — per-query spawn_blocking was adding ~0.3ms overhead.)" + }, + { + "ce": 0.00017354745068587363, + "key": "testing-time-dependent-flakes", + "rank_score": 0.34216099977493286, + "text": "project:fact - [tags: testing time flaky clock mock rust] Tests that depend on wall-clock time are inherently flaky under load (slow CI runners, GC pauses). Abstract time behind a trait (`Clock: Fn() -> SystemTime`) injected at construction, and supply a fake in tests. For tests checking that something happened \"within N seconds\", use a generous multiple of the expected duration (10x is not unreasonable for CI). `std::thread::sleep` in tests is a smell — prefer channel synchronization or a condvar instead of timing-based waits. If you must use sleep, set `KIMETSU_TEST_TIMEOUT_SCALE` to stretch timeouts in slow environments. (context: Kimetsu GC and TTL tests — time-dependent flakes on loaded CI.)" + }, + { + "ce": 0.000051715989684453234, + "key": "cargo-target-dir-sharing", + "rank_score": 0.3246302902698517, + "text": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps — use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination. (context: Kimetsu development on Windows with Windows Defender causing intermittent link failures.)" + }, + { + "ce": 0.00011685269419103861, + "key": "tokio-shutdown-ordering", + "rank_score": 0.3431645631790161, + "text": "project:fact - [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries — the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks. `axum::Server::with_graceful_shutdown` handles steps 1-2; you must handle 3-5 manually. (context: Kimetsu remote server graceful shutdown implementation.)" + } + ], + "delivered": [ + "tokio-channel-backpressure" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7780143022537231 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "overhead from calling spawn_blocking on every single query request", + "relevant": [ + "tokio-spawn-blocking" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9911772012710571, + "key": "tokio-spawn-blocking", + "rank_score": 0.9549961686134338, + "text": "project:fact - [tags: tokio spawn_blocking thread-pool rust blocking] `tokio::task::spawn_blocking` places work on a dedicated blocking thread pool (default up to 512 threads, configurable via `Builder::max_blocking_threads`). Each call creates or reuses a thread — there's no true pooling, threads may be created on demand. For many short-duration blocking calls (e.g. per-query SQLite reads), thread creation overhead may dominate. Prefer batching: collect N queries, then one `spawn_blocking` to run them all. Alternatively, keep a persistent blocking task that reads from an mpsc channel. Profile with `tokio-console` if you suspect spawn_blocking overhead. (context: Kimetsu retrieval server — per-query spawn_blocking was adding ~0.3ms overhead.)" + }, + { + "ce": 0.06648600846529007, + "key": "tokio-blocking-in-async", + "rank_score": 0.6744999885559082, + "text": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking — never call rusqlite directly from an async fn without spawn_blocking. fastembed inference is also blocking (ONNX Runtime is synchronous). The threshold: any operation taking more than 100 microseconds that can't be made async belongs in spawn_blocking. Ignoring this causes tail-latency spikes and request timeouts under load in kimetsu-remote. (context: Kimetsu remote server — SQLite and embedding calls from async handlers.)" + }, + { + "ce": 0.0025417348369956017, + "key": "cargo-build-script-rerun", + "rank_score": 0.5858240127563477, + "text": "project:fact - [tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory. If the build script generates code from a schema file, emit `rerun-if-changed=schema.json`. If there are NO inputs (e.g. the script only inspects env vars), emit `cargo:rerun-if-changed=` with an empty string to suppress re-runs entirely. Missing this directive is the most common cause of unexpectedly slow incremental builds. (context: kimetsu-cli build.rs for embedding version stamps.)" + }, + { + "ce": 0.0006409944617189467, + "key": "import-dedup-seen-ids", + "rank_score": 0.5089847445487976, + "text": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount — both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise. (context: Implementing brain export/import (Q5). First naive approach used a single `seen_ids` set local to the function; the dedup test caught it on the second-import assertion.)" + }, + { + "ce": 0.00035242331796325743, + "key": "aws-presigned-urls", + "rank_score": 0.4704638719558716, + "text": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time — clock skew > 15 minutes causes `RequestTimeTooSkewed`. kimetsu could use presigned URLs to serve brain exports from S3 without exposing credentials to the client. (context: Kimetsu potential S3 export feature — presigned URL generation.)" + }, + { + "ce": 0.0005273821298032999, + "key": "sqlite-prepared-stmt-cache", + "rank_score": 0.4590907692909241, + "text": "project:fact - [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8). The cache key is the SQL string verbatim, so template strings with interpolated values defeat caching — use `?1, ?2` placeholders instead. Calling `prepare_cached` in a tight loop is effectively free after warmup. (context: Kimetsu brain high-throughput ingest path — replacing prepare() with prepare_cached() cut ingest time by ~30%.)" + } + ], + "delivered": [ + "tokio-spawn-blocking" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8051969408988953 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "axum server panics during shutdown because the DB pool is already closed", + "relevant": [ + "tokio-shutdown-ordering" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9920267462730408, + "key": "tokio-shutdown-ordering", + "rank_score": 0.9549961090087891, + "text": "project:fact - [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries — the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks. `axum::Server::with_graceful_shutdown` handles steps 1-2; you must handle 3-5 manually. (context: Kimetsu remote server graceful shutdown implementation.)" + }, + { + "ce": 0.00011431570601416752, + "key": "mutex-deadlock-user-brain-disabled", + "rank_score": 0.5681219100952148, + "text": "project:fact - [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure — `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation. (context: New tests for Tier-1 perf work called test_env_lock().lock() inside with_user_brain_disabled closure, deadlocking all project::tests that ran after them in the same test binary.)" + }, + { + "ce": 0.0019658042583614588, + "key": "http-streaming-bodies", + "rank_score": 0.5236757397651672, + "text": "project:fact - [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding — a chunk may split across frame boundaries. In kimetsu's proxy path, accumulate bytes until `\n\n` (SSE frame delimiter) before parsing the JSON data field. Never assume one `.chunk()` call = one SSE event. (context: Kimetsu remote proxy — streaming LLM responses to the client.)" + }, + { + "ce": 0.000575309619307518, + "key": "pi-openclaw-extension-api", + "rank_score": 0.4444963335990906, + "text": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`. External commands use `pi.exec()` but `node:child_process` spawn also works. Pi has NO MCP so Kimetsu integrates via TS extension + SKILL.md only. (context: Implementing Pi host target for Kimetsu plugin install/status/uninstall system.)\n\nAlso: [tags: kimetsu host-integration pi openclaw bridge] When integrating Kimetsu with an external host agent (Pi, OpenClaw, etc.), VERIFY the host's real plugin/extension API against its actual repo before writing embedded assets — docs-from-memory are frequently wrong. Concretely corrected during v1.0: Pi uses a default-export factory `export default function(pi)` (not `defineExtension`) with lifecycle events `session_start`/`agent_end`/`session_shutdown`; OpenClaw plugin entry is `index.ts` via `definePluginEntry` from `openclaw/plugin-sdk/plugin-entry` + an `openclaw.plugin.json` manifest, with snake_case hook events `agent_turn_prepare`/`agent_end`/`session_end` (NOT colon-delimited). Always make the embedded hook shell-out a silent no-op if the `kimetsu` binary isn't on PATH so a wrong guess never breaks the host. (context: Adding Pi + OpenClaw as BridgeTarget hosts in v1.0.0; the inferred extension/plugin APIs from docs were wrong and had to be corrected against the real repos.)" + }, + { + "ce": 0.000058730442106025293, + "key": "mcp-stdout-protocol", + "rank_score": 0.45591944456100464, + "text": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr. `println!` in the request handler is forbidden. Use `eprintln!` or `tracing` with a stderr subscriber. In tests of the MCP server, capture stdout as bytes and validate it parses as JSON-Lines. When debugging, set `KIMETSU_LOG=debug` which writes to stderr only. (context: Kimetsu MCP server stdout protocol hygiene.)" + }, + { + "ce": 0.00006397819379344583, + "key": "kimetsu-rerank-pool", + "rank_score": 0.4332950711250305, + "text": "project:fact - [tags: kimetsu reranker pool size ann retrieval] kimetsu's retrieval pipeline: ANN (approximate nearest neighbor) retrieves a pool of candidates, then the reranker reorders them, then the top-K are returned. The pool size (default 6 for production, 12 in bench) controls the recall-latency tradeoff: larger pool = higher recall = more reranker calls = more latency. For the jina-tiny reranker, pool 12 adds ~80ms vs pool 6. The bench uses pool 12 to maximize measurable recall differences between rerankers; production uses pool 6 for latency. Increasing pool size beyond 20 has diminishing recall returns on corpora < 1000 memories. (context: Kimetsu ANN pool size tuning for the retrieval benchmark.)" + } + ], + "delivered": [ + "tokio-shutdown-ordering" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7232332229614258 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "reqwest Client created per-request defeats connection pooling", + "relevant": [ + "http-connection-pooling" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999794960021973, + "key": "http-connection-pooling", + "rank_score": 0.9549961090087891, + "text": "project:fact - [tags: http reqwest connection-pool keep-alive rust] reqwest's `Client` holds a connection pool; always create ONE `Client` instance and clone it for each handler — cloning is cheap (Arc under the hood). Creating a `Client::new()` per request defeats connection pooling and causes TCP connection exhaustion under load. The default pool settings: max_idle_per_host=usize::MAX (unbounded), idle_timeout=90s. For a kimetsu outbound client (LLM provider), set `pool_max_idle_per_host(5)` to limit idle connections. On Windows, the underlying hyper+winapi stack may not reuse connections as aggressively as on Linux — set `connection_verbose(true)` on the builder to confirm reuse. (context: Kimetsu provider HTTP client — connection pooling best practices.)" + }, + { + "ce": 0.009632428176701069, + "key": "tokio-shutdown-ordering", + "rank_score": 0.5253746509552002, + "text": "project:fact - [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries — the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks. `axum::Server::with_graceful_shutdown` handles steps 1-2; you must handle 3-5 manually. (context: Kimetsu remote server graceful shutdown implementation.)" + }, + { + "ce": 0.006184292025864124, + "key": "sqlite-prepared-stmt-cache", + "rank_score": 0.5008304715156555, + "text": "project:fact - [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8). The cache key is the SQL string verbatim, so template strings with interpolated values defeat caching — use `?1, ?2` placeholders instead. Calling `prepare_cached` in a tight loop is effectively free after warmup. (context: Kimetsu brain high-throughput ingest path — replacing prepare() with prepare_cached() cut ingest time by ~30%.)" + }, + { + "ce": 0.0032946106512099504, + "key": "tokio-spawn-blocking", + "rank_score": 0.49133405089378357, + "text": "project:fact - [tags: tokio spawn_blocking thread-pool rust blocking] `tokio::task::spawn_blocking` places work on a dedicated blocking thread pool (default up to 512 threads, configurable via `Builder::max_blocking_threads`). Each call creates or reuses a thread — there's no true pooling, threads may be created on demand. For many short-duration blocking calls (e.g. per-query SQLite reads), thread creation overhead may dominate. Prefer batching: collect N queries, then one `spawn_blocking` to run them all. Alternatively, keep a persistent blocking task that reads from an mpsc channel. Profile with `tokio-console` if you suspect spawn_blocking overhead. (context: Kimetsu retrieval server — per-query spawn_blocking was adding ~0.3ms overhead.)" + }, + { + "ce": 0.0471840538084507, + "key": "http-tls-roots", + "rank_score": 0.48304563760757446, + "text": "project:fact - [tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle — the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle. Alternatively, add the custom root with `add_root_certificate`. On Linux, the system CA bundle is at `/etc/ssl/certs/ca-certificates.crt`; on Windows it's in the Windows Certificate Store. (context: Kimetsu on a corporate Windows machine with a custom proxy CA.)" + }, + { + "ce": 0.0024913609959185123, + "key": "onnx-ort-threading", + "rank_score": 0.45409464836120605, + "text": "project:fact - [tags: onnx ort thread-pool parallelism cpu] ORT (ONNX Runtime) creates its own inter-op and intra-op thread pools. In a multi-process bench setup, each child inherits these pools and they compete for CPU cores. Set `SessionOptionsBuilder::with_intra_threads(1).with_inter_threads(1)` if you're running many parallel bench processes — this sacrifices per-inference throughput for lower contention. In a single-threaded embedding pipeline, 2-4 intra-op threads are better. For benchmarking, set `ORT_NUM_THREADS=1` via env var to get deterministic single-threaded latency numbers. (context: Kimetsu brain bench multi-process parallelism — ORT thread contention causing inconsistent latency.)" + } + ], + "delivered": [ + "http-connection-pooling" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.844781219959259 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "LLM request times out during streaming — which timeout setting applies?", + "relevant": [ + "http-timeout-layering" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9990266561508179, + "key": "http-timeout-layering", + "rank_score": 0.9549960494041443, + "text": "project:fact - [tags: http reqwest timeout connect read total rust] reqwest has three distinct timeout knobs: `connect_timeout`, `read_timeout`, and `timeout` (total). They compose: if all three are set, the request fails at whichever fires first. For LLM API calls with streaming responses, `read_timeout` must be larger than the slowest expected token (often 30-60s) while `connect_timeout` can be tight (3-5s). `timeout` should be your SLA ceiling. If you set only `timeout`, a slow connect eats into the overall budget. For kimetsu-remote, set both `connect_timeout(5s)` and `timeout(120s)` — the LLM call is the bottleneck. (context: Kimetsu provider timeouts — request timing out during streaming.)" + }, + { + "ce": 0.09219683706760406, + "key": "http-connection-pooling", + "rank_score": 0.6204019784927368, + "text": "project:fact - [tags: http reqwest connection-pool keep-alive rust] reqwest's `Client` holds a connection pool; always create ONE `Client` instance and clone it for each handler — cloning is cheap (Arc under the hood). Creating a `Client::new()` per request defeats connection pooling and causes TCP connection exhaustion under load. The default pool settings: max_idle_per_host=usize::MAX (unbounded), idle_timeout=90s. For a kimetsu outbound client (LLM provider), set `pool_max_idle_per_host(5)` to limit idle connections. On Windows, the underlying hyper+winapi stack may not reuse connections as aggressively as on Linux — set `connection_verbose(true)` on the builder to confirm reuse. (context: Kimetsu provider HTTP client — connection pooling best practices.)" + }, + { + "ce": 0.0055482774041593075, + "key": "aws-sigv4-bedrock-blocking", + "rank_score": 0.551798939704895, + "text": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed. aws-smithy-runtime-api required as a companion to supply Identity. (context: Implementing BedrockProvider for Kimetsu with blocking reqwest + SigV4 signing, no tokio/aws-sdk)" + }, + { + "ce": 0.36695200204849243, + "key": "http-streaming-bodies", + "rank_score": 0.551405668258667, + "text": "project:fact - [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding — a chunk may split across frame boundaries. In kimetsu's proxy path, accumulate bytes until `\n\n` (SSE frame delimiter) before parsing the JSON data field. Never assume one `.chunk()` call = one SSE event. (context: Kimetsu remote proxy — streaming LLM responses to the client.)" + }, + { + "ce": 0.023786012083292007, + "key": "aws-retry-throttling", + "rank_score": 0.5148220658302307, + "text": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with ±25% jitter. Do NOT retry `ValidationException` or `AccessDeniedException` — these are permanent errors. `ModelStreamErrorException` during streaming may be retryable. Log the `x-amzn-requestid` header from failed responses for AWS support debugging. (context: Kimetsu Bedrock provider retry logic.)" + }, + { + "ce": 0.002108800457790494, + "key": "testing-snapshot-churn", + "rank_score": 0.4728803038597107, + "text": "project:fact - [tags: testing snapshot insta assert churn rust] Snapshot tests (e.g. with the `insta` crate) fail whenever the output changes, even for intended changes. In CI, they fail loudly; locally, `cargo insta review` walks you through accepting or rejecting changes. Snapshot churn becomes a problem when output includes timestamps, process IDs, or randomly-ordered maps. Redact these before snapshotting: use `insta::with_settings!({redactions: [\".timestamp\" => \"[TIMESTAMP]\"]})`. For JSON output, sort maps and arrays before comparing. Keep snapshot files in `src/snapshots/` and always commit them — an untracked snapshot file causes the next CI run to fail with a different error than expected. (context: Kimetsu CLI output snapshot tests — reducing churn.)" + } + ], + "delivered": [ + "http-timeout-layering", + "http-streaming-bodies" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8080482482910156 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "how do I safely retry a POST to the LLM API without creating duplicates?", + "relevant": [ + "http-retry-idempotency" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9997218251228333, + "key": "http-retry-idempotency", + "rank_score": 0.9549960494041443, + "text": "project:fact - [tags: http retry idempotency post put reqwest] Only retry idempotent requests automatically. GET, HEAD, PUT, DELETE are idempotent. POST is NOT — retrying a POST may create duplicate resources. For LLM API calls (POST), implement retry with idempotency keys: include a stable `X-Idempotency-Key: ` header; the provider deduplicates. For transient 429 (rate limit) responses, back off with jitter: `min(base * 2^attempt, cap) + rand(0, base)`. For 5xx, retry at most 3 times. Never retry on 4xx (except 429). In kimetsu, retry logic lives in the provider layer, not the distiller. (context: Kimetsu LLM provider retry strategy.)" + }, + { + "ce": 0.011914651840925217, + "key": "http-timeout-layering", + "rank_score": 0.4967631995677948, + "text": "project:fact - [tags: http reqwest timeout connect read total rust] reqwest has three distinct timeout knobs: `connect_timeout`, `read_timeout`, and `timeout` (total). They compose: if all three are set, the request fails at whichever fires first. For LLM API calls with streaming responses, `read_timeout` must be larger than the slowest expected token (often 30-60s) while `connect_timeout` can be tight (3-5s). `timeout` should be your SLA ceiling. If you set only `timeout`, a slow connect eats into the overall budget. For kimetsu-remote, set both `connect_timeout(5s)` and `timeout(120s)` — the LLM call is the bottleneck. (context: Kimetsu provider timeouts — request timing out during streaming.)" + }, + { + "ce": 0.0001428022951586172, + "key": "tokio-spawn-blocking", + "rank_score": 0.45681658387184143, + "text": "project:fact - [tags: tokio spawn_blocking thread-pool rust blocking] `tokio::task::spawn_blocking` places work on a dedicated blocking thread pool (default up to 512 threads, configurable via `Builder::max_blocking_threads`). Each call creates or reuses a thread — there's no true pooling, threads may be created on demand. For many short-duration blocking calls (e.g. per-query SQLite reads), thread creation overhead may dominate. Prefer batching: collect N queries, then one `spawn_blocking` to run them all. Alternatively, keep a persistent blocking task that reads from an mpsc channel. Profile with `tokio-console` if you suspect spawn_blocking overhead. (context: Kimetsu retrieval server — per-query spawn_blocking was adding ~0.3ms overhead.)" + }, + { + "ce": 0.00296107423491776, + "key": "aws-sigv4-bedrock-blocking", + "rank_score": 0.45531344413757324, + "text": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed. aws-smithy-runtime-api required as a companion to supply Identity. (context: Implementing BedrockProvider for Kimetsu with blocking reqwest + SigV4 signing, no tokio/aws-sdk)" + }, + { + "ce": 0.00012249678547959775, + "key": "gc-trace-env-guard-placement", + "rank_score": 0.4480980634689331, + "text": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site. (context: QQ4 — runs auto-GC on run creation. Env guard placement decision when wiring opportunistic GC into TraceWriter::create.)" + }, + { + "ce": 0.004316151142120361, + "key": "http-connection-pooling", + "rank_score": 0.4610667824745178, + "text": "project:fact - [tags: http reqwest connection-pool keep-alive rust] reqwest's `Client` holds a connection pool; always create ONE `Client` instance and clone it for each handler — cloning is cheap (Arc under the hood). Creating a `Client::new()` per request defeats connection pooling and causes TCP connection exhaustion under load. The default pool settings: max_idle_per_host=usize::MAX (unbounded), idle_timeout=90s. For a kimetsu outbound client (LLM provider), set `pool_max_idle_per_host(5)` to limit idle connections. On Windows, the underlying hyper+winapi stack may not reuse connections as aggressively as on Linux — set `connection_verbose(true)` on the builder to confirm reuse. (context: Kimetsu provider HTTP client — connection pooling best practices.)" + } + ], + "delivered": [ + "http-retry-idempotency" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7882905602455139 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "custom enterprise root CA not trusted by rustls on Windows", + "relevant": [ + "http-tls-roots" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999604225158691, + "key": "http-tls-roots", + "rank_score": 0.9549960494041443, + "text": "project:fact - [tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle — the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle. Alternatively, add the custom root with `add_root_certificate`. On Linux, the system CA bundle is at `/etc/ssl/certs/ca-certificates.crt`; on Windows it's in the Windows Certificate Store. (context: Kimetsu on a corporate Windows machine with a custom proxy CA.)" + }, + { + "ce": 0.00037093323771841824, + "key": "remote-ingest-split-roots", + "rank_score": 0.39336591958999634, + "text": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races. Re-enable kimetsu_brain_ingest_repo in the tool allowlist only when ingest is configured, and INTERCEPT that tools/call in the remote handler (clone+ingest_repo_at_root) before the normal dispatch (which would walk the wrong dir). Hermetic test: git init a temp repo, register url=local path, ingest, then context retrieves the file capsule via FTS (noop embedder). (context: R3c: server-side ingest for kimetsu-remote — cloning repos so file-capsule retrieval works without a local checkout.)" + }, + { + "ce": 0.00273225549608469, + "key": "ci-cache-keys", + "rank_score": 0.4077030420303345, + "text": "project:fact - [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key — macOS and Windows have incompatible artifact formats. Separate the registry cache from the build cache: the registry (downloaded crates) changes rarely, the build cache changes every push. Bust the build cache on major dependency changes by adding a manual cache version suffix to the key. (context: Kimetsu CI — cache invalidation strategy.)" + }, + { + "ce": 0.10899780690670013, + "key": "http-proxy-env", + "rank_score": 0.4405107796192169, + "text": "project:fact - [tags: http proxy environment reqwest rust corporate] reqwest respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` environment variables by default (with `default-tls` or `rustls-tls`). In a corporate network, these may redirect traffic through an intercepting proxy that breaks mTLS or adds latency. To disable proxy usage entirely: `reqwest::ClientBuilder::no_proxy()`. On Windows, reqwest does NOT use the system proxy settings (IE/WinInet) — you must set env vars explicitly. `NO_PROXY=127.0.0.1,localhost` prevents proxying loopback traffic (important for kimetsu-remote local dev). (context: Kimetsu provider calls failing behind corporate proxy on Windows.)" + }, + { + "ce": 0.00011074014037149027, + "key": "onnx-tokenizer-mismatch", + "rank_score": 0.3794277310371399, + "text": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly — specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings — cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo. Validate by checking a reference embedding against the HuggingFace Python output. (context: Kimetsu custom ONNX reranker loading — wrong tokenizer produced degraded retrieval.)" + }, + { + "ce": 0.0009729901212267578, + "key": "testing-serial-vs-parallel", + "rank_score": 0.37601709365844727, + "text": "project:fact - [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`). `cargo nextest` runs each test in a separate process by default, avoiding the problem entirely at the cost of longer startup time. For kimetsu, prefer nextest in CI and accept that `test_env_lock` exists only for `cargo test` compatibility. (context: Kimetsu test suite — env-var mutation in parallel tests.)" + } + ], + "delivered": [ + "http-tls-roots" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8140328526496887 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "parsing server-sent events when a single TCP chunk contains a partial SSE frame", + "relevant": [ + "http-streaming-bodies" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.0008822006639093161, + "key": "process-start-time-cross-platform", + "rank_score": 0.3751204311847687, + "text": "project:fact - [2026-09-05] [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path). Keep the skew decision logic in a pure function `assess_mcp_skew(servers, binary_mtime, binary_path) -> Outcome` so it can be unit-tested without any live OS state. (context: Q3 — kimetsu doctor version-skew check for stale MCP server processes)" + }, + { + "ce": 0.00010057283361675218, + "key": "sqlite-partial-index", + "rank_score": 0.38191401958465576, + "text": "project:fact - [2026-09-05] [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query — the planner uses the partial index only when the WHERE clause matches. Verify index usage with `EXPLAIN QUERY PLAN SELECT ...`. Partial indexes are not supported before SQLite 3.8.0; rusqlite's bundled SQLite is always current, but system SQLite on old Debian/Ubuntu may not be. (context: Optimizing kimetsu brain retrieval query over the active-memories subset.)" + }, + { + "ce": 0.0008415362099185586, + "key": "git-sparse-checkout", + "rank_score": 0.39170724153518677, + "text": "project:fact - [2026-09-05] [tags: git sparse-checkout partial-clone bandwidth] `git sparse-checkout init --cone` combined with `git clone --filter=blob:none` (partial clone) fetches only the commit graph and tree objects, not blobs. Individual blobs are fetched on demand when accessed. This cuts clone time for large repos from minutes to seconds. For kimetsu server-side ingest, use `git clone --depth 1 --filter=blob:none` for the initial checkout, then `git sparse-checkout set ` to limit the working tree to indexed directories. On `git fetch --depth 1 origin main` for refresh, blobs in the sparse set are updated lazily. (context: Kimetsu remote ingest — reducing bandwidth and disk usage for large repo checkouts.)" + }, + { + "ce": 0.9920685291290283, + "key": "http-streaming-bodies", + "rank_score": 0.9549960494041443, + "text": "project:fact - [2026-09-05] [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding — a chunk may split across frame boundaries. In kimetsu's proxy path, accumulate bytes until `\n\n` (SSE frame delimiter) before parsing the JSON data field. Never assume one `.chunk()` call = one SSE event. (context: Kimetsu remote proxy — streaming LLM responses to the client.)" + }, + { + "ce": 0.0011112758656963706, + "key": "mcp-stdout-protocol", + "rank_score": 0.3884022533893585, + "text": "project:fact - [2026-09-05] [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr. `println!` in the request handler is forbidden. Use `eprintln!` or `tracing` with a stderr subscriber. In tests of the MCP server, capture stdout as bytes and validate it parses as JSON-Lines. When debugging, set `KIMETSU_LOG=debug` which writes to stderr only. (context: Kimetsu MCP server stdout protocol hygiene.)" + }, + { + "ce": 0.0006313439225777984, + "key": "kimetsu-bench-remote-embedder-singleton", + "rank_score": 0.39640846848487854, + "text": "project:fact - [2026-09-05] [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval. Workaround: run ONE `--embedders` value per invocation and kill the remote process between runs. The local bench path is not affected (each combo is process-isolated via `--single` child spawn). (context: Kimetsu brain bench --remote known issue — multi-embedder contamination.)" + } + ], + "delivered": [ + "http-streaming-bodies" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7187910676002502 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "reqwest does not use the system proxy settings on Windows", + "relevant": [ + "http-proxy-env" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999799728393555, + "key": "http-proxy-env", + "rank_score": 0.9549959897994995, + "text": "project:fact - [tags: http proxy environment reqwest rust corporate] reqwest respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` environment variables by default (with `default-tls` or `rustls-tls`). In a corporate network, these may redirect traffic through an intercepting proxy that breaks mTLS or adds latency. To disable proxy usage entirely: `reqwest::ClientBuilder::no_proxy()`. On Windows, reqwest does NOT use the system proxy settings (IE/WinInet) — you must set env vars explicitly. `NO_PROXY=127.0.0.1,localhost` prevents proxying loopback traffic (important for kimetsu-remote local dev). (context: Kimetsu provider calls failing behind corporate proxy on Windows.)" + }, + { + "ce": 0.9782498478889465, + "key": "http-tls-roots", + "rank_score": 0.6897028684616089, + "text": "project:fact - [tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle — the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle. Alternatively, add the custom root with `add_root_certificate`. On Linux, the system CA bundle is at `/etc/ssl/certs/ca-certificates.crt`; on Windows it's in the Windows Certificate Store. (context: Kimetsu on a corporate Windows machine with a custom proxy CA.)" + }, + { + "ce": 0.07140615582466125, + "key": "http-streaming-bodies", + "rank_score": 0.5613460540771484, + "text": "project:fact - [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding — a chunk may split across frame boundaries. In kimetsu's proxy path, accumulate bytes until `\n\n` (SSE frame delimiter) before parsing the JSON data field. Never assume one `.chunk()` call = one SSE event. (context: Kimetsu remote proxy — streaming LLM responses to the client.)" + }, + { + "ce": 0.0001273390807909891, + "key": "kimetsu-proactive-hooks", + "rank_score": 0.5519711375236511, + "text": "project:fact - [tags: kimetsu proactive hooks context injection] kimetsu's proactive context injection runs before each agent turn (pre-turn hook) and injects relevant memories into the system prompt prefix. The hook invocation adds latency to the first token: embedding inference + vector search + reranking + context formatting. On a cold start, this can be 1-3 seconds. The hook is optional — disable with `KIMETSU_PROACTIVE=0`. The semantic floor (min cosine similarity) filters noise capsules before injection; setting the floor too low injects irrelevant memories and wastes context window tokens. The proactive hook does NOT trigger the distiller — that runs post-session only. (context: Kimetsu proactive context injection — latency and floor tuning.)" + }, + { + "ce": 0.0002913472999352962, + "key": "sqlite-foreign-keys-default-off", + "rank_score": 0.5028889179229736, + "text": "project:fact - [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting — every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing. Check your schema with `PRAGMA foreign_key_list(table_name);` and your current setting with `PRAGMA foreign_keys;`. rusqlite does not enable foreign keys automatically. (context: Kimetsu brain schema — memory_tags table has FK to memories table, discovered ON DELETE CASCADE wasn't firing.)" + }, + { + "ce": 0.12340021133422852, + "key": "http-connection-pooling", + "rank_score": 0.5130411386489868, + "text": "project:fact - [tags: http reqwest connection-pool keep-alive rust] reqwest's `Client` holds a connection pool; always create ONE `Client` instance and clone it for each handler — cloning is cheap (Arc under the hood). Creating a `Client::new()` per request defeats connection pooling and causes TCP connection exhaustion under load. The default pool settings: max_idle_per_host=usize::MAX (unbounded), idle_timeout=90s. For a kimetsu outbound client (LLM provider), set `pool_max_idle_per_host(5)` to limit idle connections. On Windows, the underlying hyper+winapi stack may not reuse connections as aggressively as on Linux — set `connection_verbose(true)` on the builder to confirm reuse. (context: Kimetsu provider HTTP client — connection pooling best practices.)" + } + ], + "delivered": [ + "http-proxy-env", + "http-tls-roots" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8185521364212036 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "insta snapshot tests fail in CI because output includes a timestamp", + "relevant": [ + "testing-snapshot-churn" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999759197235107, + "key": "testing-snapshot-churn", + "rank_score": 0.9549959897994995, + "text": "project:fact - [tags: testing snapshot insta assert churn rust] Snapshot tests (e.g. with the `insta` crate) fail whenever the output changes, even for intended changes. In CI, they fail loudly; locally, `cargo insta review` walks you through accepting or rejecting changes. Snapshot churn becomes a problem when output includes timestamps, process IDs, or randomly-ordered maps. Redact these before snapshotting: use `insta::with_settings!({redactions: [\".timestamp\" => \"[TIMESTAMP]\"]})`. For JSON output, sort maps and arrays before comparing. Keep snapshot files in `src/snapshots/` and always commit them — an untracked snapshot file causes the next CI run to fail with a different error than expected. (context: Kimetsu CLI output snapshot tests — reducing churn.)" + }, + { + "ce": 0.08006066828966141, + "key": "init-project-git-boundary", + "rank_score": 0.48821359872817993, + "text": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain. (context: QQ3 — kimetsu setup integration test failed because init_project climbed git tree to real ~/.kimetsu instead of temp workspace)" + }, + { + "ce": 0.046447426080703735, + "key": "ci-flaky-quarantine", + "rank_score": 0.46785831451416016, + "text": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal — a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output. NEVER let a flaky test gate the merge queue. For kimetsu timing-based tests (`test_gc_old_runs_deletes_ancient`), apply `#[cfg_attr(ci, ignore)]` and run only in a dedicated slow-CI job. (context: Kimetsu CI flaky test policy.)" + }, + { + "ce": 0.08173566311597824, + "key": "testing-property-tests", + "rank_score": 0.4550896883010864, + "text": "project:fact - [tags: testing property-based proptest quickcheck rust] Property-based tests (proptest, quickcheck) find edge cases that example-based tests miss. For kimetsu's memory text normalization, proptest found that zero-width joiner characters and right-to-left marks caused hash collisions. Run proptest with `PROPTEST_CASES=10000` in CI for thorough coverage. Shrinking: when proptest finds a failure, it automatically shrinks the input to the minimal failing case — read the `Minimized failure` output, not the original random input. Use `prop_assume!` to skip inputs that violate preconditions rather than `if/return`. (context: Kimetsu brain text normalization — property test for dedup hash stability.)" + }, + { + "ce": 0.001472697127610445, + "key": "ci-secrets-masking", + "rank_score": 0.4102403223514557, + "text": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output — but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable. Never reconstruct secrets from parts in step output. For kimetsu bench `--remote` CI runs, `KIMETSU_REMOTE_TOKEN` must be in the repository secrets, not in the workflow YAML. Use `${{ secrets.KIMETSU_REMOTE_TOKEN }}` in env — never `echo ${{ secrets.KIMETSU_REMOTE_TOKEN }}` in a run step. (context: Kimetsu CI remote benchmark — token handling.)" + }, + { + "ce": 0.0024876180104911327, + "key": "cargo-feature-unification-embeddings", + "rank_score": 0.41660594940185547, + "text": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli). Diagnostic tell: a test that passes alone but fails only under `cargo test --workspace` AND a brand-new crate was just added = suspect feature unification flipping a sibling crate's behavior. (context: Building the kimetsu-remote crate (HTTP MCP server); its default embeddings feature broke 3 kimetsu-chat retrieval tests only under the full workspace test.)" + } + ], + "delivered": [ + "testing-snapshot-churn" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8255270719528198 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "two test workers writing to the same temp directory path race each other", + "relevant": [ + "testing-temp-dirs-ci" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9582907557487488, + "key": "testing-temp-dirs-ci", + "rank_score": 0.9549959897994995, + "text": "project:fact - [tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure. On Windows, `env::temp_dir()` returns `C:\\Users\\\\AppData\\Local\\Temp` — ensure the test binary has write permissions there. Avoid using the workspace root as a temp dir — tests should never write to the source tree. (context: Kimetsu test infrastructure — temp directory discipline.)" + }, + { + "ce": 0.03311114385724068, + "key": "testing-serial-vs-parallel", + "rank_score": 0.665880024433136, + "text": "project:fact - [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`). `cargo nextest` runs each test in a separate process by default, avoiding the problem entirely at the cost of longer startup time. For kimetsu, prefer nextest in CI and accept that `test_env_lock` exists only for `cargo test` compatibility. (context: Kimetsu test suite — env-var mutation in parallel tests.)" + }, + { + "ce": 0.00006090776514611207, + "key": "onnx-model-cache-paths", + "rank_score": 0.5284135937690735, + "text": "project:fact - [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use. In kimetsu, `KIMETSU_EMBEDDER_CACHE` overrides the path and is forwarded when spawning child bench processes — without forwarding it, each child re-downloads the model. (context: Kimetsu brain bench on CI — model cache path handling in child processes.)" + }, + { + "ce": 0.0024541730526834726, + "key": "git-worktree-brain-isolation", + "rank_score": 0.5355926156044006, + "text": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root — if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain. Always set `KIMETSU_BRAIN_DIR` or use `git_init_boundary` in tests to prevent this. (context: Kimetsu development with git worktrees — test isolation.)" + }, + { + "ce": 0.006391242612153292, + "key": "init-project-git-boundary", + "rank_score": 0.5242590308189392, + "text": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain. (context: QQ3 — kimetsu setup integration test failed because init_project climbed git tree to real ~/.kimetsu instead of temp workspace)" + }, + { + "ce": 0.002603440545499325, + "key": "harbor-terminal-bench-subprocess-isolation", + "rank_score": 0.4827457666397095, + "text": "project:fact - [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd). Worker re-derives auth internally from .env so the OAuth token never lands in argv; it writes {run,grade} JSON the parent reads back. One Harbor invocation per process always works (baseline-alone passed). (context: kbench multi-trial sweeps crashed on every trial after the 1st; diagnosed as Harbor/pyiceberg os.getcwd staleness on WSL2.)" + } + ], + "delivered": [ + "testing-temp-dirs-ci" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.775429904460907 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "test passes locally but fails on a slow CI runner due to a 100ms sleep", + "relevant": [ + "testing-time-dependent-flakes" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.29728952050209045, + "key": "testing-time-dependent-flakes", + "rank_score": 0.9549959301948547, + "text": "project:fact - [tags: testing time flaky clock mock rust] Tests that depend on wall-clock time are inherently flaky under load (slow CI runners, GC pauses). Abstract time behind a trait (`Clock: Fn() -> SystemTime`) injected at construction, and supply a fake in tests. For tests checking that something happened \"within N seconds\", use a generous multiple of the expected duration (10x is not unreasonable for CI). `std::thread::sleep` in tests is a smell — prefer channel synchronization or a condvar instead of timing-based waits. If you must use sleep, set `KIMETSU_TEST_TIMEOUT_SCALE` to stretch timeouts in slow environments. (context: Kimetsu GC and TTL tests — time-dependent flakes on loaded CI.)" + }, + { + "ce": 0.17467260360717773, + "key": "testing-snapshot-churn", + "rank_score": 0.8790387511253357, + "text": "project:fact - [tags: testing snapshot insta assert churn rust] Snapshot tests (e.g. with the `insta` crate) fail whenever the output changes, even for intended changes. In CI, they fail loudly; locally, `cargo insta review` walks you through accepting or rejecting changes. Snapshot churn becomes a problem when output includes timestamps, process IDs, or randomly-ordered maps. Redact these before snapshotting: use `insta::with_settings!({redactions: [\".timestamp\" => \"[TIMESTAMP]\"]})`. For JSON output, sort maps and arrays before comparing. Keep snapshot files in `src/snapshots/` and always commit them — an untracked snapshot file causes the next CI run to fail with a different error than expected. (context: Kimetsu CLI output snapshot tests — reducing churn.)" + }, + { + "ce": 0.0014347585383802652, + "key": "ci-matrix-explosion", + "rank_score": 0.8399252891540527, + "text": "project:fact - [tags: ci github-actions matrix jobs resources] A CI matrix combining OS (3) x Rust toolchain (3) x features (2) = 18 jobs. Each spawns a runner; at $0.008/min for Ubuntu and $0.016/min for Windows, a 10-minute build costs $2.40 per push. Reduce: test the full matrix only on PRs to main; on feature branches, test only Linux+stable. Use `fail-fast: false` to see all failures, not just the first. Combine related checks (clippy + test) in one job when they share build artifacts. For Windows-specific tests, run only the OS-specific job to reduce cost. (context: Kimetsu CI matrix cost optimization.)" + }, + { + "ce": 0.008367644622921944, + "key": "ci-flaky-quarantine", + "rank_score": 0.8192565441131592, + "text": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal — a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output. NEVER let a flaky test gate the merge queue. For kimetsu timing-based tests (`test_gc_old_runs_deletes_ancient`), apply `#[cfg_attr(ci, ignore)]` and run only in a dedicated slow-CI job. (context: Kimetsu CI flaky test policy.)" + }, + { + "ce": 0.001291407155804336, + "key": "init-project-git-boundary", + "rank_score": 0.7480679154396057, + "text": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain. (context: QQ3 — kimetsu setup integration test failed because init_project climbed git tree to real ~/.kimetsu instead of temp workspace)" + }, + { + "ce": 0.002702184719964862, + "key": "cargo-feature-unification-embeddings", + "rank_score": 0.7559422254562378, + "text": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli). Diagnostic tell: a test that passes alone but fails only under `cargo test --workspace` AND a brand-new crate was just added = suspect feature unification flipping a sibling crate's behavior. (context: Building the kimetsu-remote crate (HTTP MCP server); its default embeddings feature broke 3 kimetsu-chat retrieval tests only under the full workspace test.)" + } + ], + "delivered": [], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7388396859169006 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "proptest found a hash collision in text normalization that example tests missed", + "relevant": [ + "testing-property-tests" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999779462814331, + "key": "testing-property-tests", + "rank_score": 0.9549959301948547, + "text": "project:fact - [tags: testing property-based proptest quickcheck rust] Property-based tests (proptest, quickcheck) find edge cases that example-based tests miss. For kimetsu's memory text normalization, proptest found that zero-width joiner characters and right-to-left marks caused hash collisions. Run proptest with `PROPTEST_CASES=10000` in CI for thorough coverage. Shrinking: when proptest finds a failure, it automatically shrinks the input to the minimal failing case — read the `Minimized failure` output, not the original random input. Use `prop_assume!` to skip inputs that violate preconditions rather than `if/return`. (context: Kimetsu brain text normalization — property test for dedup hash stability.)" + }, + { + "ce": 0.00027316142222844064, + "key": "kimetsu-eval-fixture-shape", + "rank_score": 0.4461294710636139, + "text": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` — a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases). Keys must be unique across the dataset. The bench currently does not validate keys at load — it fails later with an `unwrap()` on a missing HashMap entry. (context: Kimetsu bench dataset shape and validation.)" + }, + { + "ce": 0.004143031779676676, + "key": "import-dedup-seen-ids", + "rank_score": 0.38969969749450684, + "text": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount — both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise. (context: Implementing brain export/import (Q5). First naive approach used a single `seen_ids` set local to the function; the dedup test caught it on the second-import assertion.)" + }, + { + "ce": 0.0002471487387083471, + "key": "cargo-feature-unification-embeddings", + "rank_score": 0.3593898415565491, + "text": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli). Diagnostic tell: a test that passes alone but fails only under `cargo test --workspace` AND a brand-new crate was just added = suspect feature unification flipping a sibling crate's behavior. (context: Building the kimetsu-remote crate (HTTP MCP server); its default embeddings feature broke 3 kimetsu-chat retrieval tests only under the full workspace test.)" + }, + { + "ce": 0.00006629205745412037, + "key": "onnx-cosine-vs-dot", + "rank_score": 0.3501185178756714, + "text": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing — double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g. E5, GTE with separate query/passage prefixes), the query and document encoders must use different prefix strings. Check the model card's `Similarity function` field. usearch/qdrant: prefer `MetricKind::Cos` over `Dot` for passage vectors that may not be perfectly normalized. (context: Kimetsu embedding storage — similarity metric selection.)" + }, + { + "ce": 0.00007031385757727548, + "key": "bridge-target-enum-seams", + "rank_score": 0.33425694704055786, + "text": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors. (context: Adding BridgeTarget::OpenClaw host to Kimetsu bridge.rs and main.rs in Workstream C)" + } + ], + "delivered": [ + "testing-property-tests" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8031892776489258 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "set_var in tests races when cargo test runs them in parallel", + "relevant": [ + "testing-serial-vs-parallel" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.0016231355257332325, + "key": "gc-trace-env-guard-placement", + "rank_score": 0.4998554289340973, + "text": "project:fact - [2026-09-05] [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site. (context: QQ4 — runs auto-GC on run creation. Env guard placement decision when wiring opportunistic GC into TraceWriter::create.)" + }, + { + "ce": 0.008467497304081917, + "key": "cargo-build-script-rerun", + "rank_score": 0.49530696868896484, + "text": "project:fact - [2026-09-05] [tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory. If the build script generates code from a schema file, emit `rerun-if-changed=schema.json`. If there are NO inputs (e.g. the script only inspects env vars), emit `cargo:rerun-if-changed=` with an empty string to suppress re-runs entirely. Missing this directive is the most common cause of unexpectedly slow incremental builds. (context: kimetsu-cli build.rs for embedding version stamps.)" + }, + { + "ce": 0.032180577516555786, + "key": "testing-snapshot-churn", + "rank_score": 0.5234954357147217, + "text": "project:fact - [2026-09-05] [tags: testing snapshot insta assert churn rust] Snapshot tests (e.g. with the `insta` crate) fail whenever the output changes, even for intended changes. In CI, they fail loudly; locally, `cargo insta review` walks you through accepting or rejecting changes. Snapshot churn becomes a problem when output includes timestamps, process IDs, or randomly-ordered maps. Redact these before snapshotting: use `insta::with_settings!({redactions: [\".timestamp\" => \"[TIMESTAMP]\"]})`. For JSON output, sort maps and arrays before comparing. Keep snapshot files in `src/snapshots/` and always commit them — an untracked snapshot file causes the next CI run to fail with a different error than expected. (context: Kimetsu CLI output snapshot tests — reducing churn.)" + }, + { + "ce": 0.1801309585571289, + "key": "testing-temp-dirs-ci", + "rank_score": 0.6113718748092651, + "text": "project:fact - [2026-09-05] [tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure. On Windows, `env::temp_dir()` returns `C:\\Users\\\\AppData\\Local\\Temp` — ensure the test binary has write permissions there. Avoid using the workspace root as a temp dir — tests should never write to the source tree. (context: Kimetsu test infrastructure — temp directory discipline.)" + }, + { + "ce": 0.9995033740997314, + "key": "testing-serial-vs-parallel", + "rank_score": 0.95499587059021, + "text": "project:fact - [2026-09-05] [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`). `cargo nextest` runs each test in a separate process by default, avoiding the problem entirely at the cost of longer startup time. For kimetsu, prefer nextest in CI and accept that `test_env_lock` exists only for `cargo test` compatibility. (context: Kimetsu test suite — env-var mutation in parallel tests.)" + }, + { + "ce": 0.042343344539403915, + "key": "ci-flaky-quarantine", + "rank_score": 0.6223882436752319, + "text": "project:fact - [2026-09-05] [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal — a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output. NEVER let a flaky test gate the merge queue. For kimetsu timing-based tests (`test_gc_old_runs_deletes_ancient`), apply `#[cfg_attr(ci, ignore)]` and run only in a dedicated slow-CI job. (context: Kimetsu CI flaky test policy.)" + } + ], + "delivered": [ + "testing-serial-vs-parallel" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7693217992782593 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "hardcoded JSON fixtures broke after a schema migration", + "relevant": [ + "testing-fixture-drift" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.03896329179406166, + "key": "harbor-terminal-bench-subprocess-isolation", + "rank_score": 0.37493211030960083, + "text": "project:fact - [2026-09-05] [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd). Worker re-derives auth internally from .env so the OAuth token never lands in argv; it writes {run,grade} JSON the parent reads back. One Harbor invocation per process always works (baseline-alone passed). (context: kbench multi-trial sweeps crashed on every trial after the 1st; diagnosed as Harbor/pyiceberg os.getcwd staleness on WSL2.)" + }, + { + "ce": 0.01050175353884697, + "key": "cargo-build-script-rerun", + "rank_score": 0.3941856026649475, + "text": "project:fact - [2026-09-05] [tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory. If the build script generates code from a schema file, emit `rerun-if-changed=schema.json`. If there are NO inputs (e.g. the script only inspects env vars), emit `cargo:rerun-if-changed=` with an empty string to suppress re-runs entirely. Missing this directive is the most common cause of unexpectedly slow incremental builds. (context: kimetsu-cli build.rs for embedding version stamps.)" + }, + { + "ce": 0.005798591300845146, + "key": "onnx-dim-mismatch", + "rank_score": 0.48180437088012695, + "text": "project:fact - [2026-09-05] [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results — the ANN index shape mismatch isn't always caught at runtime. kimetsu detects this by storing `embedder_id` in the brain schema and refusing to query if the configured embedder differs from what was used at ingest time. Mitigation: re-ingest all memories with the new model, or keep per-memory vector dim metadata. (context: Kimetsu embedder migration — detecting dimension mismatch at startup.)" + }, + { + "ce": 0.9999386072158813, + "key": "testing-fixture-drift", + "rank_score": 0.95499587059021, + "text": "project:fact - [2026-09-05] [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code. For kimetsu, `EvalFixture::from_memories(memories)` constructs a dataset from the exported format — use it in tests instead of hardcoded JSON. Tag fixture files with the schema version they were generated against in a comment. (context: Kimetsu eval fixture drift after schema migration.)" + }, + { + "ce": 0.002892687451094389, + "key": "mcp-schema-validation", + "rank_score": 0.4479562044143677, + "text": "project:fact - [2026-09-05] [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array — omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error. Use `serde(default)` for optional fields. When adding a new tool, check the schema in the tools/list response manually by piping a JSON-RPC tools/list request to `./kimetsu mcp`. (context: Kimetsu MCP tool schema — required field validation.)" + }, + { + "ce": 0.0060386378318071365, + "key": "kimetsu-eval-fixture-shape", + "rank_score": 0.4172876477241516, + "text": "project:fact - [2026-09-05] [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` — a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases). Keys must be unique across the dataset. The bench currently does not validate keys at load — it fails later with an `unwrap()` on a missing HashMap entry. (context: Kimetsu bench dataset shape and validation.)" + } + ], + "delivered": [ + "testing-fixture-drift" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7839416861534119 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "debug print in the MCP handler corrupts the JSON-Lines protocol stream", + "relevant": [ + "mcp-stdout-protocol" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999716281890869, + "key": "mcp-stdout-protocol", + "rank_score": 0.95499587059021, + "text": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr. `println!` in the request handler is forbidden. Use `eprintln!` or `tracing` with a stderr subscriber. In tests of the MCP server, capture stdout as bytes and validate it parses as JSON-Lines. When debugging, set `KIMETSU_LOG=debug` which writes to stderr only. (context: Kimetsu MCP server stdout protocol hygiene.)" + }, + { + "ce": 0.000387244246667251, + "key": "windows-console-encoding", + "rank_score": 0.4583858847618103, + "text": "project:fact - [tags: windows console encoding utf8 rust] Windows console code page defaults to the system ANSI code page (usually CP1252 or CP932), not UTF-8. Rust's `println!` writes UTF-8 bytes which display as mojibake in a non-UTF-8 console. Fix at process startup: call `SetConsoleOutputCP(65001)` via `winapi` or `windows-sys`, or set `PYTHONUTF8=1`/`RUST_LOG` before launch. In PowerShell, `[Console]::OutputEncoding = [System.Text.Encoding]::UTF8` fixes the session. For binary piped output (MCP stdio protocol), write raw bytes — don't use the console code page. (context: Kimetsu MCP server — Unicode memory text was garbled on non-UTF8 Windows terminals.)" + }, + { + "ce": 0.028298206627368927, + "key": "aws-retry-throttling", + "rank_score": 0.42003270983695984, + "text": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with ±25% jitter. Do NOT retry `ValidationException` or `AccessDeniedException` — these are permanent errors. `ModelStreamErrorException` during streaming may be retryable. Log the `x-amzn-requestid` header from failed responses for AWS support debugging. (context: Kimetsu Bedrock provider retry logic.)" + }, + { + "ce": 0.04585421457886696, + "key": "mcp-transcript-paths", + "rank_score": 0.4268433451652527, + "text": "project:fact - [tags: mcp transcript paths kimetsu hooks runs] kimetsu writes run transcripts to `/.kimetsu/runs//`. The post-session hook reads the latest run's transcript to trigger memory harvest. On Windows, the path uses backslashes internally but the MCP JSON must use forward slashes or the host may reject path-type arguments. `std::path::Path::display()` produces backslashes on Windows — use `.to_string_lossy().replace('\\\\', \"/\")` when serializing paths for MCP protocol. The transcript path is included in the `kimetsu_brain_context` response under the `run_dir` field for the distiller's reference. (context: Kimetsu transcript path handling in MCP responses on Windows.)" + }, + { + "ce": 0.006582882720977068, + "key": "remote-mcp-host-wiring", + "rank_score": 0.41868856549263, + "text": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal. Derive a stable repo id from the git remote: strip `.git`, scheme (`://`), and `user@`, then map non-alphanumerics to '-' and collapse — so both https://github.com/org/repo.git and git@github.com:org/repo.git -> `github-com-org-repo`. Remote install writes ONLY the MCP entry + instructions (no local hooks — the brain is on the server). Codex/Pi don't get --remote (no remote-MCP / no MCP). (context: R2: implementing `kimetsu plugin install --remote` to wire a host at a kimetsu-remote HTTP MCP server.)" + }, + { + "ce": 0.0569751039147377, + "key": "http-streaming-bodies", + "rank_score": 0.408209890127182, + "text": "project:fact - [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding — a chunk may split across frame boundaries. In kimetsu's proxy path, accumulate bytes until `\n\n` (SSE frame delimiter) before parsing the JSON data field. Never assume one `.chunk()` call = one SSE event. (context: Kimetsu remote proxy — streaming LLM responses to the client.)" + } + ], + "delivered": [ + "mcp-stdout-protocol" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8302762508392334 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "kimetsu MCP tool call times out because embedding model is re-initialized every call", + "relevant": [ + "mcp-tool-timeouts" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9981862902641296, + "key": "mcp-tool-timeouts", + "rank_score": 0.9549958109855652, + "text": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking — in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize — keep it in a process-global `OnceLock`). The reranker adds another 200-800ms; jina-tiny is the fastest. If tool calls are still slow, log the per-stage latency with `tracing::info!` at DEBUG level and profile under load. (context: Kimetsu MCP tool latency optimization.)" + }, + { + "ce": 0.4284912645816803, + "key": "kimetsu-distiller-config", + "rank_score": 0.6927502155303955, + "text": "project:fact - [tags: kimetsu distiller harvest config provider] The kimetsu distiller (auto-harvester) uses a SEPARATE provider configuration from the main agent: `distiller.provider`, `distiller.model`, `distiller.api_key`. This allows running the agent on an expensive model (Claude Opus) while harvesting with a cheap model (Claude Haiku). If `distiller.provider` is not set, it inherits `provider`. The distiller runs as a background task triggered by the post-session hook; it reads the session transcript and emits `kimetsu_brain_record` calls. Distiller timeouts are longer (300s) than normal tool calls (60s) because transcript processing can be slow. (context: Kimetsu distiller provider configuration — agent vs harvester model separation.)" + }, + { + "ce": 0.1209874376654625, + "key": "kimetsu-bench-remote-embedder-singleton", + "rank_score": 0.6191590428352356, + "text": "project:fact - [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval. Workaround: run ONE `--embedders` value per invocation and kill the remote process between runs. The local bench path is not affected (each combo is process-isolated via `--single` child spawn). (context: Kimetsu brain bench --remote known issue — multi-embedder contamination.)" + }, + { + "ce": 0.1726692169904709, + "key": "mcp-schema-validation", + "rank_score": 0.6017546653747559, + "text": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array — omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error. Use `serde(default)` for optional fields. When adding a new tool, check the schema in the tools/list response manually by piping a JSON-RPC tools/list request to `./kimetsu mcp`. (context: Kimetsu MCP tool schema — required field validation.)" + }, + { + "ce": 0.00528971990570426, + "key": "onnx-quantization-drift", + "rank_score": 0.5452984571456909, + "text": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals — cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case. (context: Kimetsu embedding model selection — evaluating jina-v2 int8 vs fp32.)" + }, + { + "ce": 0.010832443833351135, + "key": "onnx-tokenizer-mismatch", + "rank_score": 0.5525227785110474, + "text": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly — specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings — cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo. Validate by checking a reference embedding against the HuggingFace Python output. (context: Kimetsu custom ONNX reranker loading — wrong tokenizer produced degraded retrieval.)" + } + ], + "delivered": [ + "mcp-tool-timeouts", + "kimetsu-distiller-config" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.830784022808075 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "env var set after host launch is not visible to the MCP server process", + "relevant": [ + "mcp-env-propagation" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.007766901981085539, + "key": "mutex-deadlock-user-brain-disabled", + "rank_score": 0.5281539559364319, + "text": "project:fact - [2026-09-05] [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure — `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation. (context: New tests for Tier-1 perf work called test_env_lock().lock() inside with_user_brain_disabled closure, deadlocking all project::tests that ran after them in the same test binary.)" + }, + { + "ce": 0.019950099289417267, + "key": "remote-mcp-host-wiring", + "rank_score": 0.570264995098114, + "text": "project:fact - [2026-09-05] [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal. Derive a stable repo id from the git remote: strip `.git`, scheme (`://`), and `user@`, then map non-alphanumerics to '-' and collapse — so both https://github.com/org/repo.git and git@github.com:org/repo.git -> `github-com-org-repo`. Remote install writes ONLY the MCP entry + instructions (no local hooks — the brain is on the server). Codex/Pi don't get --remote (no remote-MCP / no MCP). (context: R2: implementing `kimetsu plugin install --remote` to wire a host at a kimetsu-remote HTTP MCP server.)" + }, + { + "ce": 0.2247989922761917, + "key": "gc-trace-env-guard-placement", + "rank_score": 0.6363220810890198, + "text": "project:fact - [2026-09-05] [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site. (context: QQ4 — runs auto-GC on run creation. Env guard placement decision when wiring opportunistic GC into TraceWriter::create.)" + }, + { + "ce": 0.999742329120636, + "key": "mcp-env-propagation", + "rank_score": 0.9549958109855652, + "text": "project:fact - [2026-09-05] [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment — changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate. For kimetsu hooks (pre-commit, post-commit), the hook script inherits the shell's env at hook invocation time, not the server's. If `KIMETSU_BRAIN_DIR` needs to vary per project, set it in the project's `.env` file and source it in the hook script. (context: Kimetsu env propagation from hooks to MCP server.)" + }, + { + "ce": 0.003909943625330925, + "key": "mcp-tool-naming", + "rank_score": 0.524860143661499, + "text": "project:fact - [2026-09-05] [tags: mcp tool naming convention kimetsu] MCP tool names must be valid identifiers for all host agents. Claude Code restricts tool names to `[a-zA-Z0-9_-]` and max 64 chars. Use `snake_case` (kimetsu_brain_context, kimetsu_brain_record) — hyphen is technically allowed but some hosts reject it. Avoid dots (not allowed). Namespace with a prefix (`kimetsu_brain_`) to prevent collisions with other MCP servers. When a tool name changes, update ALL host config files (`.mcp.json`, `openclaw.json`, skill markdown) — mismatched names cause silent failures where the host skips the tool. (context: Kimetsu MCP tool naming convention enforcement.)" + }, + { + "ce": 0.9039230942726135, + "key": "kimetsu-daemon-lifecycle", + "rank_score": 0.7187671065330505, + "text": "project:fact - [2026-09-05] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required. The server PID is not stored anywhere; use `kimetsu doctor` to enumerate running MCP server processes via OS APIs. On Windows, the server binary may be locked by AV after first launch — `kimetsu update` must stop all running server processes before replacing the binary. (context: Kimetsu daemon lifecycle — process management for updates.)" + } + ], + "delivered": [ + "mcp-env-propagation", + "kimetsu-daemon-lifecycle" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7575567960739136 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "MCP tool call fails because a required field is missing from the JSON input", + "relevant": [ + "mcp-schema-validation" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9998551607131958, + "key": "mcp-schema-validation", + "rank_score": 0.9549958109855652, + "text": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array — omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error. Use `serde(default)` for optional fields. When adding a new tool, check the schema in the tools/list response manually by piping a JSON-RPC tools/list request to `./kimetsu mcp`. (context: Kimetsu MCP tool schema — required field validation.)" + }, + { + "ce": 0.1382421851158142, + "key": "mcp-tool-naming", + "rank_score": 0.6390325427055359, + "text": "project:fact - [tags: mcp tool naming convention kimetsu] MCP tool names must be valid identifiers for all host agents. Claude Code restricts tool names to `[a-zA-Z0-9_-]` and max 64 chars. Use `snake_case` (kimetsu_brain_context, kimetsu_brain_record) — hyphen is technically allowed but some hosts reject it. Avoid dots (not allowed). Namespace with a prefix (`kimetsu_brain_`) to prevent collisions with other MCP servers. When a tool name changes, update ALL host config files (`.mcp.json`, `openclaw.json`, skill markdown) — mismatched names cause silent failures where the host skips the tool. (context: Kimetsu MCP tool naming convention enforcement.)" + }, + { + "ce": 0.14245593547821045, + "key": "cargo-feature-unification-embeddings", + "rank_score": 0.6147328615188599, + "text": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli). Diagnostic tell: a test that passes alone but fails only under `cargo test --workspace` AND a brand-new crate was just added = suspect feature unification flipping a sibling crate's behavior. (context: Building the kimetsu-remote crate (HTTP MCP server); its default embeddings feature broke 3 kimetsu-chat retrieval tests only under the full workspace test.)" + }, + { + "ce": 0.023470234125852585, + "key": "mcp-tool-timeouts", + "rank_score": 0.5603100061416626, + "text": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking — in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize — keep it in a process-global `OnceLock`). The reranker adds another 200-800ms; jina-tiny is the fastest. If tool calls are still slow, log the per-stage latency with `tracing::info!` at DEBUG level and profile under load. (context: Kimetsu MCP tool latency optimization.)" + }, + { + "ce": 0.021436229348182678, + "key": "sqlite-json1-extract", + "rank_score": 0.5410283803939819, + "text": "project:fact - [tags: sqlite json1 json_extract rusqlite] SQLite's json1 extension (built in since 3.38.0) lets you index and query JSONB columns with `json_extract(col, '$.field')`. To create a partial index over a JSON field: `CREATE INDEX idx ON memories (json_extract(metadata, '$.scope')) WHERE json_extract(metadata, '$.scope') IS NOT NULL;`. Use `json_each` for array fields. On older SQLite builds (rusqlite links whatever the system provides), check for json1 with `SELECT json('{}');` — an error means it's absent. Always prefer column storage over JSON blobs for frequently queried fields. (context: Kimetsu brain querying metadata scopes without migrating a separate column.)" + }, + { + "ce": 0.00008055399666773155, + "key": "kimetsu-distiller-config", + "rank_score": 0.5329103469848633, + "text": "project:fact - [tags: kimetsu distiller harvest config provider] The kimetsu distiller (auto-harvester) uses a SEPARATE provider configuration from the main agent: `distiller.provider`, `distiller.model`, `distiller.api_key`. This allows running the agent on an expensive model (Claude Opus) while harvesting with a cheap model (Claude Haiku). If `distiller.provider` is not set, it inherits `provider`. The distiller runs as a background task triggered by the post-session hook; it reads the session transcript and emits `kimetsu_brain_record` calls. Distiller timeouts are longer (300s) than normal tool calls (60s) because transcript processing can be slow. (context: Kimetsu distiller provider configuration — agent vs harvester model separation.)" + } + ], + "delivered": [ + "mcp-schema-validation" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7972217798233032 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "Claude Code rejects the tool name with a hyphen in it", + "relevant": [ + "mcp-tool-naming" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.999729573726654, + "key": "mcp-tool-naming", + "rank_score": 0.9549958109855652, + "text": "project:fact - [tags: mcp tool naming convention kimetsu] MCP tool names must be valid identifiers for all host agents. Claude Code restricts tool names to `[a-zA-Z0-9_-]` and max 64 chars. Use `snake_case` (kimetsu_brain_context, kimetsu_brain_record) — hyphen is technically allowed but some hosts reject it. Avoid dots (not allowed). Namespace with a prefix (`kimetsu_brain_`) to prevent collisions with other MCP servers. When a tool name changes, update ALL host config files (`.mcp.json`, `openclaw.json`, skill markdown) — mismatched names cause silent failures where the host skips the tool. (context: Kimetsu MCP tool naming convention enforcement.)" + }, + { + "ce": 0.008303160779178143, + "key": "mcp-tool-timeouts", + "rank_score": 0.5266464948654175, + "text": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking — in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize — keep it in a process-global `OnceLock`). The reranker adds another 200-800ms; jina-tiny is the fastest. If tool calls are still slow, log the per-stage latency with `tracing::info!` at DEBUG level and profile under load. (context: Kimetsu MCP tool latency optimization.)" + }, + { + "ce": 0.0008932608761824667, + "key": "kimetsu-distiller-config", + "rank_score": 0.4659808278083801, + "text": "project:fact - [tags: kimetsu distiller harvest config provider] The kimetsu distiller (auto-harvester) uses a SEPARATE provider configuration from the main agent: `distiller.provider`, `distiller.model`, `distiller.api_key`. This allows running the agent on an expensive model (Claude Opus) while harvesting with a cheap model (Claude Haiku). If `distiller.provider` is not set, it inherits `provider`. The distiller runs as a background task triggered by the post-session hook; it reads the session transcript and emits `kimetsu_brain_record` calls. Distiller timeouts are longer (300s) than normal tool calls (60s) because transcript processing can be slow. (context: Kimetsu distiller provider configuration — agent vs harvester model separation.)" + }, + { + "ce": 0.0008480148389935493, + "key": "aws-sigv4-bedrock-blocking", + "rank_score": 0.39991307258605957, + "text": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed. aws-smithy-runtime-api required as a companion to supply Identity. (context: Implementing BedrockProvider for Kimetsu with blocking reqwest + SigV4 signing, no tokio/aws-sdk)" + }, + { + "ce": 0.031776320189237595, + "key": "remote-mcp-host-wiring", + "rank_score": 0.41308078169822693, + "text": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal. Derive a stable repo id from the git remote: strip `.git`, scheme (`://`), and `user@`, then map non-alphanumerics to '-' and collapse — so both https://github.com/org/repo.git and git@github.com:org/repo.git -> `github-com-org-repo`. Remote install writes ONLY the MCP entry + instructions (no local hooks — the brain is on the server). Codex/Pi don't get --remote (no remote-MCP / no MCP). (context: R2: implementing `kimetsu plugin install --remote` to wire a host at a kimetsu-remote HTTP MCP server.)" + }, + { + "ce": 0.00006874215614516288, + "key": "mcp-schema-validation", + "rank_score": 0.40372994542121887, + "text": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array — omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error. Use `serde(default)` for optional fields. When adding a new tool, check the schema in the tools/list response manually by piping a JSON-RPC tools/list request to `./kimetsu mcp`. (context: Kimetsu MCP tool schema — required field validation.)" + } + ], + "delivered": [ + "mcp-tool-naming" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7675978541374207 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "MCP response path uses backslashes and the host rejects it", + "relevant": [ + "mcp-transcript-paths" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9998076558113098, + "key": "mcp-transcript-paths", + "rank_score": 0.9549957513809204, + "text": "project:fact - [tags: mcp transcript paths kimetsu hooks runs] kimetsu writes run transcripts to `/.kimetsu/runs//`. The post-session hook reads the latest run's transcript to trigger memory harvest. On Windows, the path uses backslashes internally but the MCP JSON must use forward slashes or the host may reject path-type arguments. `std::path::Path::display()` produces backslashes on Windows — use `.to_string_lossy().replace('\\\\', \"/\")` when serializing paths for MCP protocol. The transcript path is included in the `kimetsu_brain_context` response under the `run_dir` field for the distiller's reference. (context: Kimetsu transcript path handling in MCP responses on Windows.)" + }, + { + "ce": 0.19614966213703156, + "key": "mcp-tool-naming", + "rank_score": 0.5857377648353577, + "text": "project:fact - [tags: mcp tool naming convention kimetsu] MCP tool names must be valid identifiers for all host agents. Claude Code restricts tool names to `[a-zA-Z0-9_-]` and max 64 chars. Use `snake_case` (kimetsu_brain_context, kimetsu_brain_record) — hyphen is technically allowed but some hosts reject it. Avoid dots (not allowed). Namespace with a prefix (`kimetsu_brain_`) to prevent collisions with other MCP servers. When a tool name changes, update ALL host config files (`.mcp.json`, `openclaw.json`, skill markdown) — mismatched names cause silent failures where the host skips the tool. (context: Kimetsu MCP tool naming convention enforcement.)" + }, + { + "ce": 0.015124653466045856, + "key": "mcp-schema-validation", + "rank_score": 0.527317225933075, + "text": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array — omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error. Use `serde(default)` for optional fields. When adding a new tool, check the schema in the tools/list response manually by piping a JSON-RPC tools/list request to `./kimetsu mcp`. (context: Kimetsu MCP tool schema — required field validation.)" + }, + { + "ce": 0.03599695488810539, + "key": "mcp-stdout-protocol", + "rank_score": 0.5127547979354858, + "text": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr. `println!` in the request handler is forbidden. Use `eprintln!` or `tracing` with a stderr subscriber. In tests of the MCP server, capture stdout as bytes and validate it parses as JSON-Lines. When debugging, set `KIMETSU_LOG=debug` which writes to stderr only. (context: Kimetsu MCP server stdout protocol hygiene.)" + }, + { + "ce": 0.042481888085603714, + "key": "pi-openclaw-extension-api", + "rank_score": 0.48410454392433167, + "text": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`. External commands use `pi.exec()` but `node:child_process` spawn also works. Pi has NO MCP so Kimetsu integrates via TS extension + SKILL.md only. (context: Implementing Pi host target for Kimetsu plugin install/status/uninstall system.)\n\nAlso: [tags: kimetsu host-integration pi openclaw bridge] When integrating Kimetsu with an external host agent (Pi, OpenClaw, etc.), VERIFY the host's real plugin/extension API against its actual repo before writing embedded assets — docs-from-memory are frequently wrong. Concretely corrected during v1.0: Pi uses a default-export factory `export default function(pi)` (not `defineExtension`) with lifecycle events `session_start`/`agent_end`/`session_shutdown`; OpenClaw plugin entry is `index.ts` via `definePluginEntry` from `openclaw/plugin-sdk/plugin-entry` + an `openclaw.plugin.json` manifest, with snake_case hook events `agent_turn_prepare`/`agent_end`/`session_end` (NOT colon-delimited). Always make the embedded hook shell-out a silent no-op if the `kimetsu` binary isn't on PATH so a wrong guess never breaks the host. (context: Adding Pi + OpenClaw as BridgeTarget hosts in v1.0.0; the inferred extension/plugin APIs from docs were wrong and had to be corrected against the real repos.)" + }, + { + "ce": 0.007831564173102379, + "key": "kimetsu-write-tools-gate", + "rank_score": 0.48132529854774475, + "text": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level — disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients. The feature was introduced to prevent malicious prompts from poisoning the brain. (context: Kimetsu write-tools gate — config-driven security for remote deployments.)" + } + ], + "delivered": [ + "mcp-transcript-paths" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.770917534828186 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "AWS credentials not found — which env var does kimetsu read for Bedrock?", + "relevant": [ + "aws-credentials-chain" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999332427978516, + "key": "aws-credentials-chain", + "rank_score": 0.9549957513809204, + "text": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually. On Windows, `~/.aws` is `%USERPROFILE%\\.aws` — `std::env::var(\"USERPROFILE\")` to get the path since `~` expansion is shell-level. (context: Kimetsu Bedrock provider credential resolution.)" + }, + { + "ce": 0.9983052015304565, + "key": "bedrock-kimetsu-provider", + "rank_score": 0.7657347321510315, + "text": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env. Wire \"bedrock\" into BOTH pipeline.rs provider matches AND the distiller (normalize_distiller_provider + instantiation); the distiller is configured independently so agent-on-Bedrock + harvester-on-direct-Claude works for free. Sign and send the SAME payload bytes; test signing determinism with a fixed SystemTime. (context: Workstream A: adding AWS Bedrock as a provider for the agent + auto-harvester in v1.0.0.)" + }, + { + "ce": 0.999756395816803, + "key": "aws-region-resolution", + "rank_score": 0.7297416925430298, + "text": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time. For cross-region inference (e.g. us-west-2 for Claude Opus), set `AWS_REGION=us-west-2`; do NOT rely on the Bedrock endpoint prefix being region-agnostic. (context: Kimetsu Bedrock provider region configuration.)" + }, + { + "ce": 0.7727437615394592, + "key": "aws-sigv4-bedrock-blocking", + "rank_score": 0.6676511168479919, + "text": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed. aws-smithy-runtime-api required as a companion to supply Identity. (context: Implementing BedrockProvider for Kimetsu with blocking reqwest + SigV4 signing, no tokio/aws-sdk)" + }, + { + "ce": 0.7038406133651733, + "key": "aws-instance-metadata", + "rank_score": 0.6235418915748596, + "text": "project:fact - [tags: aws imds instance-metadata ec2 token] The AWS Instance Metadata Service v2 (IMDSv2) requires a session token: PUT `http://169.254.169.254/latest/api/token` with `X-aws-ec2-metadata-token-ttl-seconds: 21600` to get a token, then GET metadata with `X-aws-ec2-metadata-token: `. IMDSv1 (no token) is disabled on hardened instances. The metadata endpoint is only reachable from within EC2 — a connection timeout means you're not on EC2. Set a short connect timeout (200ms) when probing for the metadata service to avoid slow startup on non-EC2 hosts. (context: Kimetsu Bedrock provider — EC2 instance role credential fallback.)" + }, + { + "ce": 0.684673547744751, + "key": "aws-retry-throttling", + "rank_score": 0.5688364505767822, + "text": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with ±25% jitter. Do NOT retry `ValidationException` or `AccessDeniedException` — these are permanent errors. `ModelStreamErrorException` during streaming may be retryable. Log the `x-amzn-requestid` header from failed responses for AWS support debugging. (context: Kimetsu Bedrock provider retry logic.)" + } + ], + "delivered": [ + "aws-credentials-chain", + "aws-region-resolution", + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8450364470481873 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "Bedrock InvokeModel fails because the region is not configured", + "relevant": [ + "aws-region-resolution" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9998329877853394, + "key": "aws-region-resolution", + "rank_score": 0.9549956917762756, + "text": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time. For cross-region inference (e.g. us-west-2 for Claude Opus), set `AWS_REGION=us-west-2`; do NOT rely on the Bedrock endpoint prefix being region-agnostic. (context: Kimetsu Bedrock provider region configuration.)" + }, + { + "ce": 0.7683040499687195, + "key": "aws-sigv4-bedrock-blocking", + "rank_score": 0.6501534581184387, + "text": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed. aws-smithy-runtime-api required as a companion to supply Identity. (context: Implementing BedrockProvider for Kimetsu with blocking reqwest + SigV4 signing, no tokio/aws-sdk)" + }, + { + "ce": 0.871902346611023, + "key": "bedrock-kimetsu-provider", + "rank_score": 0.6456379890441895, + "text": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env. Wire \"bedrock\" into BOTH pipeline.rs provider matches AND the distiller (normalize_distiller_provider + instantiation); the distiller is configured independently so agent-on-Bedrock + harvester-on-direct-Claude works for free. Sign and send the SAME payload bytes; test signing determinism with a fixed SystemTime. (context: Workstream A: adding AWS Bedrock as a provider for the agent + auto-harvester in v1.0.0.)" + }, + { + "ce": 0.00031894349376671016, + "key": "kimetsu-distiller-config", + "rank_score": 0.4960768222808838, + "text": "project:fact - [tags: kimetsu distiller harvest config provider] The kimetsu distiller (auto-harvester) uses a SEPARATE provider configuration from the main agent: `distiller.provider`, `distiller.model`, `distiller.api_key`. This allows running the agent on an expensive model (Claude Opus) while harvesting with a cheap model (Claude Haiku). If `distiller.provider` is not set, it inherits `provider`. The distiller runs as a background task triggered by the post-session hook; it reads the session transcript and emits `kimetsu_brain_record` calls. Distiller timeouts are longer (300s) than normal tool calls (60s) because transcript processing can be slow. (context: Kimetsu distiller provider configuration — agent vs harvester model separation.)" + }, + { + "ce": 0.12774130702018738, + "key": "aws-retry-throttling", + "rank_score": 0.48891299962997437, + "text": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with ±25% jitter. Do NOT retry `ValidationException` or `AccessDeniedException` — these are permanent errors. `ModelStreamErrorException` during streaming may be retryable. Log the `x-amzn-requestid` header from failed responses for AWS support debugging. (context: Kimetsu Bedrock provider retry logic.)" + }, + { + "ce": 0.00012580808834172785, + "key": "init-project-git-boundary", + "rank_score": 0.43598252534866333, + "text": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain. (context: QQ3 — kimetsu setup integration test failed because init_project climbed git tree to real ~/.kimetsu instead of temp workspace)" + } + ], + "delivered": [ + "aws-region-resolution", + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.862574577331543 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "how do I handle ThrottlingException from Bedrock with exponential backoff?", + "relevant": [ + "aws-retry-throttling" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9984448552131653, + "key": "aws-retry-throttling", + "rank_score": 0.9549956917762756, + "text": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with ±25% jitter. Do NOT retry `ValidationException` or `AccessDeniedException` — these are permanent errors. `ModelStreamErrorException` during streaming may be retryable. Log the `x-amzn-requestid` header from failed responses for AWS support debugging. (context: Kimetsu Bedrock provider retry logic.)" + }, + { + "ce": 0.0067451042123138905, + "key": "aws-region-resolution", + "rank_score": 0.4592760503292084, + "text": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time. For cross-region inference (e.g. us-west-2 for Claude Opus), set `AWS_REGION=us-west-2`; do NOT rely on the Bedrock endpoint prefix being region-agnostic. (context: Kimetsu Bedrock provider region configuration.)" + }, + { + "ce": 0.024876520037651062, + "key": "bedrock-kimetsu-provider", + "rank_score": 0.43889063596725464, + "text": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env. Wire \"bedrock\" into BOTH pipeline.rs provider matches AND the distiller (normalize_distiller_provider + instantiation); the distiller is configured independently so agent-on-Bedrock + harvester-on-direct-Claude works for free. Sign and send the SAME payload bytes; test signing determinism with a fixed SystemTime. (context: Workstream A: adding AWS Bedrock as a provider for the agent + auto-harvester in v1.0.0.)" + }, + { + "ce": 0.00023748895910102874, + "key": "cargo-dev-dep-leak", + "rank_score": 0.3988249599933624, + "text": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates. Run `cargo tree --features ` to trace which crate activated an unexpected feature. (context: Kimetsu testing infra — a dev-dep was activating the embeddings feature in non-test builds.)" + }, + { + "ce": 0.00018132003606297076, + "key": "tokio-shutdown-ordering", + "rank_score": 0.39995965361595154, + "text": "project:fact - [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries — the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks. `axum::Server::with_graceful_shutdown` handles steps 1-2; you must handle 3-5 manually. (context: Kimetsu remote server graceful shutdown implementation.)" + }, + { + "ce": 0.00042892908095382154, + "key": "harbor-terminal-bench-subprocess-isolation", + "rank_score": 0.36925438046455383, + "text": "project:fact - [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd). Worker re-derives auth internally from .env so the OAuth token never lands in argv; it writes {run,grade} JSON the parent reads back. One Harbor invocation per process always works (baseline-alone passed). (context: kbench multi-trial sweeps crashed on every trial after the 1st; diagnosed as Harbor/pyiceberg os.getcwd staleness on WSL2.)" + } + ], + "delivered": [ + "aws-retry-throttling" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8757925629615784 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "generating a presigned S3 URL for brain export without exposing credentials", + "relevant": [ + "aws-presigned-urls" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999775886535645, + "key": "aws-presigned-urls", + "rank_score": 0.9549956321716309, + "text": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time — clock skew > 15 minutes causes `RequestTimeTooSkewed`. kimetsu could use presigned URLs to serve brain exports from S3 without exposing credentials to the client. (context: Kimetsu potential S3 export feature — presigned URL generation.)" + }, + { + "ce": 0.00008046287257457152, + "key": "testing-fixture-drift", + "rank_score": 0.40383538603782654, + "text": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code. For kimetsu, `EvalFixture::from_memories(memories)` constructs a dataset from the exported format — use it in tests instead of hardcoded JSON. Tag fixture files with the schema version they were generated against in a comment. (context: Kimetsu eval fixture drift after schema migration.)" + }, + { + "ce": 0.0041783773340284824, + "key": "remote-ingest-split-roots", + "rank_score": 0.37699347734451294, + "text": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races. Re-enable kimetsu_brain_ingest_repo in the tool allowlist only when ingest is configured, and INTERCEPT that tools/call in the remote handler (clone+ingest_repo_at_root) before the normal dispatch (which would walk the wrong dir). Hermetic test: git init a temp repo, register url=local path, ingest, then context retrieves the file capsule via FTS (noop embedder). (context: R3c: server-side ingest for kimetsu-remote — cloning repos so file-capsule retrieval works without a local checkout.)" + }, + { + "ce": 0.00024498492712154984, + "key": "aws-credentials-chain", + "rank_score": 0.3629988133907318, + "text": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually. On Windows, `~/.aws` is `%USERPROFILE%\\.aws` — `std::env::var(\"USERPROFILE\")` to get the path since `~` expansion is shell-level. (context: Kimetsu Bedrock provider credential resolution.)" + }, + { + "ce": 0.004011558368802071, + "key": "bedrock-kimetsu-provider", + "rank_score": 0.3684987425804138, + "text": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env. Wire \"bedrock\" into BOTH pipeline.rs provider matches AND the distiller (normalize_distiller_provider + instantiation); the distiller is configured independently so agent-on-Bedrock + harvester-on-direct-Claude works for free. Sign and send the SAME payload bytes; test signing determinism with a fixed SystemTime. (context: Workstream A: adding AWS Bedrock as a provider for the agent + auto-harvester in v1.0.0.)" + }, + { + "ce": 0.0000689724984113127, + "key": "cargo-build-script-rerun", + "rank_score": 0.3536602258682251, + "text": "project:fact - [tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory. If the build script generates code from a schema file, emit `rerun-if-changed=schema.json`. If there are NO inputs (e.g. the script only inspects env vars), emit `cargo:rerun-if-changed=` with an empty string to suppress re-runs entirely. Missing this directive is the most common cause of unexpectedly slow incremental builds. (context: kimetsu-cli build.rs for embedding version stamps.)" + } + ], + "delivered": [ + "aws-presigned-urls" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.77601158618927 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "IMDSv2 token required for instance metadata — PUT before GET", + "relevant": [ + "aws-instance-metadata" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.0028854471165686846, + "key": "harbor-terminal-bench-subprocess-isolation", + "rank_score": 0.38025200366973877, + "text": "project:fact - [2026-09-05] [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd). Worker re-derives auth internally from .env so the OAuth token never lands in argv; it writes {run,grade} JSON the parent reads back. One Harbor invocation per process always works (baseline-alone passed). (context: kbench multi-trial sweeps crashed on every trial after the 1st; diagnosed as Harbor/pyiceberg os.getcwd staleness on WSL2.)" + }, + { + "ce": 0.002049950184300542, + "key": "windows-junctions-vs-symlinks", + "rank_score": 0.4151405096054077, + "text": "project:fact - [2026-09-05] [tags: windows junctions symlinks rust std::fs] On Windows, directory junctions (NTFS reparse points) behave like symlinks for directory traversal but `std::fs::symlink_metadata` returns `FileType::is_symlink() = false` for junctions (only true for regular symlinks). Use `std::fs::read_link` — it succeeds for both junction and symlink. `walkdir` crate's `follow_links` follows both, but its `is_symlink()` method correctly reports only actual symlinks. Creating symlinks requires SeCreateSymbolicLinkPrivilege (admin or Developer Mode). Creating junctions requires no special privilege. Use junctions for internal tooling that doesn't need to cross volumes. (context: Kimetsu path handling for brain symlink detection on Windows.)" + }, + { + "ce": 0.006584345828741789, + "key": "http-retry-idempotency", + "rank_score": 0.4602915346622467, + "text": "project:fact - [2026-09-05] [tags: http retry idempotency post put reqwest] Only retry idempotent requests automatically. GET, HEAD, PUT, DELETE are idempotent. POST is NOT — retrying a POST may create duplicate resources. For LLM API calls (POST), implement retry with idempotency keys: include a stable `X-Idempotency-Key: ` header; the provider deduplicates. For transient 429 (rate limit) responses, back off with jitter: `min(base * 2^attempt, cap) + rand(0, base)`. For 5xx, retry at most 3 times. Never retry on 4xx (except 429). In kimetsu, retry logic lives in the provider layer, not the distiller. (context: Kimetsu LLM provider retry strategy.)" + }, + { + "ce": 0.030679162591695786, + "key": "aws-credentials-chain", + "rank_score": 0.5658632516860962, + "text": "project:fact - [2026-09-05] [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually. On Windows, `~/.aws` is `%USERPROFILE%\\.aws` — `std::env::var(\"USERPROFILE\")` to get the path since `~` expansion is shell-level. (context: Kimetsu Bedrock provider credential resolution.)" + }, + { + "ce": 0.9999799728393555, + "key": "aws-instance-metadata", + "rank_score": 0.9549956321716309, + "text": "project:fact - [2026-09-05] [tags: aws imds instance-metadata ec2 token] The AWS Instance Metadata Service v2 (IMDSv2) requires a session token: PUT `http://169.254.169.254/latest/api/token` with `X-aws-ec2-metadata-token-ttl-seconds: 21600` to get a token, then GET metadata with `X-aws-ec2-metadata-token: `. IMDSv1 (no token) is disabled on hardened instances. The metadata endpoint is only reachable from within EC2 — a connection timeout means you're not on EC2. Set a short connect timeout (200ms) when probing for the metadata service to avoid slow startup on non-EC2 hosts. (context: Kimetsu Bedrock provider — EC2 instance role credential fallback.)" + }, + { + "ce": 0.0021250429563224316, + "key": "kimetsu-query-stemming", + "rank_score": 0.4280453324317932, + "text": "project:fact - [2026-09-05] [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression. The lexical floor (`min_lexical_coverage`) requires at least N stemmed query tokens to match in any retrieved document — this prevents high-semantic-score but lexically-unrelated documents from dominating. Stemming is applied only when the query has >= 3 tokens; short queries skip it. (context: Kimetsu retrieval — query-side stemming implementation.)" + } + ], + "delivered": [ + "aws-instance-metadata" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8567245602607727 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "Cargo cache key strategy for GitHub Actions to avoid toolchain version collisions", + "relevant": [ + "ci-cache-keys" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999439716339111, + "key": "ci-cache-keys", + "rank_score": 0.9549955725669861, + "text": "project:fact - [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key — macOS and Windows have incompatible artifact formats. Separate the registry cache from the build cache: the registry (downloaded crates) changes rarely, the build cache changes every push. Bust the build cache on major dependency changes by adding a manual cache version suffix to the key. (context: Kimetsu CI — cache invalidation strategy.)" + }, + { + "ce": 0.036063987761735916, + "key": "cargo-msrv", + "rank_score": 0.5181794166564941, + "text": "project:fact - [tags: cargo rust msrv edition compatibility] Set `rust-version` in each `Cargo.toml` to declare the minimum supported Rust version (MSRV). Cargo enforces this with `--check`: `cargo check` fails if the toolchain is older than `rust-version`. Keep MSRV as old as your oldest supported deployment target. When bumping MSRV, update the CI matrix and the workspace root. Common trap: a transitive dep bumps its MSRV, pulling yours up silently — check with `cargo msrv` (cargo-msrv crate) or `cargo tree -e features | grep msrv`. Edition 2021 requires Rust >= 1.56.0. (context: Kimetsu workspace MSRV policy — ensuring it runs on the LTS toolchain.)" + }, + { + "ce": 0.0003246871056035161, + "key": "ci-artifact-retention", + "rank_score": 0.4767911434173584, + "text": "project:fact - [tags: ci github-actions artifacts retention benchmark] GitHub Actions artifacts are retained for 90 days (default). For benchmark results, use `actions/upload-artifact` with `retention-days: 365` for long-term tracking. The free tier has 500MB storage — per-combo JSON files from kimetsu bench (each ~60KB) add up fast if you upload them on every push. Upload only the summary.md. For regression detection, compare the current run's MRR against the artifact from the last green main build — fetch it with the `actions/download-artifact` action. (context: Kimetsu CI benchmark result tracking.)" + }, + { + "ce": 0.0011882992694154382, + "key": "ci-matrix-explosion", + "rank_score": 0.47376322746276855, + "text": "project:fact - [tags: ci github-actions matrix jobs resources] A CI matrix combining OS (3) x Rust toolchain (3) x features (2) = 18 jobs. Each spawns a runner; at $0.008/min for Ubuntu and $0.016/min for Windows, a 10-minute build costs $2.40 per push. Reduce: test the full matrix only on PRs to main; on feature branches, test only Linux+stable. Use `fail-fast: false` to see all failures, not just the first. Combine related checks (clippy + test) in one job when they share build artifacts. For Windows-specific tests, run only the OS-specific job to reduce cost. (context: Kimetsu CI matrix cost optimization.)" + }, + { + "ce": 0.009679202921688557, + "key": "cargo-target-dir-sharing", + "rank_score": 0.46041083335876465, + "text": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps — use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination. (context: Kimetsu development on Windows with Windows Defender causing intermittent link failures.)" + }, + { + "ce": 0.00013810611562803388, + "key": "http-retry-idempotency", + "rank_score": 0.42475709319114685, + "text": "project:fact - [tags: http retry idempotency post put reqwest] Only retry idempotent requests automatically. GET, HEAD, PUT, DELETE are idempotent. POST is NOT — retrying a POST may create duplicate resources. For LLM API calls (POST), implement retry with idempotency keys: include a stable `X-Idempotency-Key: ` header; the provider deduplicates. For transient 429 (rate limit) responses, back off with jitter: `min(base * 2^attempt, cap) + rand(0, base)`. For 5xx, retry at most 3 times. Never retry on 4xx (except 429). In kimetsu, retry logic lives in the provider layer, not the distiller. (context: Kimetsu LLM provider retry strategy.)" + } + ], + "delivered": [ + "ci-cache-keys" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8275420069694519 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "CI matrix has 18 jobs and costs too much — how do I reduce it?", + "relevant": [ + "ci-matrix-explosion" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.99962317943573, + "key": "ci-matrix-explosion", + "rank_score": 0.9549955725669861, + "text": "project:fact - [tags: ci github-actions matrix jobs resources] A CI matrix combining OS (3) x Rust toolchain (3) x features (2) = 18 jobs. Each spawns a runner; at $0.008/min for Ubuntu and $0.016/min for Windows, a 10-minute build costs $2.40 per push. Reduce: test the full matrix only on PRs to main; on feature branches, test only Linux+stable. Use `fail-fast: false` to see all failures, not just the first. Combine related checks (clippy + test) in one job when they share build artifacts. For Windows-specific tests, run only the OS-specific job to reduce cost. (context: Kimetsu CI matrix cost optimization.)" + }, + { + "ce": 0.0003345872100908309, + "key": "cargo-msrv", + "rank_score": 0.44084805250167847, + "text": "project:fact - [tags: cargo rust msrv edition compatibility] Set `rust-version` in each `Cargo.toml` to declare the minimum supported Rust version (MSRV). Cargo enforces this with `--check`: `cargo check` fails if the toolchain is older than `rust-version`. Keep MSRV as old as your oldest supported deployment target. When bumping MSRV, update the CI matrix and the workspace root. Common trap: a transitive dep bumps its MSRV, pulling yours up silently — check with `cargo msrv` (cargo-msrv crate) or `cargo tree -e features | grep msrv`. Edition 2021 requires Rust >= 1.56.0. (context: Kimetsu workspace MSRV policy — ensuring it runs on the LTS toolchain.)" + }, + { + "ce": 0.000057921894040191546, + "key": "ci-cache-keys", + "rank_score": 0.42162227630615234, + "text": "project:fact - [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key — macOS and Windows have incompatible artifact formats. Separate the registry cache from the build cache: the registry (downloaded crates) changes rarely, the build cache changes every push. Bust the build cache on major dependency changes by adding a manual cache version suffix to the key. (context: Kimetsu CI — cache invalidation strategy.)" + }, + { + "ce": 0.00005775317185907625, + "key": "sqlite-wal-network-drive", + "rank_score": 0.3954841196537018, + "text": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db. Fallback: `PRAGMA journal_mode=DELETE;` is safe over SMB at the cost of lower concurrency. Detect network drives at startup with `GetFileAttributes` checking FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS or using `PathIsNetworkPath`. (context: Users running kimetsu with the brain database on a mapped network drive.)" + }, + { + "ce": 0.0000602840882493183, + "key": "sqlite-page-size", + "rank_score": 0.38837912678718567, + "text": "project:fact - [tags: sqlite page_size performance rusqlite] SQLite's default page_size is 4096 bytes. For a write-heavy brain database with large BLOB payloads (embedding vectors), raising page_size to 16384 reduces fragmentation and improves sequential scan throughput. `PRAGMA page_size = 16384;` must be set BEFORE the first table is created — changing it on an existing database requires a VACUUM afterward to rebuild all pages. Verify it took effect with `PRAGMA page_size;` after VACUUM. rusqlite's `Connection::open` runs no implicit PRAGMA, so set this in the connection init path. (context: Tuning the kimetsu brain SQLite schema for embedding vector storage.)" + }, + { + "ce": 0.00010831271356437355, + "key": "testing-serial-vs-parallel", + "rank_score": 0.3749147653579712, + "text": "project:fact - [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`). `cargo nextest` runs each test in a separate process by default, avoiding the problem entirely at the cost of longer startup time. For kimetsu, prefer nextest in CI and accept that `test_env_lock` exists only for `cargo test` compatibility. (context: Kimetsu test suite — env-var mutation in parallel tests.)" + } + ], + "delivered": [ + "ci-matrix-explosion" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7728597521781921 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "GitHub Actions secret accidentally printed in build logs", + "relevant": [ + "ci-secrets-masking" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9951270818710327, + "key": "ci-secrets-masking", + "rank_score": 0.9549955725669861, + "text": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output — but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable. Never reconstruct secrets from parts in step output. For kimetsu bench `--remote` CI runs, `KIMETSU_REMOTE_TOKEN` must be in the repository secrets, not in the workflow YAML. Use `${{ secrets.KIMETSU_REMOTE_TOKEN }}` in env — never `echo ${{ secrets.KIMETSU_REMOTE_TOKEN }}` in a run step. (context: Kimetsu CI remote benchmark — token handling.)" + }, + { + "ce": 0.01845650188624859, + "key": "ci-artifact-retention", + "rank_score": 0.7517378330230713, + "text": "project:fact - [tags: ci github-actions artifacts retention benchmark] GitHub Actions artifacts are retained for 90 days (default). For benchmark results, use `actions/upload-artifact` with `retention-days: 365` for long-term tracking. The free tier has 500MB storage — per-combo JSON files from kimetsu bench (each ~60KB) add up fast if you upload them on every push. Upload only the summary.md. For regression detection, compare the current run's MRR against the artifact from the last green main build — fetch it with the `actions/download-artifact` action. (context: Kimetsu CI benchmark result tracking.)" + }, + { + "ce": 0.03577777370810509, + "key": "ci-cache-keys", + "rank_score": 0.7215610146522522, + "text": "project:fact - [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key — macOS and Windows have incompatible artifact formats. Separate the registry cache from the build cache: the registry (downloaded crates) changes rarely, the build cache changes every push. Bust the build cache on major dependency changes by adding a manual cache version suffix to the key. (context: Kimetsu CI — cache invalidation strategy.)" + }, + { + "ce": 0.005168351344764233, + "key": "ci-matrix-explosion", + "rank_score": 0.6136413216590881, + "text": "project:fact - [tags: ci github-actions matrix jobs resources] A CI matrix combining OS (3) x Rust toolchain (3) x features (2) = 18 jobs. Each spawns a runner; at $0.008/min for Ubuntu and $0.016/min for Windows, a 10-minute build costs $2.40 per push. Reduce: test the full matrix only on PRs to main; on feature branches, test only Linux+stable. Use `fail-fast: false` to see all failures, not just the first. Combine related checks (clippy + test) in one job when they share build artifacts. For Windows-specific tests, run only the OS-specific job to reduce cost. (context: Kimetsu CI matrix cost optimization.)" + }, + { + "ce": 0.006116470322012901, + "key": "remote-mcp-host-wiring", + "rank_score": 0.5727620124816895, + "text": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal. Derive a stable repo id from the git remote: strip `.git`, scheme (`://`), and `user@`, then map non-alphanumerics to '-' and collapse — so both https://github.com/org/repo.git and git@github.com:org/repo.git -> `github-com-org-repo`. Remote install writes ONLY the MCP entry + instructions (no local hooks — the brain is on the server). Codex/Pi don't get --remote (no remote-MCP / no MCP). (context: R2: implementing `kimetsu plugin install --remote` to wire a host at a kimetsu-remote HTTP MCP server.)" + }, + { + "ce": 0.0006112937116995454, + "key": "mcp-stdout-protocol", + "rank_score": 0.5179863572120667, + "text": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr. `println!` in the request handler is forbidden. Use `eprintln!` or `tracing` with a stderr subscriber. In tests of the MCP server, capture stdout as bytes and validate it parses as JSON-Lines. When debugging, set `KIMETSU_LOG=debug` which writes to stderr only. (context: Kimetsu MCP server stdout protocol hygiene.)" + } + ], + "delivered": [ + "ci-secrets-masking" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7868114113807678 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "how long do GitHub Actions artifacts persist and what's the storage limit?", + "relevant": [ + "ci-artifact-retention" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9989684820175171, + "key": "ci-artifact-retention", + "rank_score": 0.9549955725669861, + "text": "project:fact - [tags: ci github-actions artifacts retention benchmark] GitHub Actions artifacts are retained for 90 days (default). For benchmark results, use `actions/upload-artifact` with `retention-days: 365` for long-term tracking. The free tier has 500MB storage — per-combo JSON files from kimetsu bench (each ~60KB) add up fast if you upload them on every push. Upload only the summary.md. For regression detection, compare the current run's MRR against the artifact from the last green main build — fetch it with the `actions/download-artifact` action. (context: Kimetsu CI benchmark result tracking.)" + }, + { + "ce": 0.00027382816188037395, + "key": "ci-cache-keys", + "rank_score": 0.6443716287612915, + "text": "project:fact - [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key — macOS and Windows have incompatible artifact formats. Separate the registry cache from the build cache: the registry (downloaded crates) changes rarely, the build cache changes every push. Bust the build cache on major dependency changes by adding a manual cache version suffix to the key. (context: Kimetsu CI — cache invalidation strategy.)" + }, + { + "ce": 0.00588421942666173, + "key": "ci-matrix-explosion", + "rank_score": 0.5516787171363831, + "text": "project:fact - [tags: ci github-actions matrix jobs resources] A CI matrix combining OS (3) x Rust toolchain (3) x features (2) = 18 jobs. Each spawns a runner; at $0.008/min for Ubuntu and $0.016/min for Windows, a 10-minute build costs $2.40 per push. Reduce: test the full matrix only on PRs to main; on feature branches, test only Linux+stable. Use `fail-fast: false` to see all failures, not just the first. Combine related checks (clippy + test) in one job when they share build artifacts. For Windows-specific tests, run only the OS-specific job to reduce cost. (context: Kimetsu CI matrix cost optimization.)" + }, + { + "ce": 0.00003191240830346942, + "key": "windows-registry-rust", + "rank_score": 0.5247061252593994, + "text": "project:fact - [tags: windows registry rust winreg read write] Reading and writing the Windows registry from Rust requires the `winreg` crate. Open a key with `RegKey::predef(HKEY_LOCAL_MACHINE).open_subkey_with_flags(path, KEY_READ)` — use `KEY_READ` for reads and `KEY_READ | KEY_WRITE` for writes (NOT `KEY_ALL_ACCESS`, which requires admin). To set a DWORD value: `key.set_value(\"LongPathsEnabled\", &1u32)`. Registry paths use backslash separators and are case-insensitive. Prefer reading env vars over registry for runtime config — registry reads are expensive (kernel transition) and inappropriate for hot paths. For kimetsu, registry access is limited to the `kimetsu doctor` check for long-path enablement. (context: Kimetsu doctor — checking LongPathsEnabled registry value on Windows.)" + }, + { + "ce": 0.001599345589056611, + "key": "ci-secrets-masking", + "rank_score": 0.5292024612426758, + "text": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output — but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable. Never reconstruct secrets from parts in step output. For kimetsu bench `--remote` CI runs, `KIMETSU_REMOTE_TOKEN` must be in the repository secrets, not in the workflow YAML. Use `${{ secrets.KIMETSU_REMOTE_TOKEN }}` in env — never `echo ${{ secrets.KIMETSU_REMOTE_TOKEN }}` in a run step. (context: Kimetsu CI remote benchmark — token handling.)" + }, + { + "ce": 0.00003634988024714403, + "key": "sqlite-json1-extract", + "rank_score": 0.4897564649581909, + "text": "project:fact - [tags: sqlite json1 json_extract rusqlite] SQLite's json1 extension (built in since 3.38.0) lets you index and query JSONB columns with `json_extract(col, '$.field')`. To create a partial index over a JSON field: `CREATE INDEX idx ON memories (json_extract(metadata, '$.scope')) WHERE json_extract(metadata, '$.scope') IS NOT NULL;`. Use `json_each` for array fields. On older SQLite builds (rusqlite links whatever the system provides), check for json1 with `SELECT json('{}');` — an error means it's absent. Always prefer column storage over JSON blobs for frequently queried fields. (context: Kimetsu brain querying metadata scopes without migrating a separate column.)" + } + ], + "delivered": [ + "ci-artifact-retention" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8645972609519958 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "timing-based test flake in CI — quarantine or fix?", + "relevant": [ + "ci-flaky-quarantine" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9940990209579468, + "key": "ci-flaky-quarantine", + "rank_score": 0.9549955129623413, + "text": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal — a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output. NEVER let a flaky test gate the merge queue. For kimetsu timing-based tests (`test_gc_old_runs_deletes_ancient`), apply `#[cfg_attr(ci, ignore)]` and run only in a dedicated slow-CI job. (context: Kimetsu CI flaky test policy.)" + }, + { + "ce": 0.7894570231437683, + "key": "testing-time-dependent-flakes", + "rank_score": 0.7547785043716431, + "text": "project:fact - [tags: testing time flaky clock mock rust] Tests that depend on wall-clock time are inherently flaky under load (slow CI runners, GC pauses). Abstract time behind a trait (`Clock: Fn() -> SystemTime`) injected at construction, and supply a fake in tests. For tests checking that something happened \"within N seconds\", use a generous multiple of the expected duration (10x is not unreasonable for CI). `std::thread::sleep` in tests is a smell — prefer channel synchronization or a condvar instead of timing-based waits. If you must use sleep, set `KIMETSU_TEST_TIMEOUT_SCALE` to stretch timeouts in slow environments. (context: Kimetsu GC and TTL tests — time-dependent flakes on loaded CI.)" + }, + { + "ce": 0.0219721682369709, + "key": "testing-property-tests", + "rank_score": 0.5650542974472046, + "text": "project:fact - [tags: testing property-based proptest quickcheck rust] Property-based tests (proptest, quickcheck) find edge cases that example-based tests miss. For kimetsu's memory text normalization, proptest found that zero-width joiner characters and right-to-left marks caused hash collisions. Run proptest with `PROPTEST_CASES=10000` in CI for thorough coverage. Shrinking: when proptest finds a failure, it automatically shrinks the input to the minimal failing case — read the `Minimized failure` output, not the original random input. Use `prop_assume!` to skip inputs that violate preconditions rather than `if/return`. (context: Kimetsu brain text normalization — property test for dedup hash stability.)" + }, + { + "ce": 0.00978840421885252, + "key": "git-hooks-bypass", + "rank_score": 0.45789799094200134, + "text": "project:fact - [tags: git hooks bypass pre-commit skip] `git commit --no-verify` skips ALL hooks (pre-commit and commit-msg). Never use this in shared team repos where hooks enforce quality gates (lint, tests, memory harvest). Instead, fix the failing hook. If the hook itself is broken, fix the hook script. For emergency commits where hooks aren't relevant (e.g. updating a gitignore to untrack already-committed files), document the `--no-verify` use in the commit message. In CI, hooks run only if explicitly invoked — `git commit` in a CI pipeline with no hooks configured does nothing for quality enforcement. (context: Kimetsu pre-commit hook enforcing memory harvest.)" + }, + { + "ce": 0.0010033308062702417, + "key": "testing-fixture-drift", + "rank_score": 0.4575827717781067, + "text": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code. For kimetsu, `EvalFixture::from_memories(memories)` constructs a dataset from the exported format — use it in tests instead of hardcoded JSON. Tag fixture files with the schema version they were generated against in a comment. (context: Kimetsu eval fixture drift after schema migration.)" + }, + { + "ce": 0.1938311606645584, + "key": "testing-temp-dirs-ci", + "rank_score": 0.41792017221450806, + "text": "project:fact - [tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure. On Windows, `env::temp_dir()` returns `C:\\Users\\\\AppData\\Local\\Temp` — ensure the test binary has write permissions there. Avoid using the workspace root as a temp dir — tests should never write to the source tree. (context: Kimetsu test infrastructure — temp directory discipline.)" + } + ], + "delivered": [ + "ci-flaky-quarantine", + "testing-time-dependent-flakes" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8109418749809265 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "kimetsu doctor says the MCP server is running — how do I stop it before an update?", + "relevant": [ + "kimetsu-daemon-lifecycle" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.36596041917800903, + "key": "remote-mcp-host-wiring", + "rank_score": 0.550395667552948, + "text": "project:fact - [2026-09-05] [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal. Derive a stable repo id from the git remote: strip `.git`, scheme (`://`), and `user@`, then map non-alphanumerics to '-' and collapse — so both https://github.com/org/repo.git and git@github.com:org/repo.git -> `github-com-org-repo`. Remote install writes ONLY the MCP entry + instructions (no local hooks — the brain is on the server). Codex/Pi don't get --remote (no remote-MCP / no MCP). (context: R2: implementing `kimetsu plugin install --remote` to wire a host at a kimetsu-remote HTTP MCP server.)" + }, + { + "ce": 0.24280337989330292, + "key": "process-start-time-cross-platform", + "rank_score": 0.6845270991325378, + "text": "project:fact - [2026-09-05] [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path). Keep the skew decision logic in a pure function `assess_mcp_skew(servers, binary_mtime, binary_path) -> Outcome` so it can be unit-tested without any live OS state. (context: Q3 — kimetsu doctor version-skew check for stale MCP server processes)" + }, + { + "ce": 0.022639773786067963, + "key": "windows-registry-rust", + "rank_score": 0.5124743580818176, + "text": "project:fact - [2026-09-05] [tags: windows registry rust winreg read write] Reading and writing the Windows registry from Rust requires the `winreg` crate. Open a key with `RegKey::predef(HKEY_LOCAL_MACHINE).open_subkey_with_flags(path, KEY_READ)` — use `KEY_READ` for reads and `KEY_READ | KEY_WRITE` for writes (NOT `KEY_ALL_ACCESS`, which requires admin). To set a DWORD value: `key.set_value(\"LongPathsEnabled\", &1u32)`. Registry paths use backslash separators and are case-insensitive. Prefer reading env vars over registry for runtime config — registry reads are expensive (kernel transition) and inappropriate for hot paths. For kimetsu, registry access is limited to the `kimetsu doctor` check for long-path enablement. (context: Kimetsu doctor — checking LongPathsEnabled registry value on Windows.)" + }, + { + "ce": 0.0048812138848006725, + "key": "tokio-shutdown-ordering", + "rank_score": 0.56375652551651, + "text": "project:fact - [2026-09-05] [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries — the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks. `axum::Server::with_graceful_shutdown` handles steps 1-2; you must handle 3-5 manually. (context: Kimetsu remote server graceful shutdown implementation.)" + }, + { + "ce": 0.9644265174865723, + "key": "mcp-env-propagation", + "rank_score": 0.6119192242622375, + "text": "project:fact - [2026-09-05] [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment — changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate. For kimetsu hooks (pre-commit, post-commit), the hook script inherits the shell's env at hook invocation time, not the server's. If `KIMETSU_BRAIN_DIR` needs to vary per project, set it in the project's `.env` file and source it in the hook script. (context: Kimetsu env propagation from hooks to MCP server.)" + }, + { + "ce": 0.9999070167541504, + "key": "kimetsu-daemon-lifecycle", + "rank_score": 0.9549955129623413, + "text": "project:fact - [2026-09-05] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required. The server PID is not stored anywhere; use `kimetsu doctor` to enumerate running MCP server processes via OS APIs. On Windows, the server binary may be locked by AV after first launch — `kimetsu update` must stop all running server processes before replacing the binary. (context: Kimetsu daemon lifecycle — process management for updates.)" + } + ], + "delivered": [ + "kimetsu-daemon-lifecycle", + "mcp-env-propagation", + "remote-mcp-host-wiring" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7928964495658875 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "noise capsules consuming token budget without contributing retrieval signal", + "relevant": [ + "kimetsu-capsule-budgets" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999643564224243, + "key": "kimetsu-capsule-budgets", + "rank_score": 0.9549955129623413, + "text": "project:fact - [tags: kimetsu capsule tokens budget retrieval] kimetsu retrieval enforces a token budget per capsule type: memory capsules are capped at 6000 tokens total (across all retrieved memories), file capsules at 3000 tokens. When a memory is large and would exceed the budget, it is truncated at a sentence boundary. The budget is enforced AFTER reranking — reranking may reorder results so that a truncated high-ranked memory displaces a full lower-ranked one. `noise_caps` in the bench output counts capsules that scored below the noise floor — they consume budget without contributing signal. Lower noise_caps = tighter retrieval. (context: Kimetsu capsule budget enforcement and noise floor interaction.)" + }, + { + "ce": 0.0013568397844210267, + "key": "kimetsu-proactive-hooks", + "rank_score": 0.4345961809158325, + "text": "project:fact - [tags: kimetsu proactive hooks context injection] kimetsu's proactive context injection runs before each agent turn (pre-turn hook) and injects relevant memories into the system prompt prefix. The hook invocation adds latency to the first token: embedding inference + vector search + reranking + context formatting. On a cold start, this can be 1-3 seconds. The hook is optional — disable with `KIMETSU_PROACTIVE=0`. The semantic floor (min cosine similarity) filters noise capsules before injection; setting the floor too low injects irrelevant memories and wastes context window tokens. The proactive hook does NOT trigger the distiller — that runs post-session only. (context: Kimetsu proactive context injection — latency and floor tuning.)" + }, + { + "ce": 0.0005401356029324234, + "key": "remote-ingest-split-roots", + "rank_score": 0.37908387184143066, + "text": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races. Re-enable kimetsu_brain_ingest_repo in the tool allowlist only when ingest is configured, and INTERCEPT that tools/call in the remote handler (clone+ingest_repo_at_root) before the normal dispatch (which would walk the wrong dir). Hermetic test: git init a temp repo, register url=local path, ingest, then context retrieves the file capsule via FTS (noop embedder). (context: R3c: server-side ingest for kimetsu-remote — cloning repos so file-capsule retrieval works without a local checkout.)" + }, + { + "ce": 0.00013760750880464911, + "key": "sqlite-fts5-tokenizer", + "rank_score": 0.3649887442588806, + "text": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon. If you switch tokenizers on an existing FTS5 table, you MUST rebuild the shadow tables: `INSERT INTO tbl(tbl) VALUES('rebuild');` — a schema-only change leaves the inverted index unusable. The `porter` stemmer is available as `tokenize='porter unicode61'` but aggressively strips suffixes and hurts precision on technical terms. (context: Kimetsu brain FTS5 index tuning for Rust identifier retrieval.)" + }, + { + "ce": 0.0003033202374354005, + "key": "http-timeout-layering", + "rank_score": 0.3683795630931854, + "text": "project:fact - [tags: http reqwest timeout connect read total rust] reqwest has three distinct timeout knobs: `connect_timeout`, `read_timeout`, and `timeout` (total). They compose: if all three are set, the request fails at whichever fires first. For LLM API calls with streaming responses, `read_timeout` must be larger than the slowest expected token (often 30-60s) while `connect_timeout` can be tight (3-5s). `timeout` should be your SLA ceiling. If you set only `timeout`, a slow connect eats into the overall budget. For kimetsu-remote, set both `connect_timeout(5s)` and `timeout(120s)` — the LLM call is the bottleneck. (context: Kimetsu provider timeouts — request timing out during streaming.)" + }, + { + "ce": 0.00023590154887642711, + "key": "onnx-tokenizer-mismatch", + "rank_score": 0.3652462959289551, + "text": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly — specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings — cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo. Validate by checking a reference embedding against the HuggingFace Python output. (context: Kimetsu custom ONNX reranker loading — wrong tokenizer produced degraded retrieval.)" + } + ], + "delivered": [ + "kimetsu-capsule-budgets" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8133057355880737 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "kimetsu_brain_record writes to the wrong brain location — user vs project scope", + "relevant": [ + "kimetsu-memory-scopes" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9998672008514404, + "key": "kimetsu-memory-scopes", + "rank_score": 0.9549954533576965, + "text": "project:fact - [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available — if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope. The `kimetsu_brain_record` MCP tool inherits the scope from the server's launch context. When running kimetsu-remote, all memories are project-scoped to the registered repo-id. (context: Kimetsu memory scope system — project vs user isolation.)" + }, + { + "ce": 0.9389453530311584, + "key": "kimetsu-write-tools-gate", + "rank_score": 0.5016686916351318, + "text": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level — disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients. The feature was introduced to prevent malicious prompts from poisoning the brain. (context: Kimetsu write-tools gate — config-driven security for remote deployments.)" + }, + { + "ce": 0.08377432823181152, + "key": "kimetsu-distiller-config", + "rank_score": 0.4956844449043274, + "text": "project:fact - [tags: kimetsu distiller harvest config provider] The kimetsu distiller (auto-harvester) uses a SEPARATE provider configuration from the main agent: `distiller.provider`, `distiller.model`, `distiller.api_key`. This allows running the agent on an expensive model (Claude Opus) while harvesting with a cheap model (Claude Haiku). If `distiller.provider` is not set, it inherits `provider`. The distiller runs as a background task triggered by the post-session hook; it reads the session transcript and emits `kimetsu_brain_record` calls. Distiller timeouts are longer (300s) than normal tool calls (60s) because transcript processing can be slow. (context: Kimetsu distiller provider configuration — agent vs harvester model separation.)" + }, + { + "ce": 0.9388486742973328, + "key": "init-project-git-boundary", + "rank_score": 0.4811997413635254, + "text": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain. (context: QQ3 — kimetsu setup integration test failed because init_project climbed git tree to real ~/.kimetsu instead of temp workspace)" + }, + { + "ce": 0.13269849121570587, + "key": "sqlite-json1-extract", + "rank_score": 0.4759550094604492, + "text": "project:fact - [tags: sqlite json1 json_extract rusqlite] SQLite's json1 extension (built in since 3.38.0) lets you index and query JSONB columns with `json_extract(col, '$.field')`. To create a partial index over a JSON field: `CREATE INDEX idx ON memories (json_extract(metadata, '$.scope')) WHERE json_extract(metadata, '$.scope') IS NOT NULL;`. Use `json_each` for array fields. On older SQLite builds (rusqlite links whatever the system provides), check for json1 with `SELECT json('{}');` — an error means it's absent. Always prefer column storage over JSON blobs for frequently queried fields. (context: Kimetsu brain querying metadata scopes without migrating a separate column.)" + }, + { + "ce": 0.05342491716146469, + "key": "testing-temp-dirs-ci", + "rank_score": 0.4509641230106354, + "text": "project:fact - [tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure. On Windows, `env::temp_dir()` returns `C:\\Users\\\\AppData\\Local\\Temp` — ensure the test binary has write permissions there. Avoid using the workspace root as a temp dir — tests should never write to the source tree. (context: Kimetsu test infrastructure — temp directory discipline.)" + } + ], + "delivered": [ + "kimetsu-memory-scopes", + "kimetsu-write-tools-gate", + "init-project-git-boundary" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8142200112342834 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "how do I configure kimetsu to use Claude Haiku for harvesting but Opus for the agent?", + "relevant": [ + "kimetsu-distiller-config" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999725818634033, + "key": "kimetsu-distiller-config", + "rank_score": 0.9549954533576965, + "text": "project:fact - [tags: kimetsu distiller harvest config provider] The kimetsu distiller (auto-harvester) uses a SEPARATE provider configuration from the main agent: `distiller.provider`, `distiller.model`, `distiller.api_key`. This allows running the agent on an expensive model (Claude Opus) while harvesting with a cheap model (Claude Haiku). If `distiller.provider` is not set, it inherits `provider`. The distiller runs as a background task triggered by the post-session hook; it reads the session transcript and emits `kimetsu_brain_record` calls. Distiller timeouts are longer (300s) than normal tool calls (60s) because transcript processing can be slow. (context: Kimetsu distiller provider configuration — agent vs harvester model separation.)" + }, + { + "ce": 0.015412500128149986, + "key": "git-hooks-bypass", + "rank_score": 0.6000574231147766, + "text": "project:fact - [tags: git hooks bypass pre-commit skip] `git commit --no-verify` skips ALL hooks (pre-commit and commit-msg). Never use this in shared team repos where hooks enforce quality gates (lint, tests, memory harvest). Instead, fix the failing hook. If the hook itself is broken, fix the hook script. For emergency commits where hooks aren't relevant (e.g. updating a gitignore to untrack already-committed files), document the `--no-verify` use in the commit message. In CI, hooks run only if explicitly invoked — `git commit` in a CI pipeline with no hooks configured does nothing for quality enforcement. (context: Kimetsu pre-commit hook enforcing memory harvest.)" + }, + { + "ce": 0.8454174399375916, + "key": "bedrock-kimetsu-provider", + "rank_score": 0.5933955907821655, + "text": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env. Wire \"bedrock\" into BOTH pipeline.rs provider matches AND the distiller (normalize_distiller_provider + instantiation); the distiller is configured independently so agent-on-Bedrock + harvester-on-direct-Claude works for free. Sign and send the SAME payload bytes; test signing determinism with a fixed SystemTime. (context: Workstream A: adding AWS Bedrock as a provider for the agent + auto-harvester in v1.0.0.)" + }, + { + "ce": 0.8705393075942993, + "key": "aws-region-resolution", + "rank_score": 0.5856833457946777, + "text": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time. For cross-region inference (e.g. us-west-2 for Claude Opus), set `AWS_REGION=us-west-2`; do NOT rely on the Bedrock endpoint prefix being region-agnostic. (context: Kimetsu Bedrock provider region configuration.)" + }, + { + "ce": 0.027286455035209656, + "key": "tokio-channel-backpressure", + "rank_score": 0.5003699660301208, + "text": "project:fact - [tags: tokio mpsc channel backpressure async rust] `tokio::sync::mpsc::channel(N)` with a bounded buffer provides backpressure: senders block when the buffer is full. This prevents unbounded memory growth but can cause sender tasks to stall. Choosing N: too small causes frequent backpressure (throughput drops); too large defeats the purpose. For kimetsu's harvest pipeline, N=16 was a good balance — the harvester is I/O bound (LLM call), producers are fast (hook callbacks). Prefer bounded channels over unbounded in production code. `tokio::sync::mpsc::unbounded_channel()` is a footgun for bursty producers. (context: Kimetsu auto-harvester pipeline — bounded vs unbounded channel selection.)" + }, + { + "ce": 0.49993276596069336, + "key": "mcp-tool-naming", + "rank_score": 0.4818524718284607, + "text": "project:fact - [tags: mcp tool naming convention kimetsu] MCP tool names must be valid identifiers for all host agents. Claude Code restricts tool names to `[a-zA-Z0-9_-]` and max 64 chars. Use `snake_case` (kimetsu_brain_context, kimetsu_brain_record) — hyphen is technically allowed but some hosts reject it. Avoid dots (not allowed). Namespace with a prefix (`kimetsu_brain_`) to prevent collisions with other MCP servers. When a tool name changes, update ALL host config files (`.mcp.json`, `openclaw.json`, skill markdown) — mismatched names cause silent failures where the host skips the tool. (context: Kimetsu MCP tool naming convention enforcement.)" + } + ], + "delivered": [ + "kimetsu-distiller-config", + "aws-region-resolution", + "bedrock-kimetsu-provider", + "mcp-tool-naming" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7149495482444763 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "first agent turn is slow because kimetsu proactive hook runs embedding inference", + "relevant": [ + "kimetsu-proactive-hooks" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.4728188216686249, + "key": "pi-openclaw-extension-api", + "rank_score": 0.5093206167221069, + "text": "project:fact - [2026-09-05] [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`. External commands use `pi.exec()` but `node:child_process` spawn also works. Pi has NO MCP so Kimetsu integrates via TS extension + SKILL.md only. (context: Implementing Pi host target for Kimetsu plugin install/status/uninstall system.)\n\nAlso: [tags: kimetsu host-integration pi openclaw bridge] When integrating Kimetsu with an external host agent (Pi, OpenClaw, etc.), VERIFY the host's real plugin/extension API against its actual repo before writing embedded assets — docs-from-memory are frequently wrong. Concretely corrected during v1.0: Pi uses a default-export factory `export default function(pi)` (not `defineExtension`) with lifecycle events `session_start`/`agent_end`/`session_shutdown`; OpenClaw plugin entry is `index.ts` via `definePluginEntry` from `openclaw/plugin-sdk/plugin-entry` + an `openclaw.plugin.json` manifest, with snake_case hook events `agent_turn_prepare`/`agent_end`/`session_end` (NOT colon-delimited). Always make the embedded hook shell-out a silent no-op if the `kimetsu` binary isn't on PATH so a wrong guess never breaks the host. (context: Adding Pi + OpenClaw as BridgeTarget hosts in v1.0.0; the inferred extension/plugin APIs from docs were wrong and had to be corrected against the real repos.)" + }, + { + "ce": 0.5942663550376892, + "key": "cargo-profile-override", + "rank_score": 0.4527902603149414, + "text": "project:fact - [2026-09-05] [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug. The downside: rebuild time increases for that crate. For overflow-checks, `overflow-checks = false` per package speeds up hot loops. Never disable overflow-checks in release for business-critical data-mutating code. `[profile.release] strip = \"debuginfo\"` reduces binary size with minimal impact on stack traces. (context: Kimetsu dev experience — embedding inference was 10x slower in debug builds.)" + }, + { + "ce": 0.7604395151138306, + "key": "mcp-tool-timeouts", + "rank_score": 0.466677188873291, + "text": "project:fact - [2026-09-05] [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking — in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize — keep it in a process-global `OnceLock`). The reranker adds another 200-800ms; jina-tiny is the fastest. If tool calls are still slow, log the per-stage latency with `tracing::info!` at DEBUG level and profile under load. (context: Kimetsu MCP tool latency optimization.)" + }, + { + "ce": 0.8509889841079712, + "key": "kimetsu-distiller-config", + "rank_score": 0.6036672592163086, + "text": "project:fact - [2026-09-05] [tags: kimetsu distiller harvest config provider] The kimetsu distiller (auto-harvester) uses a SEPARATE provider configuration from the main agent: `distiller.provider`, `distiller.model`, `distiller.api_key`. This allows running the agent on an expensive model (Claude Opus) while harvesting with a cheap model (Claude Haiku). If `distiller.provider` is not set, it inherits `provider`. The distiller runs as a background task triggered by the post-session hook; it reads the session transcript and emits `kimetsu_brain_record` calls. Distiller timeouts are longer (300s) than normal tool calls (60s) because transcript processing can be slow. (context: Kimetsu distiller provider configuration — agent vs harvester model separation.)" + }, + { + "ce": 0.998892605304718, + "key": "kimetsu-proactive-hooks", + "rank_score": 0.9549953937530518, + "text": "project:fact - [2026-09-05] [tags: kimetsu proactive hooks context injection] kimetsu's proactive context injection runs before each agent turn (pre-turn hook) and injects relevant memories into the system prompt prefix. The hook invocation adds latency to the first token: embedding inference + vector search + reranking + context formatting. On a cold start, this can be 1-3 seconds. The hook is optional — disable with `KIMETSU_PROACTIVE=0`. The semantic floor (min cosine similarity) filters noise capsules before injection; setting the floor too low injects irrelevant memories and wastes context window tokens. The proactive hook does NOT trigger the distiller — that runs post-session only. (context: Kimetsu proactive context injection — latency and floor tuning.)" + }, + { + "ce": 0.6484696269035339, + "key": "kimetsu-bench-remote-embedder-singleton", + "rank_score": 0.543729305267334, + "text": "project:fact - [2026-09-05] [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval. Workaround: run ONE `--embedders` value per invocation and kill the remote process between runs. The local bench path is not affected (each combo is process-isolated via `--single` child spawn). (context: Kimetsu brain bench --remote known issue — multi-embedder contamination.)" + } + ], + "delivered": [ + "kimetsu-proactive-hooks", + "kimetsu-distiller-config", + "mcp-tool-timeouts", + "kimetsu-bench-remote-embedder-singleton" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7952143549919128 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "make the kimetsu brain read-only for certain repos on a shared remote server", + "relevant": [ + "kimetsu-write-tools-gate" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999514818191528, + "key": "kimetsu-write-tools-gate", + "rank_score": 0.9549953937530518, + "text": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level — disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients. The feature was introduced to prevent malicious prompts from poisoning the brain. (context: Kimetsu write-tools gate — config-driven security for remote deployments.)" + }, + { + "ce": 0.9976721405982971, + "key": "remote-ingest-split-roots", + "rank_score": 0.760320246219635, + "text": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races. Re-enable kimetsu_brain_ingest_repo in the tool allowlist only when ingest is configured, and INTERCEPT that tools/call in the remote handler (clone+ingest_repo_at_root) before the normal dispatch (which would walk the wrong dir). Hermetic test: git init a temp repo, register url=local path, ingest, then context retrieves the file capsule via FTS (noop embedder). (context: R3c: server-side ingest for kimetsu-remote — cloning repos so file-capsule retrieval works without a local checkout.)" + }, + { + "ce": 0.9153556227684021, + "key": "remote-mcp-host-wiring", + "rank_score": 0.6376349925994873, + "text": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal. Derive a stable repo id from the git remote: strip `.git`, scheme (`://`), and `user@`, then map non-alphanumerics to '-' and collapse — so both https://github.com/org/repo.git and git@github.com:org/repo.git -> `github-com-org-repo`. Remote install writes ONLY the MCP entry + instructions (no local hooks — the brain is on the server). Codex/Pi don't get --remote (no remote-MCP / no MCP). (context: R2: implementing `kimetsu plugin install --remote` to wire a host at a kimetsu-remote HTTP MCP server.)" + }, + { + "ce": 0.004050008021295071, + "key": "sqlite-busy-timeout-wal", + "rank_score": 0.6039800643920898, + "text": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch. Set the timeout before any transaction, not inside one — it is a connection-level property. (context: Kimetsu brain writer and reader processes sharing the same SQLite brain database.)" + }, + { + "ce": 0.006196254398673773, + "key": "git-submodule-pinning", + "rank_score": 0.5714722275733948, + "text": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip — this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version. If a submodule is the kimetsu-bench repo inside the main repo, pin the bench SHA after validating the dataset change. Use `git diff HEAD -- bench` to see the pinned SHA change before committing. (context: Kimetsu bench as a git submodule of the main repo.)" + }, + { + "ce": 0.470986932516098, + "key": "git-worktree-brain-isolation", + "rank_score": 0.5826837420463562, + "text": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root — if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain. Always set `KIMETSU_BRAIN_DIR` or use `git_init_boundary` in tests to prevent this. (context: Kimetsu development with git worktrees — test isolation.)" + } + ], + "delivered": [ + "kimetsu-write-tools-gate", + "remote-ingest-split-roots", + "remote-mcp-host-wiring", + "git-worktree-brain-isolation" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7889073491096497 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "kimetsu FTS search misses 'deadlocking' when memory says 'deadlock'", + "relevant": [ + "kimetsu-query-stemming" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.246894970536232, + "key": "mutex-deadlock-user-brain-disabled", + "rank_score": 0.7293793559074402, + "text": "project:fact - [2026-09-05] [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure — `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation. (context: New tests for Tier-1 perf work called test_env_lock().lock() inside with_user_brain_disabled closure, deadlocking all project::tests that ran after them in the same test binary.)" + }, + { + "ce": 0.15477891266345978, + "key": "sqlite-fts5-tokenizer", + "rank_score": 0.591143786907196, + "text": "project:fact - [2026-09-05] [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon. If you switch tokenizers on an existing FTS5 table, you MUST rebuild the shadow tables: `INSERT INTO tbl(tbl) VALUES('rebuild');` — a schema-only change leaves the inverted index unusable. The `porter` stemmer is available as `tokenize='porter unicode61'` but aggressively strips suffixes and hurts precision on technical terms. (context: Kimetsu brain FTS5 index tuning for Rust identifier retrieval.)" + }, + { + "ce": 0.11751849204301834, + "key": "mcp-tool-timeouts", + "rank_score": 0.5007147789001465, + "text": "project:fact - [2026-09-05] [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking — in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize — keep it in a process-global `OnceLock`). The reranker adds another 200-800ms; jina-tiny is the fastest. If tool calls are still slow, log the per-stage latency with `tracing::info!` at DEBUG level and profile under load. (context: Kimetsu MCP tool latency optimization.)" + }, + { + "ce": 0.06785819679498672, + "key": "kimetsu-memory-scopes", + "rank_score": 0.5335937738418579, + "text": "project:fact - [2026-09-05] [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available — if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope. The `kimetsu_brain_record` MCP tool inherits the scope from the server's launch context. When running kimetsu-remote, all memories are project-scoped to the registered repo-id. (context: Kimetsu memory scope system — project vs user isolation.)" + }, + { + "ce": 0.9927980303764343, + "key": "kimetsu-query-stemming", + "rank_score": 0.9549953937530518, + "text": "project:fact - [2026-09-05] [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression. The lexical floor (`min_lexical_coverage`) requires at least N stemmed query tokens to match in any retrieved document — this prevents high-semantic-score but lexically-unrelated documents from dominating. Stemming is applied only when the query has >= 3 tokens; short queries skip it. (context: Kimetsu retrieval — query-side stemming implementation.)" + }, + { + "ce": 0.3425157368183136, + "key": "kimetsu-eval-fixture-shape", + "rank_score": 0.4692133963108063, + "text": "project:fact - [2026-09-05] [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` — a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases). Keys must be unique across the dataset. The bench currently does not validate keys at load — it fails later with an `unwrap()` on a missing HashMap entry. (context: Kimetsu bench dataset shape and validation.)" + } + ], + "delivered": [ + "kimetsu-query-stemming", + "kimetsu-eval-fixture-shape" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7542863488197327 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "how does pool size affect retrieval recall and latency in the bench?", + "relevant": [ + "kimetsu-rerank-pool" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.999588668346405, + "key": "kimetsu-rerank-pool", + "rank_score": 0.954995334148407, + "text": "project:fact - [tags: kimetsu reranker pool size ann retrieval] kimetsu's retrieval pipeline: ANN (approximate nearest neighbor) retrieves a pool of candidates, then the reranker reorders them, then the top-K are returned. The pool size (default 6 for production, 12 in bench) controls the recall-latency tradeoff: larger pool = higher recall = more reranker calls = more latency. For the jina-tiny reranker, pool 12 adds ~80ms vs pool 6. The bench uses pool 12 to maximize measurable recall differences between rerankers; production uses pool 6 for latency. Increasing pool size beyond 20 has diminishing recall returns on corpora < 1000 memories. (context: Kimetsu ANN pool size tuning for the retrieval benchmark.)" + }, + { + "ce": 0.004114254843443632, + "key": "onnx-ort-threading", + "rank_score": 0.5690031051635742, + "text": "project:fact - [tags: onnx ort thread-pool parallelism cpu] ORT (ONNX Runtime) creates its own inter-op and intra-op thread pools. In a multi-process bench setup, each child inherits these pools and they compete for CPU cores. Set `SessionOptionsBuilder::with_intra_threads(1).with_inter_threads(1)` if you're running many parallel bench processes — this sacrifices per-inference throughput for lower contention. In a single-threaded embedding pipeline, 2-4 intra-op threads are better. For benchmarking, set `ORT_NUM_THREADS=1` via env var to get deterministic single-threaded latency numbers. (context: Kimetsu brain bench multi-process parallelism — ORT thread contention causing inconsistent latency.)" + }, + { + "ce": 0.006807573605328798, + "key": "kimetsu-mrr-metric", + "rank_score": 0.5386407971382141, + "text": "project:fact - [tags: kimetsu bench mrr recall metrics evaluation] kimetsu bench reports MRR (Mean Reciprocal Rank) and Recall@K. MRR is 1/rank_of_first_relevant_result, averaged across cases; it penalizes models that rank the correct answer 2nd or 3rd. Recall@K is the fraction of cases where at least one relevant answer appears in the top K. For multi-answer cases, recall@K considers a case satisfied if ANY relevant key appears in top K. MRR is the primary metric for knowledge retrieval because users read the first result first. A 0.01 MRR difference on a 100-case dataset corresponds to about 1 case changing from rank-2 to rank-1. Noise of ~2-3 cases is expected run-to-run. (context: Kimetsu benchmark metric interpretation.)" + }, + { + "ce": 0.00037411469384096563, + "key": "kimetsu-bench-remote-embedder-singleton", + "rank_score": 0.5266498923301697, + "text": "project:fact - [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval. Workaround: run ONE `--embedders` value per invocation and kill the remote process between runs. The local bench path is not affected (each combo is process-isolated via `--single` child spawn). (context: Kimetsu brain bench --remote known issue — multi-embedder contamination.)" + }, + { + "ce": 0.000048999532737070695, + "key": "sqlite-vacuum-wal-checkpoint", + "rank_score": 0.4593881070613861, + "text": "project:fact - [tags: rust sqlite vacuum rusqlite windows] When implementing SQLite VACUUM in rusqlite: VACUUM cannot run inside a transaction. rusqlite's Connection does not hold an implicit transaction, so `conn.execute_batch(\"VACUUM;\")` works directly. After VACUUM, run `PRAGMA wal_checkpoint(TRUNCATE);` before measuring file size — on Windows the WAL file can hold significant space that isn't reflected in the main db file until the checkpoint runs. (context: Implementing kimetsu brain compact (Q8) — SQLite VACUUM + WAL checkpoint for accurate post-compact file size.)" + }, + { + "ce": 0.00008059733227128163, + "key": "kimetsu-proactive-hooks", + "rank_score": 0.4396187663078308, + "text": "project:fact - [tags: kimetsu proactive hooks context injection] kimetsu's proactive context injection runs before each agent turn (pre-turn hook) and injects relevant memories into the system prompt prefix. The hook invocation adds latency to the first token: embedding inference + vector search + reranking + context formatting. On a cold start, this can be 1-3 seconds. The hook is optional — disable with `KIMETSU_PROACTIVE=0`. The semantic floor (min cosine similarity) filters noise capsules before injection; setting the floor too low injects irrelevant memories and wastes context window tokens. The proactive hook does NOT trigger the distiller — that runs post-session only. (context: Kimetsu proactive context injection — latency and floor tuning.)" + } + ], + "delivered": [ + "kimetsu-rerank-pool" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8280626535415649 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "second embedder in a remote bench run gets worse results than the first", + "relevant": [ + "kimetsu-bench-remote-embedder-singleton" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.005159968510270119, + "key": "harbor-terminal-bench-subprocess-isolation", + "rank_score": 0.7130707502365112, + "text": "project:fact - [2026-09-05] [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd). Worker re-derives auth internally from .env so the OAuth token never lands in argv; it writes {run,grade} JSON the parent reads back. One Harbor invocation per process always works (baseline-alone passed). (context: kbench multi-trial sweeps crashed on every trial after the 1st; diagnosed as Harbor/pyiceberg os.getcwd staleness on WSL2.)" + }, + { + "ce": 0.0004921680083498359, + "key": "onnx-model-cache-paths", + "rank_score": 0.6452701091766357, + "text": "project:fact - [2026-09-05] [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use. In kimetsu, `KIMETSU_EMBEDDER_CACHE` overrides the path and is forwarded when spawning child bench processes — without forwarding it, each child re-downloads the model. (context: Kimetsu brain bench on CI — model cache path handling in child processes.)" + }, + { + "ce": 0.004050213843584061, + "key": "onnx-dim-mismatch", + "rank_score": 0.6060594320297241, + "text": "project:fact - [2026-09-05] [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results — the ANN index shape mismatch isn't always caught at runtime. kimetsu detects this by storing `embedder_id` in the brain schema and refusing to query if the configured embedder differs from what was used at ingest time. Mitigation: re-ingest all memories with the new model, or keep per-memory vector dim metadata. (context: Kimetsu embedder migration — detecting dimension mismatch at startup.)" + }, + { + "ce": 0.0001078732093446888, + "key": "ci-artifact-retention", + "rank_score": 0.6166631579399109, + "text": "project:fact - [2026-09-05] [tags: ci github-actions artifacts retention benchmark] GitHub Actions artifacts are retained for 90 days (default). For benchmark results, use `actions/upload-artifact` with `retention-days: 365` for long-term tracking. The free tier has 500MB storage — per-combo JSON files from kimetsu bench (each ~60KB) add up fast if you upload them on every push. Upload only the summary.md. For regression detection, compare the current run's MRR against the artifact from the last green main build — fetch it with the `actions/download-artifact` action. (context: Kimetsu CI benchmark result tracking.)" + }, + { + "ce": 0.8562779426574707, + "key": "kimetsu-bench-remote-embedder-singleton", + "rank_score": 0.954995334148407, + "text": "project:fact - [2026-09-05] [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval. Workaround: run ONE `--embedders` value per invocation and kill the remote process between runs. The local bench path is not affected (each combo is process-isolated via `--single` child spawn). (context: Kimetsu brain bench --remote known issue — multi-embedder contamination.)" + }, + { + "ce": 0.0021204755175858736, + "key": "kimetsu-mrr-metric", + "rank_score": 0.738929808139801, + "text": "project:fact - [2026-09-05] [tags: kimetsu bench mrr recall metrics evaluation] kimetsu bench reports MRR (Mean Reciprocal Rank) and Recall@K. MRR is 1/rank_of_first_relevant_result, averaged across cases; it penalizes models that rank the correct answer 2nd or 3rd. Recall@K is the fraction of cases where at least one relevant answer appears in the top K. For multi-answer cases, recall@K considers a case satisfied if ANY relevant key appears in top K. MRR is the primary metric for knowledge retrieval because users read the first result first. A 0.01 MRR difference on a 100-case dataset corresponds to about 1 case changing from rank-2 to rank-1. Noise of ~2-3 cases is expected run-to-run. (context: Kimetsu benchmark metric interpretation.)" + } + ], + "delivered": [ + "kimetsu-bench-remote-embedder-singleton" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8167648911476135 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "what is the expected JSON schema for kimetsu brain bench dataset files?", + "relevant": [ + "kimetsu-eval-fixture-shape" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999712705612183, + "key": "kimetsu-eval-fixture-shape", + "rank_score": 0.954995334148407, + "text": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` — a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases). Keys must be unique across the dataset. The bench currently does not validate keys at load — it fails later with an `unwrap()` on a missing HashMap entry. (context: Kimetsu bench dataset shape and validation.)" + }, + { + "ce": 0.5093982815742493, + "key": "testing-fixture-drift", + "rank_score": 0.8501691818237305, + "text": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code. For kimetsu, `EvalFixture::from_memories(memories)` constructs a dataset from the exported format — use it in tests instead of hardcoded JSON. Tag fixture files with the schema version they were generated against in a comment. (context: Kimetsu eval fixture drift after schema migration.)" + }, + { + "ce": 0.21262294054031372, + "key": "sqlite-json1-extract", + "rank_score": 0.6636665463447571, + "text": "project:fact - [tags: sqlite json1 json_extract rusqlite] SQLite's json1 extension (built in since 3.38.0) lets you index and query JSONB columns with `json_extract(col, '$.field')`. To create a partial index over a JSON field: `CREATE INDEX idx ON memories (json_extract(metadata, '$.scope')) WHERE json_extract(metadata, '$.scope') IS NOT NULL;`. Use `json_each` for array fields. On older SQLite builds (rusqlite links whatever the system provides), check for json1 with `SELECT json('{}');` — an error means it's absent. Always prefer column storage over JSON blobs for frequently queried fields. (context: Kimetsu brain querying metadata scopes without migrating a separate column.)" + }, + { + "ce": 0.42808839678764343, + "key": "kimetsu-mrr-metric", + "rank_score": 0.6773895621299744, + "text": "project:fact - [tags: kimetsu bench mrr recall metrics evaluation] kimetsu bench reports MRR (Mean Reciprocal Rank) and Recall@K. MRR is 1/rank_of_first_relevant_result, averaged across cases; it penalizes models that rank the correct answer 2nd or 3rd. Recall@K is the fraction of cases where at least one relevant answer appears in the top K. For multi-answer cases, recall@K considers a case satisfied if ANY relevant key appears in top K. MRR is the primary metric for knowledge retrieval because users read the first result first. A 0.01 MRR difference on a 100-case dataset corresponds to about 1 case changing from rank-2 to rank-1. Noise of ~2-3 cases is expected run-to-run. (context: Kimetsu benchmark metric interpretation.)" + }, + { + "ce": 0.047336138784885406, + "key": "onnx-dim-mismatch", + "rank_score": 0.6641482710838318, + "text": "project:fact - [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results — the ANN index shape mismatch isn't always caught at runtime. kimetsu detects this by storing `embedder_id` in the brain schema and refusing to query if the configured embedder differs from what was used at ingest time. Mitigation: re-ingest all memories with the new model, or keep per-memory vector dim metadata. (context: Kimetsu embedder migration — detecting dimension mismatch at startup.)" + }, + { + "ce": 0.7343910336494446, + "key": "mcp-schema-validation", + "rank_score": 0.6441216468811035, + "text": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array — omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error. Use `serde(default)` for optional fields. When adding a new tool, check the schema in the tools/list response manually by piping a JSON-RPC tools/list request to `./kimetsu mcp`. (context: Kimetsu MCP tool schema — required field validation.)" + } + ], + "delivered": [ + "kimetsu-eval-fixture-shape", + "mcp-schema-validation", + "testing-fixture-drift", + "kimetsu-mrr-metric" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8360985517501831 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "what does MRR mean and how do I interpret a 0.01 difference between combos?", + "relevant": [ + "kimetsu-mrr-metric" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9307324886322021, + "key": "kimetsu-mrr-metric", + "rank_score": 0.954995334148407, + "text": "project:fact - [tags: kimetsu bench mrr recall metrics evaluation] kimetsu bench reports MRR (Mean Reciprocal Rank) and Recall@K. MRR is 1/rank_of_first_relevant_result, averaged across cases; it penalizes models that rank the correct answer 2nd or 3rd. Recall@K is the fraction of cases where at least one relevant answer appears in the top K. For multi-answer cases, recall@K considers a case satisfied if ANY relevant key appears in top K. MRR is the primary metric for knowledge retrieval because users read the first result first. A 0.01 MRR difference on a 100-case dataset corresponds to about 1 case changing from rank-2 to rank-1. Noise of ~2-3 cases is expected run-to-run. (context: Kimetsu benchmark metric interpretation.)" + }, + { + "ce": 0.0059293825179338455, + "key": "onnx-quantization-drift", + "rank_score": 0.6337414979934692, + "text": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals — cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case. (context: Kimetsu embedding model selection — evaluating jina-v2 int8 vs fp32.)" + }, + { + "ce": 0.0008765222737565637, + "key": "ci-artifact-retention", + "rank_score": 0.5710563659667969, + "text": "project:fact - [tags: ci github-actions artifacts retention benchmark] GitHub Actions artifacts are retained for 90 days (default). For benchmark results, use `actions/upload-artifact` with `retention-days: 365` for long-term tracking. The free tier has 500MB storage — per-combo JSON files from kimetsu bench (each ~60KB) add up fast if you upload them on every push. Upload only the summary.md. For regression detection, compare the current run's MRR against the artifact from the last green main build — fetch it with the `actions/download-artifact` action. (context: Kimetsu CI benchmark result tracking.)" + }, + { + "ce": 0.00005625460471492261, + "key": "kimetsu-rerank-pool", + "rank_score": 0.5378338694572449, + "text": "project:fact - [tags: kimetsu reranker pool size ann retrieval] kimetsu's retrieval pipeline: ANN (approximate nearest neighbor) retrieves a pool of candidates, then the reranker reorders them, then the top-K are returned. The pool size (default 6 for production, 12 in bench) controls the recall-latency tradeoff: larger pool = higher recall = more reranker calls = more latency. For the jina-tiny reranker, pool 12 adds ~80ms vs pool 6. The bench uses pool 12 to maximize measurable recall differences between rerankers; production uses pool 6 for latency. Increasing pool size beyond 20 has diminishing recall returns on corpora < 1000 memories. (context: Kimetsu ANN pool size tuning for the retrieval benchmark.)" + }, + { + "ce": 0.0002500477130524814, + "key": "kimetsu-bench-remote-embedder-singleton", + "rank_score": 0.5305659174919128, + "text": "project:fact - [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval. Workaround: run ONE `--embedders` value per invocation and kill the remote process between runs. The local bench path is not affected (each combo is process-isolated via `--single` child spawn). (context: Kimetsu brain bench --remote known issue — multi-embedder contamination.)" + }, + { + "ce": 0.00003602978176786564, + "key": "sqlite-json1-extract", + "rank_score": 0.4960232079029083, + "text": "project:fact - [tags: sqlite json1 json_extract rusqlite] SQLite's json1 extension (built in since 3.38.0) lets you index and query JSONB columns with `json_extract(col, '$.field')`. To create a partial index over a JSON field: `CREATE INDEX idx ON memories (json_extract(metadata, '$.scope')) WHERE json_extract(metadata, '$.scope') IS NOT NULL;`. Use `json_each` for array fields. On older SQLite builds (rusqlite links whatever the system provides), check for json1 with `SELECT json('{}');` — an error means it's absent. Always prefer column storage over JSON blobs for frequently queried fields. (context: Kimetsu brain querying metadata scopes without migrating a separate column.)" + } + ], + "delivered": [ + "kimetsu-mrr-metric" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7038604617118835 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "SQLITE_BUSY keeps appearing even with WAL mode enabled", + "relevant": [ + "sqlite-busy-timeout-wal" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9940937161445618, + "key": "sqlite-busy-timeout-wal", + "rank_score": 0.9549950361251831, + "text": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch. Set the timeout before any transaction, not inside one — it is a connection-level property. (context: Kimetsu brain writer and reader processes sharing the same SQLite brain database.)" + }, + { + "ce": 0.00015381416596937925, + "key": "testing-snapshot-churn", + "rank_score": 0.7609618902206421, + "text": "project:fact - [tags: testing snapshot insta assert churn rust] Snapshot tests (e.g. with the `insta` crate) fail whenever the output changes, even for intended changes. In CI, they fail loudly; locally, `cargo insta review` walks you through accepting or rejecting changes. Snapshot churn becomes a problem when output includes timestamps, process IDs, or randomly-ordered maps. Redact these before snapshotting: use `insta::with_settings!({redactions: [\".timestamp\" => \"[TIMESTAMP]\"]})`. For JSON output, sort maps and arrays before comparing. Keep snapshot files in `src/snapshots/` and always commit them — an untracked snapshot file causes the next CI run to fail with a different error than expected. (context: Kimetsu CLI output snapshot tests — reducing churn.)" + }, + { + "ce": 0.0004425587540026754, + "key": "remote-ingest-split-roots", + "rank_score": 0.6740684509277344, + "text": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races. Re-enable kimetsu_brain_ingest_repo in the tool allowlist only when ingest is configured, and INTERCEPT that tools/call in the remote handler (clone+ingest_repo_at_root) before the normal dispatch (which would walk the wrong dir). Hermetic test: git init a temp repo, register url=local path, ingest, then context retrieves the file capsule via FTS (noop embedder). (context: R3c: server-side ingest for kimetsu-remote — cloning repos so file-capsule retrieval works without a local checkout.)" + }, + { + "ce": 0.7747130393981934, + "key": "sqlite-wal-network-drive", + "rank_score": 0.6857626438140869, + "text": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db. Fallback: `PRAGMA journal_mode=DELETE;` is safe over SMB at the cost of lower concurrency. Detect network drives at startup with `GetFileAttributes` checking FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS or using `PathIsNetworkPath`. (context: Users running kimetsu with the brain database on a mapped network drive.)" + }, + { + "ce": 0.00007890452252468094, + "key": "kimetsu-mrr-metric", + "rank_score": 0.6366104483604431, + "text": "project:fact - [tags: kimetsu bench mrr recall metrics evaluation] kimetsu bench reports MRR (Mean Reciprocal Rank) and Recall@K. MRR is 1/rank_of_first_relevant_result, averaged across cases; it penalizes models that rank the correct answer 2nd or 3rd. Recall@K is the fraction of cases where at least one relevant answer appears in the top K. For multi-answer cases, recall@K considers a case satisfied if ANY relevant key appears in top K. MRR is the primary metric for knowledge retrieval because users read the first result first. A 0.01 MRR difference on a 100-case dataset corresponds to about 1 case changing from rank-2 to rank-1. Noise of ~2-3 cases is expected run-to-run. (context: Kimetsu benchmark metric interpretation.)" + }, + { + "ce": 0.00009736205538501963, + "key": "onnx-dim-mismatch", + "rank_score": 0.6033197045326233, + "text": "project:fact - [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results — the ANN index shape mismatch isn't always caught at runtime. kimetsu detects this by storing `embedder_id` in the brain schema and refusing to query if the configured embedder differs from what was used at ingest time. Mitigation: re-ingest all memories with the new model, or keep per-memory vector dim metadata. (context: Kimetsu embedder migration — detecting dimension mismatch at startup.)" + } + ], + "delivered": [ + "sqlite-busy-timeout-wal", + "sqlite-wal-network-drive" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7975165843963623 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "my brain file got huge again right after I compacted it", + "relevant": [ + "sqlite-vacuum-wal-checkpoint", + "sqlite-page-size" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.00043200980871915817, + "key": "cargo-feature-unification-embeddings", + "rank_score": 0.6529825329780579, + "text": "project:fact - [2026-09-05] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli). Diagnostic tell: a test that passes alone but fails only under `cargo test --workspace` AND a brand-new crate was just added = suspect feature unification flipping a sibling crate's behavior. (context: Building the kimetsu-remote crate (HTTP MCP server); its default embeddings feature broke 3 kimetsu-chat retrieval tests only under the full workspace test.)" + }, + { + "ce": 0.0036837446969002485, + "key": "sqlite-vacuum-wal-checkpoint", + "rank_score": 0.9549949169158936, + "text": "project:fact - [2026-09-05] [tags: rust sqlite vacuum rusqlite windows] When implementing SQLite VACUUM in rusqlite: VACUUM cannot run inside a transaction. rusqlite's Connection does not hold an implicit transaction, so `conn.execute_batch(\"VACUUM;\")` works directly. After VACUUM, run `PRAGMA wal_checkpoint(TRUNCATE);` before measuring file size — on Windows the WAL file can hold significant space that isn't reflected in the main db file until the checkpoint runs. (context: Implementing kimetsu brain compact (Q8) — SQLite VACUUM + WAL checkpoint for accurate post-compact file size.)" + }, + { + "ce": 0.00012824423902202398, + "key": "cargo-patch-section", + "rank_score": 0.6346641182899475, + "text": "project:fact - [2026-09-05] [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace — including transitive deps — that depend on `my-crate`. Remove the patch before publishing. Using `[replace]` is deprecated since Cargo 0.47; always use `[patch]`. When patching a crate pinned via an exact version specifier, the patch must satisfy that exact version. Use `cargo tree` to confirm the patch is applied. (context: Kimetsu patching upstream rusqlite for a Windows-specific WAL fix.)" + }, + { + "ce": 0.00014346737589221448, + "key": "testing-property-tests", + "rank_score": 0.5971766114234924, + "text": "project:fact - [2026-09-05] [tags: testing property-based proptest quickcheck rust] Property-based tests (proptest, quickcheck) find edge cases that example-based tests miss. For kimetsu's memory text normalization, proptest found that zero-width joiner characters and right-to-left marks caused hash collisions. Run proptest with `PROPTEST_CASES=10000` in CI for thorough coverage. Shrinking: when proptest finds a failure, it automatically shrinks the input to the minimal failing case — read the `Minimized failure` output, not the original random input. Use `prop_assume!` to skip inputs that violate preconditions rather than `if/return`. (context: Kimetsu brain text normalization — property test for dedup hash stability.)" + }, + { + "ce": 0.0002277977328049019, + "key": "testing-fixture-drift", + "rank_score": 0.6982339024543762, + "text": "project:fact - [2026-09-05] [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code. For kimetsu, `EvalFixture::from_memories(memories)` constructs a dataset from the exported format — use it in tests instead of hardcoded JSON. Tag fixture files with the schema version they were generated against in a comment. (context: Kimetsu eval fixture drift after schema migration.)" + }, + { + "ce": 0.00012205570237711072, + "key": "ci-artifact-retention", + "rank_score": 0.5338141918182373, + "text": "project:fact - [2026-09-05] [tags: ci github-actions artifacts retention benchmark] GitHub Actions artifacts are retained for 90 days (default). For benchmark results, use `actions/upload-artifact` with `retention-days: 365` for long-term tracking. The free tier has 500MB storage — per-combo JSON files from kimetsu bench (each ~60KB) add up fast if you upload them on every push. Upload only the summary.md. For regression detection, compare the current run's MRR against the artifact from the last green main build — fetch it with the `actions/download-artifact` action. (context: Kimetsu CI benchmark result tracking.)" + } + ], + "delivered": [], + "excluded_gold": [ + { + "ce": null, + "key": "sqlite-page-size" + } + ], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.6257204413414001 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "all my FTS queries stopped returning results after I changed the tokenizer config", + "relevant": [ + "sqlite-fts5-tokenizer" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.0008613576646894217, + "key": "harbor-terminal-bench-subprocess-isolation", + "rank_score": 0.5660423040390015, + "text": "project:fact - [2026-09-05] [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd). Worker re-derives auth internally from .env so the OAuth token never lands in argv; it writes {run,grade} JSON the parent reads back. One Harbor invocation per process always works (baseline-alone passed). (context: kbench multi-trial sweeps crashed on every trial after the 1st; diagnosed as Harbor/pyiceberg os.getcwd staleness on WSL2.)" + }, + { + "ce": 0.04170652851462364, + "key": "sqlite-fts5-tokenizer", + "rank_score": 0.9549949169158936, + "text": "project:fact - [2026-09-05] [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon. If you switch tokenizers on an existing FTS5 table, you MUST rebuild the shadow tables: `INSERT INTO tbl(tbl) VALUES('rebuild');` — a schema-only change leaves the inverted index unusable. The `porter` stemmer is available as `tokenize='porter unicode61'` but aggressively strips suffixes and hurts precision on technical terms. (context: Kimetsu brain FTS5 index tuning for Rust identifier retrieval.)" + }, + { + "ce": 0.0007703533628955483, + "key": "cargo-patch-section", + "rank_score": 0.6364384889602661, + "text": "project:fact - [2026-09-05] [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace — including transitive deps — that depend on `my-crate`. Remove the patch before publishing. Using `[replace]` is deprecated since Cargo 0.47; always use `[patch]`. When patching a crate pinned via an exact version specifier, the patch must satisfy that exact version. Use `cargo tree` to confirm the patch is applied. (context: Kimetsu patching upstream rusqlite for a Windows-specific WAL fix.)" + }, + { + "ce": 0.001252001035027206, + "key": "onnx-tokenizer-mismatch", + "rank_score": 0.6266893744468689, + "text": "project:fact - [2026-09-05] [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly — specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings — cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo. Validate by checking a reference embedding against the HuggingFace Python output. (context: Kimetsu custom ONNX reranker loading — wrong tokenizer produced degraded retrieval.)" + }, + { + "ce": 0.0016921237111091614, + "key": "onnx-dim-mismatch", + "rank_score": 0.7861688733100891, + "text": "project:fact - [2026-09-05] [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results — the ANN index shape mismatch isn't always caught at runtime. kimetsu detects this by storing `embedder_id` in the brain schema and refusing to query if the configured embedder differs from what was used at ingest time. Mitigation: re-ingest all memories with the new model, or keep per-memory vector dim metadata. (context: Kimetsu embedder migration — detecting dimension mismatch at startup.)" + }, + { + "ce": 0.008115414530038834, + "key": "kimetsu-query-stemming", + "rank_score": 0.7216838002204895, + "text": "project:fact - [2026-09-05] [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression. The lexical floor (`min_lexical_coverage`) requires at least N stemmed query tokens to match in any retrieved document — this prevents high-semantic-score but lexically-unrelated documents from dominating. Stemming is applied only when the query has >= 3 tokens; short queries skip it. (context: Kimetsu retrieval — query-side stemming implementation.)" + } + ], + "delivered": [], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.760891318321228 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "something is preventing the kimetsu binary from being replaced during update", + "relevant": [ + "kimetsu-daemon-lifecycle", + "windows-file-locking-av", + "windows-update-process-locking" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9216884970664978, + "key": "kimetsu-daemon-lifecycle", + "rank_score": 0.9549951553344727, + "text": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required. The server PID is not stored anywhere; use `kimetsu doctor` to enumerate running MCP server processes via OS APIs. On Windows, the server binary may be locked by AV after first launch — `kimetsu update` must stop all running server processes before replacing the binary. (context: Kimetsu daemon lifecycle — process management for updates.)" + }, + { + "ce": 0.021963156759738922, + "key": "windows-exit-codes", + "rank_score": 0.8502125144004822, + "text": "project:fact - [tags: windows exit-codes rust process child] On Windows, process exit codes are 32-bit unsigned integers (DWORD). Rust's `ExitStatus::code()` returns `Option` — it's `None` if the process was killed by a signal (which Windows doesn't use; instead, TerminateProcess with a code). Conventional codes: 0=success, 1=generic error, 0xC0000005=access violation. Programs that call `std::process::exit(-1)` on Windows produce exit code 0xFFFFFFFF (4294967295), not -1. When checking for success in a subprocess chain, always check `status.success()` rather than `status.code() == Some(0)` to handle this portably. (context: Kimetsu update binary replacement — exit code handling.)" + }, + { + "ce": 0.0016466703964397311, + "key": "sqlite-prepared-stmt-cache", + "rank_score": 0.7594854831695557, + "text": "project:fact - [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8). The cache key is the SQL string verbatim, so template strings with interpolated values defeat caching — use `?1, ?2` placeholders instead. Calling `prepare_cached` in a tight loop is effectively free after warmup. (context: Kimetsu brain high-throughput ingest path — replacing prepare() with prepare_cached() cut ingest time by ~30%.)" + }, + { + "ce": 0.02362785115838051, + "key": "git-submodule-pinning", + "rank_score": 0.7585165500640869, + "text": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip — this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version. If a submodule is the kimetsu-bench repo inside the main repo, pin the bench SHA after validating the dataset change. Use `git diff HEAD -- bench` to see the pinned SHA change before committing. (context: Kimetsu bench as a git submodule of the main repo.)" + }, + { + "ce": 0.2287038415670395, + "key": "windows-update-process-locking", + "rank_score": 0.7041141390800476, + "text": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics — mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code. (context: Q2 — kimetsu update preflight for locked binary on Windows)" + }, + { + "ce": 0.0016072194557636976, + "key": "aws-region-resolution", + "rank_score": 0.6791172623634338, + "text": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time. For cross-region inference (e.g. us-west-2 for Claude Opus), set `AWS_REGION=us-west-2`; do NOT rely on the Bedrock endpoint prefix being region-agnostic. (context: Kimetsu Bedrock provider region configuration.)" + } + ], + "delivered": [ + "kimetsu-daemon-lifecycle" + ], + "excluded_gold": [ + { + "ce": null, + "key": "windows-file-locking-av" + } + ], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.744592547416687 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "tool call results not appearing in the context — is the semantic floor too high?", + "relevant": [ + "kimetsu-proactive-hooks", + "kimetsu-rerank-pool" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.0738905593752861, + "key": "kimetsu-proactive-hooks", + "rank_score": 0.9549950957298279, + "text": "project:fact - [tags: kimetsu proactive hooks context injection] kimetsu's proactive context injection runs before each agent turn (pre-turn hook) and injects relevant memories into the system prompt prefix. The hook invocation adds latency to the first token: embedding inference + vector search + reranking + context formatting. On a cold start, this can be 1-3 seconds. The hook is optional — disable with `KIMETSU_PROACTIVE=0`. The semantic floor (min cosine similarity) filters noise capsules before injection; setting the floor too low injects irrelevant memories and wastes context window tokens. The proactive hook does NOT trigger the distiller — that runs post-session only. (context: Kimetsu proactive context injection — latency and floor tuning.)" + }, + { + "ce": 0.0001241629506694153, + "key": "kimetsu-mrr-metric", + "rank_score": 0.8554686903953552, + "text": "project:fact - [tags: kimetsu bench mrr recall metrics evaluation] kimetsu bench reports MRR (Mean Reciprocal Rank) and Recall@K. MRR is 1/rank_of_first_relevant_result, averaged across cases; it penalizes models that rank the correct answer 2nd or 3rd. Recall@K is the fraction of cases where at least one relevant answer appears in the top K. For multi-answer cases, recall@K considers a case satisfied if ANY relevant key appears in top K. MRR is the primary metric for knowledge retrieval because users read the first result first. A 0.01 MRR difference on a 100-case dataset corresponds to about 1 case changing from rank-2 to rank-1. Noise of ~2-3 cases is expected run-to-run. (context: Kimetsu benchmark metric interpretation.)" + }, + { + "ce": 0.00005381699520512484, + "key": "kimetsu-capsule-budgets", + "rank_score": 0.8582921028137207, + "text": "project:fact - [tags: kimetsu capsule tokens budget retrieval] kimetsu retrieval enforces a token budget per capsule type: memory capsules are capped at 6000 tokens total (across all retrieved memories), file capsules at 3000 tokens. When a memory is large and would exceed the budget, it is truncated at a sentence boundary. The budget is enforced AFTER reranking — reranking may reorder results so that a truncated high-ranked memory displaces a full lower-ranked one. `noise_caps` in the bench output counts capsules that scored below the noise floor — they consume budget without contributing signal. Lower noise_caps = tighter retrieval. (context: Kimetsu capsule budget enforcement and noise floor interaction.)" + }, + { + "ce": 0.021808089688420296, + "key": "kimetsu-query-stemming", + "rank_score": 0.820538341999054, + "text": "project:fact - [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression. The lexical floor (`min_lexical_coverage`) requires at least N stemmed query tokens to match in any retrieved document — this prevents high-semantic-score but lexically-unrelated documents from dominating. Stemming is applied only when the query has >= 3 tokens; short queries skip it. (context: Kimetsu retrieval — query-side stemming implementation.)" + }, + { + "ce": 0.0000798082837718539, + "key": "mcp-schema-validation", + "rank_score": 0.7166203856468201, + "text": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array — omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error. Use `serde(default)` for optional fields. When adding a new tool, check the schema in the tools/list response manually by piping a JSON-RPC tools/list request to `./kimetsu mcp`. (context: Kimetsu MCP tool schema — required field validation.)" + }, + { + "ce": 0.0027021963614970446, + "key": "mcp-tool-timeouts", + "rank_score": 0.7032174468040466, + "text": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking — in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize — keep it in a process-global `OnceLock`). The reranker adds another 200-800ms; jina-tiny is the fastest. If tool calls are still slow, log the per-stage latency with `tracing::info!` at DEBUG level and profile under load. (context: Kimetsu MCP tool latency optimization.)" + } + ], + "delivered": [], + "excluded_gold": [ + { + "ce": null, + "key": "kimetsu-rerank-pool" + } + ], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.6835663914680481 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "CARGO_INCREMENTAL=0 in CI prevents a class of spurious compilation errors", + "relevant": [ + "cargo-incremental-cache-corruption" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999802112579346, + "key": "cargo-incremental-cache-corruption", + "rank_score": 0.7639959454536438, + "text": "project:fact - [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase. Fix: `cargo clean` then rebuild. Adding `CARGO_INCREMENTAL=0` to CI matrices prevents this class of false failures. (context: Kimetsu development — spurious type mismatch errors after branch switches.)" + }, + { + "ce": 0.018704814836382866, + "key": "git-line-endings-windows", + "rank_score": 0.4286561608314514, + "text": "project:fact - [tags: git line-endings windows crlf autocrlf] On Windows, `core.autocrlf=true` (git's default for Windows installs) converts LF to CRLF on checkout and CRLF to LF on commit. This causes spurious diffs when files are edited on Windows then committed — the content is identical but the line endings differ in the index vs the working tree. Fix: set `core.autocrlf=false` and `.gitattributes` with `* text=auto eol=lf` for the repo. For Rust projects, all source files should be LF; only Windows batch scripts need CRLF. Warn: AV scanners that modify newly written files can re-introduce CRLF in files Rust writes. (context: Kimetsu CI — spurious diffs from Windows CRLF conversion.)" + }, + { + "ce": 0.033863749355077744, + "key": "ci-flaky-quarantine", + "rank_score": 0.3918408155441284, + "text": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal — a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output. NEVER let a flaky test gate the merge queue. For kimetsu timing-based tests (`test_gc_old_runs_deletes_ancient`), apply `#[cfg_attr(ci, ignore)]` and run only in a dedicated slow-CI job. (context: Kimetsu CI flaky test policy.)" + }, + { + "ce": 0.00024404613941442221, + "key": "kimetsu-query-stemming", + "rank_score": 0.3659473657608032, + "text": "project:fact - [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression. The lexical floor (`min_lexical_coverage`) requires at least N stemmed query tokens to match in any retrieved document — this prevents high-semantic-score but lexically-unrelated documents from dominating. Stemming is applied only when the query has >= 3 tokens; short queries skip it. (context: Kimetsu retrieval — query-side stemming implementation.)" + }, + { + "ce": 0.00029995362274348736, + "key": "testing-time-dependent-flakes", + "rank_score": 0.3623645007610321, + "text": "project:fact - [tags: testing time flaky clock mock rust] Tests that depend on wall-clock time are inherently flaky under load (slow CI runners, GC pauses). Abstract time behind a trait (`Clock: Fn() -> SystemTime`) injected at construction, and supply a fake in tests. For tests checking that something happened \"within N seconds\", use a generous multiple of the expected duration (10x is not unreasonable for CI). `std::thread::sleep` in tests is a smell — prefer channel synchronization or a condvar instead of timing-based waits. If you must use sleep, set `KIMETSU_TEST_TIMEOUT_SCALE` to stretch timeouts in slow environments. (context: Kimetsu GC and TTL tests — time-dependent flakes on loaded CI.)" + }, + { + "ce": 0.0006937528378330171, + "key": "onnx-model-cache-paths", + "rank_score": 0.3543938398361206, + "text": "project:fact - [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use. In kimetsu, `KIMETSU_EMBEDDER_CACHE` overrides the path and is forwarded when spawning child bench processes — without forwarding it, each child re-downloads the model. (context: Kimetsu brain bench on CI — model cache path handling in child processes.)" + } + ], + "delivered": [ + "cargo-incremental-cache-corruption" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8392301797866821 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "how do I check whether my Cargo workspace respects the MSRV constraint?", + "relevant": [ + "cargo-msrv" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9985359907150269, + "key": "cargo-msrv", + "rank_score": 0.9549948573112488, + "text": "project:fact - [tags: cargo rust msrv edition compatibility] Set `rust-version` in each `Cargo.toml` to declare the minimum supported Rust version (MSRV). Cargo enforces this with `--check`: `cargo check` fails if the toolchain is older than `rust-version`. Keep MSRV as old as your oldest supported deployment target. When bumping MSRV, update the CI matrix and the workspace root. Common trap: a transitive dep bumps its MSRV, pulling yours up silently — check with `cargo msrv` (cargo-msrv crate) or `cargo tree -e features | grep msrv`. Edition 2021 requires Rust >= 1.56.0. (context: Kimetsu workspace MSRV policy — ensuring it runs on the LTS toolchain.)" + }, + { + "ce": 0.620707631111145, + "key": "cargo-patch-section", + "rank_score": 0.7885444760322571, + "text": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace — including transitive deps — that depend on `my-crate`. Remove the patch before publishing. Using `[replace]` is deprecated since Cargo 0.47; always use `[patch]`. When patching a crate pinned via an exact version specifier, the patch must satisfy that exact version. Use `cargo tree` to confirm the patch is applied. (context: Kimetsu patching upstream rusqlite for a Windows-specific WAL fix.)" + }, + { + "ce": 0.6190821528434753, + "key": "cargo-dev-dep-leak", + "rank_score": 0.7110868692398071, + "text": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates. Run `cargo tree --features ` to trace which crate activated an unexpected feature. (context: Kimetsu testing infra — a dev-dep was activating the embeddings feature in non-test builds.)" + }, + { + "ce": 0.057616669684648514, + "key": "cargo-profile-override", + "rank_score": 0.5919820070266724, + "text": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug. The downside: rebuild time increases for that crate. For overflow-checks, `overflow-checks = false` per package speeds up hot loops. Never disable overflow-checks in release for business-critical data-mutating code. `[profile.release] strip = \"debuginfo\"` reduces binary size with minimal impact on stack traces. (context: Kimetsu dev experience — embedding inference was 10x slower in debug builds.)" + }, + { + "ce": 0.0951559990644455, + "key": "cargo-lockfile-drift", + "rank_score": 0.5720766186714172, + "text": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this — it errors on any lockfile diff. For library crates, `Cargo.lock` is normally gitignored, but for workspace roots with binary crates it should be committed. Use `cargo update --precise ` to pin a specific dep version without touching unrelated entries. (context: Kimetsu workspace lockfile drift after adding kimetsu-remote crate.)" + }, + { + "ce": 0.04220019280910492, + "key": "cargo-target-dir-sharing", + "rank_score": 0.5515825152397156, + "text": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps — use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination. (context: Kimetsu development on Windows with Windows Defender causing intermittent link failures.)" + } + ], + "delivered": [ + "cargo-msrv", + "cargo-patch-section", + "cargo-dev-dep-leak" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8185868263244629 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "rusqlite connection opened but ON DELETE CASCADE cascade never fires", + "relevant": [ + "sqlite-foreign-keys-default-off" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9797621369361877, + "key": "sqlite-foreign-keys-default-off", + "rank_score": 0.9549948573112488, + "text": "project:fact - [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting — every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing. Check your schema with `PRAGMA foreign_key_list(table_name);` and your current setting with `PRAGMA foreign_keys;`. rusqlite does not enable foreign keys automatically. (context: Kimetsu brain schema — memory_tags table has FK to memories table, discovered ON DELETE CASCADE wasn't firing.)" + }, + { + "ce": 0.06196349486708641, + "key": "sqlite-busy-timeout-wal", + "rank_score": 0.5988357067108154, + "text": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch. Set the timeout before any transaction, not inside one — it is a connection-level property. (context: Kimetsu brain writer and reader processes sharing the same SQLite brain database.)" + }, + { + "ce": 0.008352282457053661, + "key": "sqlite-page-size", + "rank_score": 0.5227271318435669, + "text": "project:fact - [tags: sqlite page_size performance rusqlite] SQLite's default page_size is 4096 bytes. For a write-heavy brain database with large BLOB payloads (embedding vectors), raising page_size to 16384 reduces fragmentation and improves sequential scan throughput. `PRAGMA page_size = 16384;` must be set BEFORE the first table is created — changing it on an existing database requires a VACUUM afterward to rebuild all pages. Verify it took effect with `PRAGMA page_size;` after VACUUM. rusqlite's `Connection::open` runs no implicit PRAGMA, so set this in the connection init path. (context: Tuning the kimetsu brain SQLite schema for embedding vector storage.)" + }, + { + "ce": 0.2597297132015228, + "key": "sqlite-prepared-stmt-cache", + "rank_score": 0.4744627773761749, + "text": "project:fact - [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8). The cache key is the SQL string verbatim, so template strings with interpolated values defeat caching — use `?1, ?2` placeholders instead. Calling `prepare_cached` in a tight loop is effectively free after warmup. (context: Kimetsu brain high-throughput ingest path — replacing prepare() with prepare_cached() cut ingest time by ~30%.)" + }, + { + "ce": 0.0034921399783343077, + "key": "pi-openclaw-extension-api", + "rank_score": 0.4310590624809265, + "text": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`. External commands use `pi.exec()` but `node:child_process` spawn also works. Pi has NO MCP so Kimetsu integrates via TS extension + SKILL.md only. (context: Implementing Pi host target for Kimetsu plugin install/status/uninstall system.)\n\nAlso: [tags: kimetsu host-integration pi openclaw bridge] When integrating Kimetsu with an external host agent (Pi, OpenClaw, etc.), VERIFY the host's real plugin/extension API against its actual repo before writing embedded assets — docs-from-memory are frequently wrong. Concretely corrected during v1.0: Pi uses a default-export factory `export default function(pi)` (not `defineExtension`) with lifecycle events `session_start`/`agent_end`/`session_shutdown`; OpenClaw plugin entry is `index.ts` via `definePluginEntry` from `openclaw/plugin-sdk/plugin-entry` + an `openclaw.plugin.json` manifest, with snake_case hook events `agent_turn_prepare`/`agent_end`/`session_end` (NOT colon-delimited). Always make the embedded hook shell-out a silent no-op if the `kimetsu` binary isn't on PATH so a wrong guess never breaks the host. (context: Adding Pi + OpenClaw as BridgeTarget hosts in v1.0.0; the inferred extension/plugin APIs from docs were wrong and had to be corrected against the real repos.)" + }, + { + "ce": 0.0027349996380507946, + "key": "sqlite-partial-index", + "rank_score": 0.44316497445106506, + "text": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query — the planner uses the partial index only when the WHERE clause matches. Verify index usage with `EXPLAIN QUERY PLAN SELECT ...`. Partial indexes are not supported before SQLite 3.8.0; rusqlite's bundled SQLite is always current, but system SQLite on old Debian/Ubuntu may not be. (context: Optimizing kimetsu brain retrieval query over the active-memories subset.)" + } + ], + "delivered": [ + "sqlite-foreign-keys-default-off" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7462239265441895 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "I cannot connect to kimetsu-remote — something about TLS cert validation failed", + "relevant": [ + "http-tls-roots" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.5578561425209045, + "key": "http-tls-roots", + "rank_score": 0.9549949169158936, + "text": "project:fact - [tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle — the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle. Alternatively, add the custom root with `add_root_certificate`. On Linux, the system CA bundle is at `/etc/ssl/certs/ca-certificates.crt`; on Windows it's in the Windows Certificate Store. (context: Kimetsu on a corporate Windows machine with a custom proxy CA.)" + }, + { + "ce": 0.31222063302993774, + "key": "http-proxy-env", + "rank_score": 0.6416454911231995, + "text": "project:fact - [tags: http proxy environment reqwest rust corporate] reqwest respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` environment variables by default (with `default-tls` or `rustls-tls`). In a corporate network, these may redirect traffic through an intercepting proxy that breaks mTLS or adds latency. To disable proxy usage entirely: `reqwest::ClientBuilder::no_proxy()`. On Windows, reqwest does NOT use the system proxy settings (IE/WinInet) — you must set env vars explicitly. `NO_PROXY=127.0.0.1,localhost` prevents proxying loopback traffic (important for kimetsu-remote local dev). (context: Kimetsu provider calls failing behind corporate proxy on Windows.)" + }, + { + "ce": 0.0033067758195102215, + "key": "sqlite-vacuum-wal-checkpoint", + "rank_score": 0.5717190504074097, + "text": "project:fact - [tags: rust sqlite vacuum rusqlite windows] When implementing SQLite VACUUM in rusqlite: VACUUM cannot run inside a transaction. rusqlite's Connection does not hold an implicit transaction, so `conn.execute_batch(\"VACUUM;\")` works directly. After VACUUM, run `PRAGMA wal_checkpoint(TRUNCATE);` before measuring file size — on Windows the WAL file can hold significant space that isn't reflected in the main db file until the checkpoint runs. (context: Implementing kimetsu brain compact (Q8) — SQLite VACUUM + WAL checkpoint for accurate post-compact file size.)" + }, + { + "ce": 0.2490668147802353, + "key": "http-timeout-layering", + "rank_score": 0.5752007365226746, + "text": "project:fact - [tags: http reqwest timeout connect read total rust] reqwest has three distinct timeout knobs: `connect_timeout`, `read_timeout`, and `timeout` (total). They compose: if all three are set, the request fails at whichever fires first. For LLM API calls with streaming responses, `read_timeout` must be larger than the slowest expected token (often 30-60s) while `connect_timeout` can be tight (3-5s). `timeout` should be your SLA ceiling. If you set only `timeout`, a slow connect eats into the overall budget. For kimetsu-remote, set both `connect_timeout(5s)` and `timeout(120s)` — the LLM call is the bottleneck. (context: Kimetsu provider timeouts — request timing out during streaming.)" + }, + { + "ce": 0.16581077873706818, + "key": "kimetsu-eval-fixture-shape", + "rank_score": 0.5049269795417786, + "text": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` — a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases). Keys must be unique across the dataset. The bench currently does not validate keys at load — it fails later with an `unwrap()` on a missing HashMap entry. (context: Kimetsu bench dataset shape and validation.)" + }, + { + "ce": 0.02656012773513794, + "key": "tokio-runtime-in-tests", + "rank_score": 0.5149235725402832, + "text": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests. For sync test code that calls async, use `tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async { ... })`. Never call `block_on` from inside an async function. (context: Kimetsu remote integration tests — nested runtime panic.)" + } + ], + "delivered": [ + "http-tls-roots", + "http-proxy-env" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7400436997413635 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "graceful shutdown fails because in-flight SQLite queries are still running when pool closes", + "relevant": [ + "tokio-shutdown-ordering", + "tokio-blocking-in-async" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.0016660214168950915, + "key": "windows-update-process-locking", + "rank_score": 0.48503759503364563, + "text": "project:fact - [2026-09-05] [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics — mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code. (context: Q2 — kimetsu update preflight for locked binary on Windows)" + }, + { + "ce": 0.0010513804154470563, + "key": "sqlite-busy-timeout-wal", + "rank_score": 0.4884035885334015, + "text": "project:fact - [2026-09-05] [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch. Set the timeout before any transaction, not inside one — it is a connection-level property. (context: Kimetsu brain writer and reader processes sharing the same SQLite brain database.)" + }, + { + "ce": 0.0005551899084821343, + "key": "sqlite-partial-index", + "rank_score": 0.5179747939109802, + "text": "project:fact - [2026-09-05] [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query — the planner uses the partial index only when the WHERE clause matches. Verify index usage with `EXPLAIN QUERY PLAN SELECT ...`. Partial indexes are not supported before SQLite 3.8.0; rusqlite's bundled SQLite is always current, but system SQLite on old Debian/Ubuntu may not be. (context: Optimizing kimetsu brain retrieval query over the active-memories subset.)" + }, + { + "ce": 0.0006796889938414097, + "key": "tokio-select-cancellation", + "rank_score": 0.5867579579353333, + "text": "project:fact - [2026-09-05] [tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded. For correctness, cancelled futures must be cancellation-safe: holding no partially committed state. `tokio::sync::watch::Receiver::changed()` is cancellation-safe; `tokio::sync::mpsc::Sender::send()` is NOT (the item is lost). In kimetsu shutdown, use a `CancellationToken` and `select!` branches that are all cancellation-safe. (context: Kimetsu remote graceful shutdown — race between incoming requests and shutdown signal.)" + }, + { + "ce": 0.9987805485725403, + "key": "tokio-shutdown-ordering", + "rank_score": 0.9549948573112488, + "text": "project:fact - [2026-09-05] [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries — the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks. `axum::Server::with_graceful_shutdown` handles steps 1-2; you must handle 3-5 manually. (context: Kimetsu remote server graceful shutdown implementation.)" + }, + { + "ce": 0.00003969165845774114, + "key": "kimetsu-distiller-config", + "rank_score": 0.445553183555603, + "text": "project:fact - [2026-09-05] [tags: kimetsu distiller harvest config provider] The kimetsu distiller (auto-harvester) uses a SEPARATE provider configuration from the main agent: `distiller.provider`, `distiller.model`, `distiller.api_key`. This allows running the agent on an expensive model (Claude Opus) while harvesting with a cheap model (Claude Haiku). If `distiller.provider` is not set, it inherits `provider`. The distiller runs as a background task triggered by the post-session hook; it reads the session transcript and emits `kimetsu_brain_record` calls. Distiller timeouts are longer (300s) than normal tool calls (60s) because transcript processing can be slow. (context: Kimetsu distiller provider configuration — agent vs harvester model separation.)" + } + ], + "delivered": [ + "tokio-shutdown-ordering" + ], + "excluded_gold": [ + { + "ce": null, + "key": "tokio-blocking-in-async" + } + ], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8091139197349548 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "kimetsu-remote response takes 8 seconds — which stage is slow?", + "relevant": [ + "mcp-tool-timeouts", + "kimetsu-proactive-hooks" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9805353283882141, + "key": "mcp-tool-timeouts", + "rank_score": 0.9549949169158936, + "text": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking — in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize — keep it in a process-global `OnceLock`). The reranker adds another 200-800ms; jina-tiny is the fastest. If tool calls are still slow, log the per-stage latency with `tracing::info!` at DEBUG level and profile under load. (context: Kimetsu MCP tool latency optimization.)" + }, + { + "ce": 0.4605625867843628, + "key": "http-timeout-layering", + "rank_score": 0.7077353596687317, + "text": "project:fact - [tags: http reqwest timeout connect read total rust] reqwest has three distinct timeout knobs: `connect_timeout`, `read_timeout`, and `timeout` (total). They compose: if all three are set, the request fails at whichever fires first. For LLM API calls with streaming responses, `read_timeout` must be larger than the slowest expected token (often 30-60s) while `connect_timeout` can be tight (3-5s). `timeout` should be your SLA ceiling. If you set only `timeout`, a slow connect eats into the overall budget. For kimetsu-remote, set both `connect_timeout(5s)` and `timeout(120s)` — the LLM call is the bottleneck. (context: Kimetsu provider timeouts — request timing out during streaming.)" + }, + { + "ce": 0.01823274791240692, + "key": "http-streaming-bodies", + "rank_score": 0.5603010058403015, + "text": "project:fact - [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding — a chunk may split across frame boundaries. In kimetsu's proxy path, accumulate bytes until `\n\n` (SSE frame delimiter) before parsing the JSON data field. Never assume one `.chunk()` call = one SSE event. (context: Kimetsu remote proxy — streaming LLM responses to the client.)" + }, + { + "ce": 0.30323830246925354, + "key": "testing-time-dependent-flakes", + "rank_score": 0.5490591526031494, + "text": "project:fact - [tags: testing time flaky clock mock rust] Tests that depend on wall-clock time are inherently flaky under load (slow CI runners, GC pauses). Abstract time behind a trait (`Clock: Fn() -> SystemTime`) injected at construction, and supply a fake in tests. For tests checking that something happened \"within N seconds\", use a generous multiple of the expected duration (10x is not unreasonable for CI). `std::thread::sleep` in tests is a smell — prefer channel synchronization or a condvar instead of timing-based waits. If you must use sleep, set `KIMETSU_TEST_TIMEOUT_SCALE` to stretch timeouts in slow environments. (context: Kimetsu GC and TTL tests — time-dependent flakes on loaded CI.)" + }, + { + "ce": 0.002021165331825614, + "key": "kimetsu-write-tools-gate", + "rank_score": 0.5093954205513, + "text": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level — disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients. The feature was introduced to prevent malicious prompts from poisoning the brain. (context: Kimetsu write-tools gate — config-driven security for remote deployments.)" + }, + { + "ce": 0.00525176664814353, + "key": "cargo-feature-unification-embeddings", + "rank_score": 0.5057297348976135, + "text": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli). Diagnostic tell: a test that passes alone but fails only under `cargo test --workspace` AND a brand-new crate was just added = suspect feature unification flipping a sibling crate's behavior. (context: Building the kimetsu-remote crate (HTTP MCP server); its default embeddings feature broke 3 kimetsu-chat retrieval tests only under the full workspace test.)" + } + ], + "delivered": [ + "mcp-tool-timeouts", + "http-timeout-layering", + "testing-time-dependent-flakes" + ], + "excluded_gold": [ + { + "ce": null, + "key": "kimetsu-proactive-hooks" + } + ], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.741588294506073 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "git reflog to rescue accidentally deleted branch", + "relevant": [ + "git-reflog-rescue" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9931837916374207, + "key": "git-reflog-rescue", + "rank_score": 0.9549948573112488, + "text": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone — they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only — remote reflog is not accessible via normal git commands. If you need the remote version, use `git fetch origin +refs/heads/main:refs/heads/main-backup` before a force push. In kimetsu bench development, always create a branch before destructive rebases. (context: Kimetsu bench dataset recovery after accidental hard reset.)" + }, + { + "ce": 0.0022487908136099577, + "key": "git-submodule-pinning", + "rank_score": 0.6249861121177673, + "text": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip — this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version. If a submodule is the kimetsu-bench repo inside the main repo, pin the bench SHA after validating the dataset change. Use `git diff HEAD -- bench` to see the pinned SHA change before committing. (context: Kimetsu bench as a git submodule of the main repo.)" + }, + { + "ce": 0.0017348222900182009, + "key": "git-worktree-brain-isolation", + "rank_score": 0.4580797255039215, + "text": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root — if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain. Always set `KIMETSU_BRAIN_DIR` or use `git_init_boundary` in tests to prevent this. (context: Kimetsu development with git worktrees — test isolation.)" + }, + { + "ce": 0.004844710696488619, + "key": "tokio-select-cancellation", + "rank_score": 0.4195278286933899, + "text": "project:fact - [tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded. For correctness, cancelled futures must be cancellation-safe: holding no partially committed state. `tokio::sync::watch::Receiver::changed()` is cancellation-safe; `tokio::sync::mpsc::Sender::send()` is NOT (the item is lost). In kimetsu shutdown, use a `CancellationToken` and `select!` branches that are all cancellation-safe. (context: Kimetsu remote graceful shutdown — race between incoming requests and shutdown signal.)" + }, + { + "ce": 0.00034236980718560517, + "key": "ci-matrix-explosion", + "rank_score": 0.4110536277294159, + "text": "project:fact - [tags: ci github-actions matrix jobs resources] A CI matrix combining OS (3) x Rust toolchain (3) x features (2) = 18 jobs. Each spawns a runner; at $0.008/min for Ubuntu and $0.016/min for Windows, a 10-minute build costs $2.40 per push. Reduce: test the full matrix only on PRs to main; on feature branches, test only Linux+stable. Use `fail-fast: false` to see all failures, not just the first. Combine related checks (clippy + test) in one job when they share build artifacts. For Windows-specific tests, run only the OS-specific job to reduce cost. (context: Kimetsu CI matrix cost optimization.)" + }, + { + "ce": 0.0007631028420291841, + "key": "sqlite-foreign-keys-default-off", + "rank_score": 0.38325369358062744, + "text": "project:fact - [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting — every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing. Check your schema with `PRAGMA foreign_key_list(table_name);` and your current setting with `PRAGMA foreign_keys;`. rusqlite does not enable foreign keys automatically. (context: Kimetsu brain schema — memory_tags table has FK to memories table, discovered ON DELETE CASCADE wasn't firing.)" + } + ], + "delivered": [ + "git-reflog-rescue" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8326649069786072 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "git submodule --remote advances the pinned SHA unexpectedly", + "relevant": [ + "git-submodule-pinning" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999607801437378, + "key": "git-submodule-pinning", + "rank_score": 0.954994797706604, + "text": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip — this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version. If a submodule is the kimetsu-bench repo inside the main repo, pin the bench SHA after validating the dataset change. Use `git diff HEAD -- bench` to see the pinned SHA change before committing. (context: Kimetsu bench as a git submodule of the main repo.)" + }, + { + "ce": 0.016433240845799446, + "key": "remote-mcp-host-wiring", + "rank_score": 0.4530598223209381, + "text": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal. Derive a stable repo id from the git remote: strip `.git`, scheme (`://`), and `user@`, then map non-alphanumerics to '-' and collapse — so both https://github.com/org/repo.git and git@github.com:org/repo.git -> `github-com-org-repo`. Remote install writes ONLY the MCP entry + instructions (no local hooks — the brain is on the server). Codex/Pi don't get --remote (no remote-MCP / no MCP). (context: R2: implementing `kimetsu plugin install --remote` to wire a host at a kimetsu-remote HTTP MCP server.)" + }, + { + "ce": 0.0759701207280159, + "key": "ci-secrets-masking", + "rank_score": 0.44535598158836365, + "text": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output — but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable. Never reconstruct secrets from parts in step output. For kimetsu bench `--remote` CI runs, `KIMETSU_REMOTE_TOKEN` must be in the repository secrets, not in the workflow YAML. Use `${{ secrets.KIMETSU_REMOTE_TOKEN }}` in env — never `echo ${{ secrets.KIMETSU_REMOTE_TOKEN }}` in a run step. (context: Kimetsu CI remote benchmark — token handling.)" + }, + { + "ce": 0.005518889054656029, + "key": "remote-ingest-split-roots", + "rank_score": 0.419565886259079, + "text": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races. Re-enable kimetsu_brain_ingest_repo in the tool allowlist only when ingest is configured, and INTERCEPT that tools/call in the remote handler (clone+ingest_repo_at_root) before the normal dispatch (which would walk the wrong dir). Hermetic test: git init a temp repo, register url=local path, ingest, then context retrieves the file capsule via FTS (noop embedder). (context: R3c: server-side ingest for kimetsu-remote — cloning repos so file-capsule retrieval works without a local checkout.)" + }, + { + "ce": 0.0018093247199431062, + "key": "cargo-patch-section", + "rank_score": 0.4204961061477661, + "text": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace — including transitive deps — that depend on `my-crate`. Remove the patch before publishing. Using `[replace]` is deprecated since Cargo 0.47; always use `[patch]`. When patching a crate pinned via an exact version specifier, the patch must satisfy that exact version. Use `cargo tree` to confirm the patch is applied. (context: Kimetsu patching upstream rusqlite for a Windows-specific WAL fix.)" + }, + { + "ce": 0.1921437829732895, + "key": "git-reflog-rescue", + "rank_score": 0.45238447189331055, + "text": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone — they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only — remote reflog is not accessible via normal git commands. If you need the remote version, use `git fetch origin +refs/heads/main:refs/heads/main-backup` before a force push. In kimetsu bench development, always create a branch before destructive rebases. (context: Kimetsu bench dataset recovery after accidental hard reset.)" + } + ], + "delivered": [ + "git-submodule-pinning" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.836008608341217 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "axum SSE streaming drops the last event when client disconnects", + "relevant": [ + "http-streaming-bodies", + "tokio-select-cancellation" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.0026216977275907993, + "key": "pi-openclaw-extension-api", + "rank_score": 0.3827792704105377, + "text": "project:fact - [2026-09-05] [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`. External commands use `pi.exec()` but `node:child_process` spawn also works. Pi has NO MCP so Kimetsu integrates via TS extension + SKILL.md only. (context: Implementing Pi host target for Kimetsu plugin install/status/uninstall system.)\n\nAlso: [tags: kimetsu host-integration pi openclaw bridge] When integrating Kimetsu with an external host agent (Pi, OpenClaw, etc.), VERIFY the host's real plugin/extension API against its actual repo before writing embedded assets — docs-from-memory are frequently wrong. Concretely corrected during v1.0: Pi uses a default-export factory `export default function(pi)` (not `defineExtension`) with lifecycle events `session_start`/`agent_end`/`session_shutdown`; OpenClaw plugin entry is `index.ts` via `definePluginEntry` from `openclaw/plugin-sdk/plugin-entry` + an `openclaw.plugin.json` manifest, with snake_case hook events `agent_turn_prepare`/`agent_end`/`session_end` (NOT colon-delimited). Always make the embedded hook shell-out a silent no-op if the `kimetsu` binary isn't on PATH so a wrong guess never breaks the host. (context: Adding Pi + OpenClaw as BridgeTarget hosts in v1.0.0; the inferred extension/plugin APIs from docs were wrong and had to be corrected against the real repos.)" + }, + { + "ce": 0.0006227098638191819, + "key": "tokio-select-cancellation", + "rank_score": 0.39343032240867615, + "text": "project:fact - [2026-09-05] [tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded. For correctness, cancelled futures must be cancellation-safe: holding no partially committed state. `tokio::sync::watch::Receiver::changed()` is cancellation-safe; `tokio::sync::mpsc::Sender::send()` is NOT (the item is lost). In kimetsu shutdown, use a `CancellationToken` and `select!` branches that are all cancellation-safe. (context: Kimetsu remote graceful shutdown — race between incoming requests and shutdown signal.)" + }, + { + "ce": 0.0002831840538419783, + "key": "http-connection-pooling", + "rank_score": 0.3800017237663269, + "text": "project:fact - [2026-09-05] [tags: http reqwest connection-pool keep-alive rust] reqwest's `Client` holds a connection pool; always create ONE `Client` instance and clone it for each handler — cloning is cheap (Arc under the hood). Creating a `Client::new()` per request defeats connection pooling and causes TCP connection exhaustion under load. The default pool settings: max_idle_per_host=usize::MAX (unbounded), idle_timeout=90s. For a kimetsu outbound client (LLM provider), set `pool_max_idle_per_host(5)` to limit idle connections. On Windows, the underlying hyper+winapi stack may not reuse connections as aggressively as on Linux — set `connection_verbose(true)` on the builder to confirm reuse. (context: Kimetsu provider HTTP client — connection pooling best practices.)" + }, + { + "ce": 0.0001864099904196337, + "key": "http-timeout-layering", + "rank_score": 0.3742407262325287, + "text": "project:fact - [2026-09-05] [tags: http reqwest timeout connect read total rust] reqwest has three distinct timeout knobs: `connect_timeout`, `read_timeout`, and `timeout` (total). They compose: if all three are set, the request fails at whichever fires first. For LLM API calls with streaming responses, `read_timeout` must be larger than the slowest expected token (often 30-60s) while `connect_timeout` can be tight (3-5s). `timeout` should be your SLA ceiling. If you set only `timeout`, a slow connect eats into the overall budget. For kimetsu-remote, set both `connect_timeout(5s)` and `timeout(120s)` — the LLM call is the bottleneck. (context: Kimetsu provider timeouts — request timing out during streaming.)" + }, + { + "ce": 0.7567459344863892, + "key": "http-streaming-bodies", + "rank_score": 0.954994797706604, + "text": "project:fact - [2026-09-05] [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding — a chunk may split across frame boundaries. In kimetsu's proxy path, accumulate bytes until `\n\n` (SSE frame delimiter) before parsing the JSON data field. Never assume one `.chunk()` call = one SSE event. (context: Kimetsu remote proxy — streaming LLM responses to the client.)" + }, + { + "ce": 0.000037689747841795906, + "key": "ci-artifact-retention", + "rank_score": 0.3862118422985077, + "text": "project:fact - [2026-09-05] [tags: ci github-actions artifacts retention benchmark] GitHub Actions artifacts are retained for 90 days (default). For benchmark results, use `actions/upload-artifact` with `retention-days: 365` for long-term tracking. The free tier has 500MB storage — per-combo JSON files from kimetsu bench (each ~60KB) add up fast if you upload them on every push. Upload only the summary.md. For regression detection, compare the current run's MRR against the artifact from the last green main build — fetch it with the `actions/download-artifact` action. (context: Kimetsu CI benchmark result tracking.)" + } + ], + "delivered": [ + "http-streaming-bodies" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7409846186637878 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "how do I detect that I am running inside a git worktree vs the main checkout?", + "relevant": [ + "git-worktree-brain-isolation" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9114787578582764, + "key": "git-worktree-brain-isolation", + "rank_score": 0.9549947381019592, + "text": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root — if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain. Always set `KIMETSU_BRAIN_DIR` or use `git_init_boundary` in tests to prevent this. (context: Kimetsu development with git worktrees — test isolation.)" + }, + { + "ce": 0.006190927233546972, + "key": "git-submodule-pinning", + "rank_score": 0.7884877324104309, + "text": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip — this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version. If a submodule is the kimetsu-bench repo inside the main repo, pin the bench SHA after validating the dataset change. Use `git diff HEAD -- bench` to see the pinned SHA change before committing. (context: Kimetsu bench as a git submodule of the main repo.)" + }, + { + "ce": 0.0007744583999738097, + "key": "kimetsu-memory-scopes", + "rank_score": 0.7078049778938293, + "text": "project:fact - [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available — if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope. The `kimetsu_brain_record` MCP tool inherits the scope from the server's launch context. When running kimetsu-remote, all memories are project-scoped to the registered repo-id. (context: Kimetsu memory scope system — project vs user isolation.)" + }, + { + "ce": 0.004400396719574928, + "key": "ci-artifact-retention", + "rank_score": 0.6838398575782776, + "text": "project:fact - [tags: ci github-actions artifacts retention benchmark] GitHub Actions artifacts are retained for 90 days (default). For benchmark results, use `actions/upload-artifact` with `retention-days: 365` for long-term tracking. The free tier has 500MB storage — per-combo JSON files from kimetsu bench (each ~60KB) add up fast if you upload them on every push. Upload only the summary.md. For regression detection, compare the current run's MRR against the artifact from the last green main build — fetch it with the `actions/download-artifact` action. (context: Kimetsu CI benchmark result tracking.)" + }, + { + "ce": 0.0010081538930535316, + "key": "bridge-target-enum-seams", + "rank_score": 0.6055086255073547, + "text": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors. (context: Adding BridgeTarget::OpenClaw host to Kimetsu bridge.rs and main.rs in Workstream C)" + }, + { + "ce": 0.0013903308426961303, + "key": "sqlite-vacuum-wal-checkpoint", + "rank_score": 0.6051194667816162, + "text": "project:fact - [tags: rust sqlite vacuum rusqlite windows] When implementing SQLite VACUUM in rusqlite: VACUUM cannot run inside a transaction. rusqlite's Connection does not hold an implicit transaction, so `conn.execute_batch(\"VACUUM;\")` works directly. After VACUUM, run `PRAGMA wal_checkpoint(TRUNCATE);` before measuring file size — on Windows the WAL file can hold significant space that isn't reflected in the main db file until the checkpoint runs. (context: Implementing kimetsu brain compact (Q8) — SQLite VACUUM + WAL checkpoint for accurate post-compact file size.)" + } + ], + "delivered": [ + "git-worktree-brain-isolation" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7777106761932373 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "ONNX Runtime intra-op threads causing CPU contention during parallel bench", + "relevant": [ + "onnx-ort-threading" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.9999747276306152, + "key": "onnx-ort-threading", + "rank_score": 0.9549946784973145, + "text": "project:fact - [tags: onnx ort thread-pool parallelism cpu] ORT (ONNX Runtime) creates its own inter-op and intra-op thread pools. In a multi-process bench setup, each child inherits these pools and they compete for CPU cores. Set `SessionOptionsBuilder::with_intra_threads(1).with_inter_threads(1)` if you're running many parallel bench processes — this sacrifices per-inference throughput for lower contention. In a single-threaded embedding pipeline, 2-4 intra-op threads are better. For benchmarking, set `ORT_NUM_THREADS=1` via env var to get deterministic single-threaded latency numbers. (context: Kimetsu brain bench multi-process parallelism — ORT thread contention causing inconsistent latency.)" + }, + { + "ce": 0.30381831526756287, + "key": "tokio-blocking-in-async", + "rank_score": 0.5375729203224182, + "text": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking — never call rusqlite directly from an async fn without spawn_blocking. fastembed inference is also blocking (ONNX Runtime is synchronous). The threshold: any operation taking more than 100 microseconds that can't be made async belongs in spawn_blocking. Ignoring this causes tail-latency spikes and request timeouts under load in kimetsu-remote. (context: Kimetsu remote server — SQLite and embedding calls from async handlers.)" + }, + { + "ce": 0.004301246255636215, + "key": "onnx-quantization-drift", + "rank_score": 0.4160427451133728, + "text": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals — cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case. (context: Kimetsu embedding model selection — evaluating jina-v2 int8 vs fp32.)" + }, + { + "ce": 0.00043527709203772247, + "key": "tokio-runtime-in-tests", + "rank_score": 0.4253779649734497, + "text": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests. For sync test code that calls async, use `tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async { ... })`. Never call `block_on` from inside an async function. (context: Kimetsu remote integration tests — nested runtime panic.)" + }, + { + "ce": 0.00012026129115838557, + "key": "git-submodule-pinning", + "rank_score": 0.37925413250923157, + "text": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip — this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version. If a submodule is the kimetsu-bench repo inside the main repo, pin the bench SHA after validating the dataset change. Use `git diff HEAD -- bench` to see the pinned SHA change before committing. (context: Kimetsu bench as a git submodule of the main repo.)" + }, + { + "ce": 0.0001055209941114299, + "key": "testing-serial-vs-parallel", + "rank_score": 0.36873021721839905, + "text": "project:fact - [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`). `cargo nextest` runs each test in a separate process by default, avoiding the problem entirely at the cost of longer startup time. For kimetsu, prefer nextest in CI and accept that `test_env_lock` exists only for `cargo test` compatibility. (context: Kimetsu test suite — env-var mutation in parallel tests.)" + } + ], + "delivered": [ + "onnx-ort-threading", + "tokio-blocking-in-async" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.8168830871582031 + } + ] + }, + { + "floors": { + "abstain": 0.550000011920929, + "lexical": 0.5, + "semantic": 0.3499999940395355 + }, + "query": "what is the right way to supply AWS session token alongside access key and secret?", + "relevant": [ + "aws-credentials-chain", + "aws-sigv4-bedrock-blocking" + ], + "stages": [ + { + "candidates": [ + { + "ce": 0.4265393316745758, + "key": "aws-credentials-chain", + "rank_score": 0.954994797706604, + "text": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually. On Windows, `~/.aws` is `%USERPROFILE%\\.aws` — `std::env::var(\"USERPROFILE\")` to get the path since `~` expansion is shell-level. (context: Kimetsu Bedrock provider credential resolution.)" + }, + { + "ce": 0.13902758061885834, + "key": "aws-sigv4-bedrock-blocking", + "rank_score": 0.9102301001548767, + "text": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed. aws-smithy-runtime-api required as a companion to supply Identity. (context: Implementing BedrockProvider for Kimetsu with blocking reqwest + SigV4 signing, no tokio/aws-sdk)" + }, + { + "ce": 0.19872885942459106, + "key": "aws-instance-metadata", + "rank_score": 0.8334929943084717, + "text": "project:fact - [tags: aws imds instance-metadata ec2 token] The AWS Instance Metadata Service v2 (IMDSv2) requires a session token: PUT `http://169.254.169.254/latest/api/token` with `X-aws-ec2-metadata-token-ttl-seconds: 21600` to get a token, then GET metadata with `X-aws-ec2-metadata-token: `. IMDSv1 (no token) is disabled on hardened instances. The metadata endpoint is only reachable from within EC2 — a connection timeout means you're not on EC2. Set a short connect timeout (200ms) when probing for the metadata service to avoid slow startup on non-EC2 hosts. (context: Kimetsu Bedrock provider — EC2 instance role credential fallback.)" + }, + { + "ce": 0.20642927289009094, + "key": "bedrock-kimetsu-provider", + "rank_score": 0.8017053604125977, + "text": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env. Wire \"bedrock\" into BOTH pipeline.rs provider matches AND the distiller (normalize_distiller_provider + instantiation); the distiller is configured independently so agent-on-Bedrock + harvester-on-direct-Claude works for free. Sign and send the SAME payload bytes; test signing determinism with a fixed SystemTime. (context: Workstream A: adding AWS Bedrock as a provider for the agent + auto-harvester in v1.0.0.)" + }, + { + "ce": 0.0018027330515906215, + "key": "onnx-tokenizer-mismatch", + "rank_score": 0.7047035098075867, + "text": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly — specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings — cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo. Validate by checking a reference embedding against the HuggingFace Python output. (context: Kimetsu custom ONNX reranker loading — wrong tokenizer produced degraded retrieval.)" + }, + { + "ce": 0.0008686722721904516, + "key": "aws-retry-throttling", + "rank_score": 0.6315585374832153, + "text": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with ±25% jitter. Do NOT retry `ValidationException` or `AccessDeniedException` — these are permanent errors. `ModelStreamErrorException` during streaming may be retryable. Log the `x-amzn-requestid` header from failed responses for AWS support debugging. (context: Kimetsu Bedrock provider retry logic.)" + } + ], + "delivered": [ + "aws-credentials-chain" + ], + "excluded_gold": [], + "name": "pool-6", + "pre_skipped": false, + "top_cosine": 0.7239893078804016 + } + ] + } +] \ No newline at end of file diff --git a/docs/audits/2026-09-07-retrieval/development-sweep.json b/docs/audits/2026-09-07-retrieval/development-sweep.json new file mode 100644 index 0000000..9dd17cd --- /dev/null +++ b/docs/audits/2026-09-07-retrieval/development-sweep.json @@ -0,0 +1,122 @@ +[ + { + "cutoff": 0, + "positive_hits": 186, + "positive_queries": 197, + "negative_injections": 11, + "negative_queries": 13, + "recall": 0.916243654822335, + "mrr": 0.9217428087986465, + "equal_class_quality": 0.5490042951971885 + }, + { + "cutoff": 0.1, + "positive_hits": 176, + "positive_queries": 197, + "negative_injections": 8, + "negative_queries": 13, + "recall": 0.8773265651438239, + "mrr": 0.8857868020304569, + "equal_class_quality": 0.6390081999219055 + }, + { + "cutoff": 0.2, + "positive_hits": 176, + "positive_queries": 197, + "negative_injections": 8, + "negative_queries": 13, + "recall": 0.8747884940778341, + "mrr": 0.8857868020304569, + "equal_class_quality": 0.6390081999219055 + }, + { + "cutoff": 0.3, + "positive_hits": 174, + "positive_queries": 197, + "negative_injections": 8, + "negative_queries": 13, + "recall": 0.8587140439932319, + "mrr": 0.8756345177664975, + "equal_class_quality": 0.6339320577899258 + }, + { + "cutoff": 0.4, + "positive_hits": 173, + "positive_queries": 197, + "negative_injections": 7, + "negative_queries": 13, + "recall": 0.8536379018612522, + "mrr": 0.8730964467005076, + "equal_class_quality": 0.6698555251854745 + }, + { + "cutoff": 0.5, + "positive_hits": 170, + "positive_queries": 197, + "negative_injections": 6, + "negative_queries": 13, + "recall": 0.8434856175972928, + "mrr": 0.8604060913705583, + "equal_class_quality": 0.7007028504490433 + }, + { + "cutoff": 0.55, + "positive_hits": 170, + "positive_queries": 197, + "negative_injections": 5, + "negative_queries": 13, + "recall": 0.8434856175972928, + "mrr": 0.8604060913705583, + "equal_class_quality": 0.7391643889105819 + }, + { + "cutoff": 0.6, + "positive_hits": 168, + "positive_queries": 197, + "negative_injections": 5, + "negative_queries": 13, + "recall": 0.8333333333333335, + "mrr": 0.850253807106599, + "equal_class_quality": 0.7340882467786021 + }, + { + "cutoff": 0.7, + "positive_hits": 166, + "positive_queries": 197, + "negative_injections": 3, + "negative_queries": 13, + "recall": 0.8231810490693741, + "mrr": 0.8401015228426396, + "equal_class_quality": 0.8059351815696993 + }, + { + "cutoff": 0.8, + "positive_hits": 160, + "positive_queries": 197, + "negative_injections": 2, + "negative_queries": 13, + "recall": 0.7952622673434857, + "mrr": 0.8096446700507615, + "equal_class_quality": 0.8291682936352986 + }, + { + "cutoff": 0.9, + "positive_hits": 154, + "positive_queries": 197, + "negative_injections": 1, + "negative_queries": 13, + "recall": 0.7707275803722504, + "mrr": 0.7791878172588832, + "equal_class_quality": 0.8524014057008981 + }, + { + "cutoff": 0.95, + "positive_hits": 147, + "positive_queries": 197, + "negative_injections": 1, + "negative_queries": 13, + "recall": 0.7360406091370558, + "mrr": 0.7436548223350253, + "equal_class_quality": 0.8346349082389691 + } +] diff --git a/docs/audits/2026-09-07-retrieval/development/1-baseline.json b/docs/audits/2026-09-07-retrieval/development/1-baseline.json new file mode 100644 index 0000000..914394a --- /dev/null +++ b/docs/audits/2026-09-07-retrieval/development/1-baseline.json @@ -0,0 +1,6811 @@ +{ + "generated_at": "2026-09-07T03:56:47.1479736Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\tmp-tests\\brainbench-development-100.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "test_env_lock inside with_user_brain_disabled deadlock", + "ranked": [ + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ1D64KNHB9FWWN4YQGH6", + "id": "01M1WZZ7FVH9A5XDGE1DY21CES", + "kind": "memory", + "score": 0.9999488592147828, + "summary": "project:fact - [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure \u2014 `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1227.6536, + "first_query": true, + "server_startup_ms": 99.9376, + "model_text_bytes": 796, + "mcp_result_bytes": 877, + "wire_bytes": 912, + "reported_used_tokens": 877, + "working_set_bytes": 227123200, + "peak_working_set_bytes": 248250368 + }, + { + "query": "why does my test hang after calling with_user_brain_disabled when I also lock test_env_lock?", + "ranked": [ + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ1D64KNHB9FWWN4YQGH6", + "id": "01M1WZZ89WJBP0BHSBEV11T38H", + "kind": "memory", + "score": 0.9990190267562866, + "summary": "project:fact - [2026-09-07] [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure \u2014 `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 939.6312, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 808, + "mcp_result_bytes": 889, + "wire_bytes": 924, + "reported_used_tokens": 889, + "working_set_bytes": 229117952, + "peak_working_set_bytes": 248250368 + }, + { + "query": "ingest_repo_at_root brain_root files_root kimetsu remote", + "ranked": [ + "remote-ingest-split-roots", + "kimetsu-write-tools-gate", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ1EG91C0J27K1WTBHJX3", + "id": "01M1WZZ970NE0MAWXWG29C01MY", + "kind": "memory", + "score": 0.999886393547058, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1WZZ6A18T5J8D9KB9Z9T85D", + "id": "01M1WZZ971ANVGX3FSW9E1MYXK", + "kind": "memory", + "score": 0.8439717888832092, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level \u2014 disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1WZZ1GX3CC0CD5ED0GKG0BA", + "id": "01M1WZZ9701XTYGMDXDRX9C0DC", + "kind": "memory", + "score": 0.8363722562789917, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1059.4972, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2726, + "mcp_result_bytes": 2891, + "wire_bytes": 2926, + "reported_used_tokens": 2891, + "working_set_bytes": 252108800, + "peak_working_set_bytes": 253018112 + }, + { + "query": "why does the remote server index the wrong directory when I run kimetsu brain ingest?", + "ranked": [ + "remote-ingest-split-roots", + "onnx-dim-mismatch" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ1EG91C0J27K1WTBHJX3", + "id": "01M1WZZA8K6NJWQ75F8WW693VQ", + "kind": "memory", + "score": 0.9836117625236512, + "summary": "project:fact - [2026-09-07] [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1WZZ44A5F5ZGR0Y9X4XD8QQ", + "id": "01M1WZZA8KH3ABDBPC2M5KHZHV", + "kind": "memory", + "score": 0.3657674789428711, + "summary": "project:fact - [2026-09-07] [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results \u2014 the ANN index shape mismatch isn't always caught at runtime." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1072.6417999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1800, + "mcp_result_bytes": 1899, + "wire_bytes": 1934, + "reported_used_tokens": 1899, + "working_set_bytes": 257921024, + "peak_working_set_bytes": 258842624 + }, + { + "query": "kimetsu plugin install --remote mcp.json authorization bearer token", + "ranked": [ + "remote-mcp-host-wiring", + "mcp-stdout-protocol" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ1GX3CC0CD5ED0GKG0BA", + "id": "01M1WZZBA5ZH1RMS7KWQXKAHJZ", + "kind": "memory", + "score": 0.999605119228363, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + }, + { + "expansion_handle": "memory:01M1WZZ5CP43ZTFN7JS5HMR0CE", + "id": "01M1WZZBA5VS4MKQV1QY8FB1SC", + "kind": "memory", + "score": 0.3375842869281769, + "summary": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1001.1137000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1472, + "mcp_result_bytes": 1619, + "wire_bytes": 1654, + "reported_used_tokens": 1619, + "working_set_bytes": 258228224, + "peak_working_set_bytes": 259145728 + }, + { + "query": "how do I wire a remote kimetsu brain into Claude Code without storing the token in the config file?", + "ranked": [ + "remote-mcp-host-wiring", + "mcp-tool-naming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ1GX3CC0CD5ED0GKG0BA", + "id": "01M1WZZC9455S48PFRNKKXKCCW", + "kind": "memory", + "score": 0.9963359832763672, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + }, + { + "expansion_handle": "memory:01M1WZZ5HTMVKATVGA5KN68RBM", + "id": "01M1WZZC94RSD73FG4PJ65V481", + "kind": "memory", + "score": 0.831425666809082, + "summary": "project:fact - [tags: mcp tool naming convention kimetsu] MCP tool names must be valid identifiers for all host agents. Claude Code restricts tool names to `[a-zA-Z0-9_-]` and max 64 chars. Use `snake_case` (kimetsu_brain_context, kimetsu_brain_record) \u2014 hyphen is technically allowed but some hosts reject it." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 973.5159, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1454, + "mcp_result_bytes": 1601, + "wire_bytes": 1636, + "reported_used_tokens": 1601, + "working_set_bytes": 258555904, + "peak_working_set_bytes": 259489792 + }, + { + "query": "cargo feature unification kimetsu-brain embeddings fastembed test failure", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-profile-override", + "clap-version-build-flavor" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ1K677X60WYRNNJPYS14", + "id": "01M1WZZD7J25159N6XZE3YWJ0C", + "kind": "memory", + "score": 0.9996790885925292, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1WZZ2VAR23ZNYW4BWEJDVMH", + "id": "01M1WZZD7JRDD6MRSJEQV0HQZG", + "kind": "memory", + "score": 0.9923595786094666, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1WZZ1ZVYFK1XCYW5NJ8DGV6", + "id": "01M1WZZD7J913KFXH3JTTZBG16", + "kind": "memory", + "score": 0.585203230381012, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 962.295, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2387, + "mcp_result_bytes": 2524, + "wire_bytes": 2559, + "reported_used_tokens": 2524, + "working_set_bytes": 259858432, + "peak_working_set_bytes": 260780032 + }, + { + "query": "my integration tests pass in isolation but break when I run cargo test --workspace \u2014 embedder changed?", + "ranked": [ + "cargo-feature-unification-embeddings", + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ1K677X60WYRNNJPYS14", + "id": "01M1WZZE5Y79FRDX7EMQQV73K9", + "kind": "memory", + "score": 0.9943140745162964, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1WZZ1YV2PYW23FMEBR45V15", + "id": "01M1WZZE5XH1CK14Q0T823ZJAF", + "kind": "memory", + "score": 0.31398114562034607, + "summary": "project:fact - [2026-09-07] [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1032.1429, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1714, + "mcp_result_bytes": 1817, + "wire_bytes": 1852, + "reported_used_tokens": 1817, + "working_set_bytes": 260366336, + "peak_working_set_bytes": 261287936 + }, + { + "query": "build_anthropic_body bedrock-2023-05-31 InvokeModel blocking reqwest", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ1MW1QYZSCVN2RX0C797", + "id": "01M1WZZF6A6KMQHSJDZN5DPCE0", + "kind": "memory", + "score": 0.9973788261413574, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1WZZ1VNAZKMYF51RBAAKDDG", + "id": "01M1WZZF6BNQG7QSMDSHPHDN3H", + "kind": "memory", + "score": 0.6916899085044861, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 833.6791999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2193, + "mcp_result_bytes": 2320, + "wire_bytes": 2356, + "reported_used_tokens": 2320, + "working_set_bytes": 260767744, + "peak_working_set_bytes": 261681152 + }, + { + "query": "how do I add AWS Bedrock as a model provider in Kimetsu without pulling in the aws-sdk?", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-region-resolution", + "aws-credentials-chain", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ1MW1QYZSCVN2RX0C797", + "id": "01M1WZZG0NY1GFSPTFNM1ME67X", + "kind": "memory", + "score": 0.9998898506164552, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1WZZ5P307DHRY74HHY467GP", + "id": "01M1WZZG0N1C7MX6PG57DP3E6W", + "kind": "memory", + "score": 0.995676338672638, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1WZZ5MGVMP2RVNC21FBGSEM", + "id": "01M1WZZG0NGAVAXMS5W0HT2DCX", + "kind": "memory", + "score": 0.987064242362976, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + }, + { + "expansion_handle": "memory:01M1WZZ1VNAZKMYF51RBAAKDDG", + "id": "01M1WZZG0N2T8N830KFKC6A6PD", + "kind": "memory", + "score": 0.9493880867958068, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1009.6151, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3455, + "mcp_result_bytes": 3618, + "wire_bytes": 3654, + "reported_used_tokens": 3618, + "working_set_bytes": 269103104, + "peak_working_set_bytes": 270020608 + }, + { + "query": "BridgeTarget enum seams plugin_install_inner plugin_status_inner resolve_setup_hosts", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ1Q6GK5HGXX12DMJGNPG", + "id": "01M1WZZGZM4YB6FY3S9JJZQ2PD", + "kind": "memory", + "score": 0.9997583031654358, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 839.8252, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1060, + "mcp_result_bytes": 1141, + "wire_bytes": 1177, + "reported_used_tokens": 1141, + "working_set_bytes": 278757376, + "peak_working_set_bytes": 279670784 + }, + { + "query": "I added a new host to the bridge enum but cargo gives me compile errors in five different match arms \u2014 what did I miss?", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ1Q6GK5HGXX12DMJGNPG", + "id": "01M1WZZHSWE3BS2TJ5PXZC2MAB", + "kind": "memory", + "score": 0.9977060556411744, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1055.3078, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1059, + "mcp_result_bytes": 1140, + "wire_bytes": 1176, + "reported_used_tokens": 1140, + "working_set_bytes": 279228416, + "peak_working_set_bytes": 280141824 + }, + { + "query": "Pi extension factory defineExtension agent_end session_shutdown kimetsu.ts", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ1RGKJFG9SJC46KXFY3S", + "id": "01M1WZZJV1R3TA7FYZGH35NHRE", + "kind": "memory", + "score": 0.9990354776382446, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1055.8118000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 804, + "mcp_result_bytes": 893, + "wire_bytes": 929, + "reported_used_tokens": 893, + "working_set_bytes": 279687168, + "peak_working_set_bytes": 280600576 + }, + { + "query": "how does Pi (earendil-works/pi) load plugins and what lifecycle hooks does it expose?", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ1RGKJFG9SJC46KXFY3S", + "id": "01M1WZZKVXG9KKXKP124Y9TSTC", + "kind": "memory", + "score": 0.9934834837913512, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1066.2179, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 803, + "mcp_result_bytes": 892, + "wire_bytes": 928, + "reported_used_tokens": 892, + "working_set_bytes": 279883776, + "peak_working_set_bytes": 280793088 + }, + { + "query": "aws-sigv4 SigningParams apply_to_request_http1x reqwest sign-http", + "ranked": [ + "aws-sigv4-bedrock-blocking", + "aws-presigned-urls", + "bedrock-kimetsu-provider", + "aws-credentials-chain" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ1VNAZKMYF51RBAAKDDG", + "id": "01M1WZZMXK7M62EZC5AZNM24CK", + "kind": "memory", + "score": 0.9995608925819396, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1WZZ5RRGGVC9C9M0RSEG4PW", + "id": "01M1WZZMXKP51BJ0SZ11JGM8FZ", + "kind": "memory", + "score": 0.984916627407074, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + }, + { + "expansion_handle": "memory:01M1WZZ1MW1QYZSCVN2RX0C797", + "id": "01M1WZZMXK4M7V7XYKW31Q8HTR", + "kind": "memory", + "score": 0.983895778656006, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1WZZ5MGVMP2RVNC21FBGSEM", + "id": "01M1WZZMXKJ6W8MJS7R43AT6NS", + "kind": "memory", + "score": 0.8592692017555237, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 851.6025999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3507, + "mcp_result_bytes": 3670, + "wire_bytes": 3706, + "reported_used_tokens": 3670, + "working_set_bytes": 279949312, + "peak_working_set_bytes": 280850432 + }, + { + "query": "how do I sign a Bedrock InvokeModel request with aws-sigv4 in blocking Rust?", + "ranked": [ + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider", + "aws-region-resolution", + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ1VNAZKMYF51RBAAKDDG", + "id": "01M1WZZNR50153EJT6Y12XRK61", + "kind": "memory", + "score": 0.9998323917388916, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1WZZ1MW1QYZSCVN2RX0C797", + "id": "01M1WZZNR585YZH8W6WGG0A22V", + "kind": "memory", + "score": 0.9970844388008118, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1WZZ5P307DHRY74HHY467GP", + "id": "01M1WZZNR53JGG2QXRYF8HNWNP", + "kind": "memory", + "score": 0.9468621611595154, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1WZZ5RRGGVC9C9M0RSEG4PW", + "id": "01M1WZZNR54A4GS3MMBSPGZKYZ", + "kind": "memory", + "score": 0.9210098385810852, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 992.3118, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3434, + "mcp_result_bytes": 3597, + "wire_bytes": 3633, + "reported_used_tokens": 3597, + "working_set_bytes": 280616960, + "peak_working_set_bytes": 281534464 + }, + { + "query": "KIMETSU_RUNS_GC env opt-out TraceWriter create gc_old_runs caller", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ1XS4BZ2YB5SEZY0ZB5F", + "id": "01M1WZZPQ4EVZ1V9WTK7FVRSGB", + "kind": "memory", + "score": 0.999936580657959, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 943.4477, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 761, + "mcp_result_bytes": 842, + "wire_bytes": 878, + "reported_used_tokens": 842, + "working_set_bytes": 281399296, + "peak_working_set_bytes": 282312704 + }, + { + "query": "where should I put the KIMETSU_RUNS_GC=0 guard \u2014 inside the GC function or at the call site?", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ1XS4BZ2YB5SEZY0ZB5F", + "id": "01M1WZZQMKN6806TK7AYX52ZT5", + "kind": "memory", + "score": 0.9971211552619934, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1044.7333, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 762, + "mcp_result_bytes": 843, + "wire_bytes": 879, + "reported_used_tokens": 843, + "working_set_bytes": 282148864, + "peak_working_set_bytes": 283070464 + }, + { + "query": "git_init_boundary ProjectPaths::discover temp dir user brain isolation", + "ranked": [ + "init-project-git-boundary", + "git-worktree-brain-isolation", + "testing-temp-dirs-ci", + "kimetsu-memory-scopes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ1YV2PYW23FMEBR45V15", + "id": "01M1WZZRP04WT0EKBKBZ5XHGYS", + "kind": "memory", + "score": 0.9997712969779968, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + }, + { + "expansion_handle": "memory:01M1WZZ499RMTPQ3NXPQSC99G9", + "id": "01M1WZZRP01JXMABDBYR3FYNDZ", + "kind": "memory", + "score": 0.9962491393089294, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root \u2014 if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + }, + { + "expansion_handle": "memory:01M1WZZ5401JXZW9Q74D7VHBDA", + "id": "01M1WZZRP0Q3A7P28T5RQ08F0W", + "kind": "memory", + "score": 0.9682154655456544, + "summary": "project:fact - [tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure." + }, + { + "expansion_handle": "memory:01M1WZZ6638MCJY1KN4ZANDNJY", + "id": "01M1WZZRP0PY31486ZPP742VR1", + "kind": "memory", + "score": 0.3057229816913605, + "summary": "project:fact - [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available \u2014 if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 947.7126999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2580, + "mcp_result_bytes": 2715, + "wire_bytes": 2751, + "reported_used_tokens": 2715, + "working_set_bytes": 282509312, + "peak_working_set_bytes": 283422720 + }, + { + "query": "my test calls init_project but it writes to the real ~/.kimetsu instead of the temp folder \u2014 why?", + "ranked": [ + "init-project-git-boundary", + "cargo-feature-unification-embeddings", + "testing-fixture-drift", + "tokio-runtime-in-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ1YV2PYW23FMEBR45V15", + "id": "01M1WZZSK70K1D53BNMK3FVGT5", + "kind": "memory", + "score": 0.9995088577270508, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + }, + { + "expansion_handle": "memory:01M1WZZ1K677X60WYRNNJPYS14", + "id": "01M1WZZSK7G15YYF0JKYXZZ165", + "kind": "memory", + "score": 0.7287850975990295, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1WZZ5BM8Y427BTMNJXYD0CF", + "id": "01M1WZZSK7XCWG60QNSJ0ZF2VD", + "kind": "memory", + "score": 0.6596062183380127, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + }, + { + "expansion_handle": "memory:01M1WZZ4HSK9D41PCJ519ETJAR", + "id": "01M1WZZSK7NASA2M1FAPYNPGWK", + "kind": "memory", + "score": 0.3297702968120575, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1073.7688, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2833, + "mcp_result_bytes": 2980, + "wire_bytes": 3016, + "reported_used_tokens": 2980, + "working_set_bytes": 283336704, + "peak_working_set_bytes": 284250112 + }, + { + "query": "clap command version KIMETSU_VERSION_DISPLAY cfg feature embeddings", + "ranked": [ + "clap-version-build-flavor", + "cargo-feature-unification-embeddings" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ1ZVYFK1XCYW5NJ8DGV6", + "id": "01M1WZZTMA0QCFYXC513DW8P1S", + "kind": "memory", + "score": 0.9996613264083862, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + }, + { + "expansion_handle": "memory:01M1WZZ1K677X60WYRNNJPYS14", + "id": "01M1WZZTMAWZYDRD0Z4RMXYSQ8", + "kind": "memory", + "score": 0.3973360061645508, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 825.3266, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1922, + "mcp_result_bytes": 2041, + "wire_bytes": 2077, + "reported_used_tokens": 2041, + "working_set_bytes": 283549696, + "peak_working_set_bytes": 284454912 + }, + { + "query": "how do I show the build flavor (lean vs embeddings) in the kimetsu --version output?", + "ranked": [ + "clap-version-build-flavor", + "cargo-feature-unification-embeddings", + "onnx-quantization-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ1ZVYFK1XCYW5NJ8DGV6", + "id": "01M1WZZVEEV6DJCHTV6X9KE0ZQ", + "kind": "memory", + "score": 0.9978312849998474, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + }, + { + "expansion_handle": "memory:01M1WZZ1K677X60WYRNNJPYS14", + "id": "01M1WZZVEE0JJQEM4HQR8AXBR4", + "kind": "memory", + "score": 0.8926984667778015, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1WZZ3ZENJB98071GPSSCBFP", + "id": "01M1WZZVEETQ4XR3JF7PMQG8EM", + "kind": "memory", + "score": 0.8877003192901611, + "summary": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals \u2014 cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1092.4807999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2672, + "mcp_result_bytes": 2809, + "wire_bytes": 2845, + "reported_used_tokens": 2809, + "working_set_bytes": 283889664, + "peak_working_set_bytes": 284807168 + }, + { + "query": "Harbor pyiceberg os.getcwd stale WSL2 DrvFs worker-result subprocess re-exec", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ21BQJHJHRF425A0WP7Q", + "id": "01M1WZZWH3Z7BBFP1CXNJE0GMF", + "kind": "memory", + "score": 0.9998155236244202, + "summary": "project:fact - [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1110.6077, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1026, + "mcp_result_bytes": 1107, + "wire_bytes": 1143, + "reported_used_tokens": 1107, + "working_set_bytes": 284069888, + "peak_working_set_bytes": 284979200 + }, + { + "query": "why does my kbench sweep crash after the first trial with 'result.json missing' on WSL2?", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ21BQJHJHRF425A0WP7Q", + "id": "01M1WZZXKMB28KMS8K0DM0F8BJ", + "kind": "memory", + "score": 0.998451828956604, + "summary": "project:fact - [2026-09-07] [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1098.3118, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1038, + "mcp_result_bytes": 1119, + "wire_bytes": 1155, + "reported_used_tokens": 1119, + "working_set_bytes": 284315648, + "peak_working_set_bytes": 285237248 + }, + { + "query": "rusqlite VACUUM transaction WAL checkpoint wal_checkpoint TRUNCATE", + "ranked": [ + "sqlite-vacuum-wal-checkpoint", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ22XF3D07VCBYV74PHN4", + "id": "01M1WZZYN8CFERVP04X64R7G6W", + "kind": "memory", + "score": 0.9996871948242188, + "summary": "project:fact - [tags: rust sqlite vacuum rusqlite windows] When implementing SQLite VACUUM in rusqlite: VACUUM cannot run inside a transaction. rusqlite's Connection does not hold an implicit transaction, so `conn.execute_batch(\"VACUUM;\")` works directly. After VACUUM, run `PRAGMA wal_checkpoint(TRUNCATE);` before measuring file size \u2014 on Windows the WAL file can hold significant space that isn't reflected in the main db file until the checkpoint runs." + }, + { + "expansion_handle": "memory:01M1WZZ2A8XJX26X64WAX094EF", + "id": "01M1WZZYN854XHSBQ1WR53JK87", + "kind": "memory", + "score": 0.5274003744125366, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 840.5993000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1507, + "mcp_result_bytes": 1610, + "wire_bytes": 1646, + "reported_used_tokens": 1610, + "working_set_bytes": 284557312, + "peak_working_set_bytes": 285458432 + }, + { + "query": "my SQLite VACUUM reports the file shrank but the disk usage stayed the same \u2014 Windows WAL?", + "ranked": [ + "sqlite-vacuum-wal-checkpoint", + "sqlite-wal-network-drive" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ22XF3D07VCBYV74PHN4", + "id": "01M1WZZZG2RRVHQDXBAZR6PYVT", + "kind": "memory", + "score": 0.9155893921852112, + "summary": "project:fact - [tags: rust sqlite vacuum rusqlite windows] When implementing SQLite VACUUM in rusqlite: VACUUM cannot run inside a transaction. rusqlite's Connection does not hold an implicit transaction, so `conn.execute_batch(\"VACUUM;\")` works directly. After VACUUM, run `PRAGMA wal_checkpoint(TRUNCATE);` before measuring file size \u2014 on Windows the WAL file can hold significant space that isn't reflected in the main db file until the checkpoint runs." + }, + { + "expansion_handle": "memory:01M1WZZ2DHWWPARX2BHC1Y0VCX", + "id": "01M1WZZZG2VBXER7KGE39W4MWZ", + "kind": "memory", + "score": 0.902395486831665, + "summary": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1083.7886, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1357, + "mcp_result_bytes": 1460, + "wire_bytes": 1496, + "reported_used_tokens": 1460, + "working_set_bytes": 284917760, + "peak_working_set_bytes": 285835264 + }, + { + "query": "add_memory import dedup seen_ids snapshot pre-existing active memory IDs", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ23XYD7MWE2X5XH68JZ7", + "id": "01M1X000HRFCMFFK3YE6X6NNP4", + "kind": "memory", + "score": 0.9999133348464966, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount \u2014 both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 938.0889, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 966, + "mcp_result_bytes": 1047, + "wire_bytes": 1083, + "reported_used_tokens": 1047, + "working_set_bytes": 284954624, + "peak_working_set_bytes": 285863936 + }, + { + "query": "brain import re-imports the same JSON file but the deduplication counter is wrong \u2014 why?", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ23XYD7MWE2X5XH68JZ7", + "id": "01M1X001FXJK9HSMRP942FJ12Z", + "kind": "memory", + "score": 0.9254016876220704, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount \u2014 both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1024.755, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 965, + "mcp_result_bytes": 1046, + "wire_bytes": 1082, + "reported_used_tokens": 1046, + "working_set_bytes": 285069312, + "peak_working_set_bytes": 285986816 + }, + { + "query": "toml::from_str Value parse document unexpected content str.parse", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ2580T5T548TEDG08EKK", + "id": "01M1X002FCMXT4K9PBNBWBM6DX", + "kind": "memory", + "score": 0.9991866946220398, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 891.4822, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 734, + "mcp_result_bytes": 815, + "wire_bytes": 851, + "reported_used_tokens": 815, + "working_set_bytes": 285081600, + "peak_working_set_bytes": 285995008 + }, + { + "query": "how do I parse a TOML configuration file into a toml::Value in toml 0.9?", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ2580T5T548TEDG08EKK", + "id": "01M1X003B392P4TFCJGPG0F29H", + "kind": "memory", + "score": 0.9992641806602478, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1021.1493, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 733, + "mcp_result_bytes": 814, + "wire_bytes": 850, + "reported_used_tokens": 814, + "working_set_bytes": 285233152, + "peak_working_set_bytes": 286146560 + }, + { + "query": "CIM CreationDate DMTF WMI ps etimes started_at assess_mcp_skew", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ26EP3QDDE71Z8HBYTF9", + "id": "01M1X004AR6AMK4WA6TV4GMNZW", + "kind": "memory", + "score": 0.9957948923110962, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 817.9295, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 924, + "mcp_result_bytes": 1013, + "wire_bytes": 1049, + "reported_used_tokens": 1013, + "working_set_bytes": 285294592, + "peak_working_set_bytes": 286199808 + }, + { + "query": "how do I read a process start time on both Windows and Linux in pure Rust?", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ26EP3QDDE71Z8HBYTF9", + "id": "01M1X00552237WZYBXM1CMHWV1", + "kind": "memory", + "score": 0.99687659740448, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1081.0495, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 921, + "mcp_result_bytes": 1010, + "wire_bytes": 1046, + "reported_used_tokens": 1010, + "working_set_bytes": 285306880, + "peak_working_set_bytes": 286232576 + }, + { + "query": "processes_locking_target decide_preflight_action BufRead Write update.rs", + "ranked": [ + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ27WAVWVRRC3DCDD6JTV", + "id": "01M1X0066MXYCP3MT5RRNQBTAD", + "kind": "memory", + "score": 0.9995336532592772, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 895.2646, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1133, + "mcp_result_bytes": 1214, + "wire_bytes": 1250, + "reported_used_tokens": 1214, + "working_set_bytes": 285327360, + "peak_working_set_bytes": 286244864 + }, + { + "query": "how should I reuse the existing process enumerator in the update preflight check to avoid a second PowerShell query?", + "ranked": [ + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ27WAVWVRRC3DCDD6JTV", + "id": "01M1X0072RG11MHRJB4QP611EB", + "kind": "memory", + "score": 0.9973384737968444, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1081.8601, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1132, + "mcp_result_bytes": 1213, + "wire_bytes": 1249, + "reported_used_tokens": 1213, + "working_set_bytes": 285433856, + "peak_working_set_bytes": 286351360 + }, + { + "query": "cfg_attr windows allow dead_code parse_unix_ps cross-platform tests", + "ranked": [ + "cfg-cross-platform-dead-code", + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ296M2XPY4YBW470F4P4", + "id": "01M1X0084GY6D71YACM1MW5N29", + "kind": "memory", + "score": 0.9999476671218872, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + }, + { + "expansion_handle": "memory:01M1WZZ26EP3QDDE71Z8HBYTF9", + "id": "01M1X0084G3RBK41R0R197V59M", + "kind": "memory", + "score": 0.9764312505722046, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 861.1622, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1518, + "mcp_result_bytes": 1625, + "wire_bytes": 1661, + "reported_used_tokens": 1625, + "working_set_bytes": 285478912, + "peak_working_set_bytes": 286392320 + }, + { + "query": "how do I keep a function that is only called on Unix from triggering dead_code warnings on Windows?", + "ranked": [ + "cfg-cross-platform-dead-code" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ296M2XPY4YBW470F4P4", + "id": "01M1X008ZCD53FDARWAVC6EJ21", + "kind": "memory", + "score": 0.9988092184066772, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1019.9481, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 939, + "reported_used_tokens": 903, + "working_set_bytes": 285605888, + "peak_working_set_bytes": 286519296 + }, + { + "query": "deadlocking a Rust mutex in integration tests", + "ranked": [ + "mutex-deadlock-user-brain-disabled", + "testing-serial-vs-parallel", + "kimetsu-query-stemming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ1D64KNHB9FWWN4YQGH6", + "id": "01M1X009Z9VCY97ZSACG0FVME3", + "kind": "memory", + "score": 0.9997490048408508, + "summary": "project:fact - [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure \u2014 `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + }, + { + "expansion_handle": "memory:01M1WZZ57Z18NV7YTG4DH94FQA", + "id": "01M1X009Z9SBP0N9M40DYJ4A04", + "kind": "memory", + "score": 0.9057517647743224, + "summary": "project:fact - [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`)." + }, + { + "expansion_handle": "memory:01M1WZZ6E1RM4TJDASRHG08A1T", + "id": "01M1X009Z9BSMQGBX571SB112N", + "kind": "memory", + "score": 0.4889622032642365, + "summary": "project:fact - [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1021.1787999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1930, + "mcp_result_bytes": 2063, + "wire_bytes": 2099, + "reported_used_tokens": 2063, + "working_set_bytes": 285622272, + "peak_working_set_bytes": 286535680 + }, + { + "query": "benchmarking retrieval quality across embedders", + "ranked": [ + "kimetsu-bench-remote-embedder-singleton", + "onnx-quantization-drift", + "cargo-feature-unification-embeddings" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ6GJM9T3GS7347GS9914", + "id": "01M1X00AZ6NMF0AKDG4SPCV5S5", + "kind": "memory", + "score": 0.988014280796051, + "summary": "project:fact - [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval." + }, + { + "expansion_handle": "memory:01M1WZZ3ZENJB98071GPSSCBFP", + "id": "01M1X00AZ6NCS8Y4CZGS2GSRWR", + "kind": "memory", + "score": 0.985597550868988, + "summary": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals \u2014 cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + }, + { + "expansion_handle": "memory:01M1WZZ1K677X60WYRNNJPYS14", + "id": "01M1X00AZ69BD5S2A188DA9AM1", + "kind": "memory", + "score": 0.5341982841491699, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 851.6424000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2537, + "mcp_result_bytes": 2658, + "wire_bytes": 2694, + "reported_used_tokens": 2658, + "working_set_bytes": 285634560, + "peak_working_set_bytes": 286543872 + }, + { + "query": "process memory working set RSS peak measurement Windows", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1028.7170999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 285741056, + "peak_working_set_bytes": 286633984 + }, + { + "query": "cloning a git repository server-side into a managed checkout", + "ranked": [ + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ1EG91C0J27K1WTBHJX3", + "id": "01M1X00CSSFEJDBM7TAF5903QP", + "kind": "memory", + "score": 0.9466677904129028, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 900.3611000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1261, + "mcp_result_bytes": 1342, + "wire_bytes": 1378, + "reported_used_tokens": 1342, + "working_set_bytes": 285745152, + "peak_working_set_bytes": 286650368 + }, + { + "query": "SigV4 signing HTTP requests in Rust", + "ranked": [ + "aws-presigned-urls", + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ5RRGGVC9C9M0RSEG4PW", + "id": "01M1X00DNSEJ6P776FR5NG4ZKT", + "kind": "memory", + "score": 0.9992632269859314, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + }, + { + "expansion_handle": "memory:01M1WZZ1VNAZKMYF51RBAAKDDG", + "id": "01M1X00DNSE0DJ02Y86S2K06E9", + "kind": "memory", + "score": 0.9991399049758912, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1WZZ1MW1QYZSCVN2RX0C797", + "id": "01M1X00DNSZQ1R1HH48FYR1FAV", + "kind": "memory", + "score": 0.9803794622421264, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 0.5, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 964.7932999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2840, + "mcp_result_bytes": 2985, + "wire_bytes": 3021, + "reported_used_tokens": 2985, + "working_set_bytes": 285749248, + "peak_working_set_bytes": 286650368 + }, + { + "query": "cargo test --workspace feature flag changes broke my unit tests", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-dev-dep-leak", + "ci-flaky-quarantine" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ1K677X60WYRNNJPYS14", + "id": "01M1X00EMC2MZCHASVPQZ9P1DH", + "kind": "memory", + "score": 0.997899889945984, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1WZZ2QYQ6TW5R3E8C8F0K12", + "id": "01M1X00EMCBCR5MY4QVYCNQQ10", + "kind": "memory", + "score": 0.9901249408721924, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + }, + { + "expansion_handle": "memory:01M1WZZ62DSH248PKJ5DS9QJP5", + "id": "01M1X00EMCD9WQ0X2GE6JQEBEK", + "kind": "memory", + "score": 0.835382342338562, + "summary": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal \u2014 a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 880.0930999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2383, + "mcp_result_bytes": 2504, + "wire_bytes": 2540, + "reported_used_tokens": 2504, + "working_set_bytes": 285945856, + "peak_working_set_bytes": 286859264 + }, + { + "query": "how do I make pasta carbonara?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 909.1442, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 286101504, + "peak_working_set_bytes": 287014912 + }, + { + "query": "what is the offside rule in football?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 1109.0346, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 286482432, + "peak_working_set_bytes": 287391744 + }, + { + "query": "best way to train for a half marathon", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 1109.8402, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 286560256, + "peak_working_set_bytes": 287481856 + }, + { + "query": "my test passes when I run it alone but fails under cargo test --workspace", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ1K677X60WYRNNJPYS14", + "id": "01M1X00JJ1815T69Y8X92F2M1V", + "kind": "memory", + "score": 0.9907942414283752, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1WZZ2QYQ6TW5R3E8C8F0K12", + "id": "01M1X00JJ1TC2GEY0ZZA0Z8YDX", + "kind": "memory", + "score": 0.986136794090271, + "summary": "project:fact - [2026-09-07] [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1086.3053, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1863, + "mcp_result_bytes": 1966, + "wire_bytes": 2002, + "reported_used_tokens": 1966, + "working_set_bytes": 287186944, + "peak_working_set_bytes": 288104448 + }, + { + "query": "all the project tests started hanging forever after I added my new test", + "ranked": [ + "cargo-feature-unification-embeddings", + "tokio-runtime-in-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ1K677X60WYRNNJPYS14", + "id": "01M1X00KM88MBDCEHYDXW2XM18", + "kind": "memory", + "score": 0.774284839630127, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1WZZ4HSK9D41PCJ519ETJAR", + "id": "01M1X00KM82HMKZT7SXJW8C02Z", + "kind": "memory", + "score": 0.33030807971954346, + "summary": "project:fact - [2026-09-07] [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1041.4851999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1763, + "mcp_result_bytes": 1874, + "wire_bytes": 1910, + "reported_used_tokens": 1874, + "working_set_bytes": 287334400, + "peak_working_set_bytes": 288243712 + }, + { + "query": "my integration test silently wrote memories into my real home brain instead of the temp workspace", + "ranked": [ + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ1YV2PYW23FMEBR45V15", + "id": "01M1X00MMHQXED21SAVJR6P4NW", + "kind": "memory", + "score": 0.9922831654548644, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1022.7598, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 780, + "mcp_result_bytes": 861, + "wire_bytes": 897, + "reported_used_tokens": 861, + "working_set_bytes": 287350784, + "peak_working_set_bytes": 288268288 + }, + { + "query": "where should the env-var opt-out check live for a cleanup feature triggered from a hot code path", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ1XS4BZ2YB5SEZY0ZB5F", + "id": "01M1X00NM34C6FT3R1B8QNA66Y", + "kind": "memory", + "score": 0.9952055215835572, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1058.7466000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 761, + "mcp_result_bytes": 842, + "wire_bytes": 878, + "reported_used_tokens": 842, + "working_set_bytes": 287375360, + "peak_working_set_bytes": 288292864 + }, + { + "query": "the brain database file stays huge on Windows even after deleting most rows", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1081.865, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 287510528, + "peak_working_set_bytes": 288432128 + }, + { + "query": "re-importing the same exported memories file counts them as new instead of deduplicated", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ23XYD7MWE2X5XH68JZ7", + "id": "01M1X00QQ9SMSQ3ZFEAGEVCNVA", + "kind": "memory", + "score": 0.9878425598144532, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount \u2014 both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1034.8757, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 965, + "mcp_result_bytes": 1046, + "wire_bytes": 1082, + "reported_used_tokens": 1046, + "working_set_bytes": 287543296, + "peak_working_set_bytes": 288460800 + }, + { + "query": "a helper function only called on Unix at runtime fails the dead-code lint on the Windows build", + "ranked": [ + "cfg-cross-platform-dead-code", + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ296M2XPY4YBW470F4P4", + "id": "01M1X00RQA6DS8W85FN2ZMJ10D", + "kind": "memory", + "score": 0.9971064925193788, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + }, + { + "expansion_handle": "memory:01M1WZZ27WAVWVRRC3DCDD6JTV", + "id": "01M1X00RQAVTNZGFD6Y4WAX5RR", + "kind": "memory", + "score": 0.427912950515747, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1020.4321999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1728, + "mcp_result_bytes": 1827, + "wire_bytes": 1863, + "reported_used_tokens": 1827, + "working_set_bytes": 287670272, + "peak_working_set_bytes": 288591872 + }, + { + "query": "the second Terminal-Bench trial always crashes even though the first one passes", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ21BQJHJHRF425A0WP7Q", + "id": "01M1X00SQ60BHCJPKJFKSJVK1R", + "kind": "memory", + "score": 0.9963042736053468, + "summary": "project:fact - [2026-09-07] [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1048.619, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1038, + "mcp_result_bytes": 1119, + "wire_bytes": 1155, + "reported_used_tokens": 1119, + "working_set_bytes": 287686656, + "peak_working_set_bytes": 288600064 + }, + { + "query": "how does doctor tell a running MCP server process is older than the kimetsu binary on disk", + "ranked": [ + "kimetsu-daemon-lifecycle", + "process-start-time-cross-platform", + "mcp-env-propagation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ63RSHWZWENZZS7EACQ8", + "id": "01M1X00TRCV5WCKHHANW95WB6D", + "kind": "memory", + "score": 0.9985345602035522, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1WZZ26EP3QDDE71Z8HBYTF9", + "id": "01M1X00TRC4MCCFJBZ3S2ZQ5QD", + "kind": "memory", + "score": 0.9438157677650452, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + }, + { + "expansion_handle": "memory:01M1WZZ5F9J8SA788P3MW0M540", + "id": "01M1X00TRCTAFYV32FYMEBP3P0", + "kind": "memory", + "score": 0.33611738681793213, + "summary": "project:fact - [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment \u2014 changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 0.5, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1076.3205, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1936, + "mcp_result_bytes": 2061, + "wire_bytes": 2097, + "reported_used_tokens": 2061, + "working_set_bytes": 287948800, + "peak_working_set_bytes": 288866304 + }, + { + "query": "the self-update preflight needs the list of running kimetsu processes without re-running the OS query", + "ranked": [ + "windows-update-process-locking", + "kimetsu-daemon-lifecycle" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ27WAVWVRRC3DCDD6JTV", + "id": "01M1X00VSYCE5VN0X3GE7YS4SK", + "kind": "memory", + "score": 0.9972410202026368, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + }, + { + "expansion_handle": "memory:01M1WZZ63RSHWZWENZZS7EACQ8", + "id": "01M1X00VSY1CC6EQ6GW4503BYT", + "kind": "memory", + "score": 0.8902595043182373, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1048.4158, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1658, + "mcp_result_bytes": 1757, + "wire_bytes": 1793, + "reported_used_tokens": 1757, + "working_set_bytes": 287961088, + "peak_working_set_bytes": 288866304 + }, + { + "query": "parsing the WMI DMTF CreationDate timestamp into epoch seconds without extra crates", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ26EP3QDDE71Z8HBYTF9", + "id": "01M1X00WV7ZWDJMA2CWEPVRY8K", + "kind": "memory", + "score": 0.9258026480674744, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1104.7112000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 924, + "mcp_result_bytes": 1013, + "wire_bytes": 1049, + "reported_used_tokens": 1013, + "working_set_bytes": 288223232, + "peak_working_set_bytes": 289136640 + }, + { + "query": "calling Bedrock InvokeModel from blocking reqwest without the aws sdk", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking", + "aws-region-resolution", + "aws-retry-throttling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ1MW1QYZSCVN2RX0C797", + "id": "01M1X00XX878YP63KTN0B3A908", + "kind": "memory", + "score": 0.9991798996925354, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1WZZ1VNAZKMYF51RBAAKDDG", + "id": "01M1X00XX71NEK0XYAWF7J60QC", + "kind": "memory", + "score": 0.999082326889038, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1WZZ5P307DHRY74HHY467GP", + "id": "01M1X00XX8K511PDBZBNH1PE20", + "kind": "memory", + "score": 0.8391201496124268, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1WZZ5QDWAP2895ZJ5T5H7M3", + "id": "01M1X00XX8T41MH1VDCNMAV532", + "kind": "memory", + "score": 0.4906356632709503, + "summary": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with \u00b125% jitter." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1042.5643, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3330, + "mcp_result_bytes": 3509, + "wire_bytes": 3545, + "reported_used_tokens": 3509, + "working_set_bytes": 288235520, + "peak_working_set_bytes": 289153024 + }, + { + "query": "how do I rotate the encryption key protecting the kimetsu brain database", + "ranked": [ + "kimetsu-eval-fixture-shape" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ6HXX18R7HWCVWNBJA54", + "id": "01M1X00YXN5Z490GEBWDW35VHA", + "kind": "memory", + "score": 0.8046634197235107, + "summary": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` \u2014 a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases)." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 1044.1047999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 817, + "mcp_result_bytes": 942, + "wire_bytes": 978, + "reported_used_tokens": 942, + "working_set_bytes": 288243712, + "peak_working_set_bytes": 289153024 + }, + { + "query": "which tokio runtime worker-thread settings does the kimetsu MCP server use", + "ranked": [ + "tokio-blocking-in-async", + "tokio-runtime-in-tests", + "mcp-stdout-protocol" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ4GH1JNHZ64XPEQW4NT3", + "id": "01M1X00ZY50R3CJEQRKKFX5QSN", + "kind": "memory", + "score": 0.9973159432411194, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + }, + { + "expansion_handle": "memory:01M1WZZ4HSK9D41PCJ519ETJAR", + "id": "01M1X00ZY5JEF1BXG60DEHJ320", + "kind": "memory", + "score": 0.8583173155784607, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + }, + { + "expansion_handle": "memory:01M1WZZ5CP43ZTFN7JS5HMR0CE", + "id": "01M1X00ZY6DV3W61FA5P5X2BPA", + "kind": "memory", + "score": 0.8141786456108093, + "summary": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 1065.3795, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1847, + "mcp_result_bytes": 1972, + "wire_bytes": 2008, + "reported_used_tokens": 1972, + "working_set_bytes": 288251904, + "peak_working_set_bytes": 289165312 + }, + { + "query": "how does kimetsu sync memories between two machines over the network", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 1012.9858, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288276480, + "peak_working_set_bytes": 289189888 + }, + { + "query": "recovering a corrupted usearch ANN index after a power loss", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 971.4682, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288288768, + "peak_working_set_bytes": 289202176 + }, + { + "query": "what postgres schema should I use to store kimetsu memories", + "ranked": [ + "kimetsu-memory-scopes", + "testing-fixture-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ6638MCJY1KN4ZANDNJY", + "id": "01M1X012XZZNE4APT6YQMWNHBR", + "kind": "memory", + "score": 0.9890244603157043, + "summary": "project:fact - [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available \u2014 if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope." + }, + { + "expansion_handle": "memory:01M1WZZ5BM8Y427BTMNJXYD0CF", + "id": "01M1X012XZ8ZM9DBGJS2WW3R2M", + "kind": "memory", + "score": 0.8922504782676697, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 1033.6222, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1389, + "mcp_result_bytes": 1488, + "wire_bytes": 1524, + "reported_used_tokens": 1488, + "working_set_bytes": 288296960, + "peak_working_set_bytes": 289202176 + }, + { + "query": "the whole CI job just froze forever with no failure output after my latest test PR", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1029.6732, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288296960, + "peak_working_set_bytes": 289214464 + }, + { + "query": "running the test suite left junk state in my home directory", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1081.5236, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288296960, + "peak_working_set_bytes": 289214464 + }, + { + "query": "I deleted a bunch of old rows but the file on disk is still the same size", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1027.2745, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288313344, + "peak_working_set_bytes": 289230848 + }, + { + "query": "adding one new crate quietly changed how the whole workspace builds", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-lockfile-drift", + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ1K677X60WYRNNJPYS14", + "id": "01M1X016ZWWR463GR2QCWE2JKP", + "kind": "memory", + "score": 0.9941080808639526, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1WZZ2NHJ86ZPHBHATQ212S6", + "id": "01M1X016ZWTF4VEE68M9DYZ3A9", + "kind": "memory", + "score": 0.9717232584953308, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this \u2014 it errors on any lockfile diff." + }, + { + "expansion_handle": "memory:01M1WZZ2QYQ6TW5R3E8C8F0K12", + "id": "01M1X016ZWWN0DAYBHS05F4QRR", + "kind": "memory", + "score": 0.9183088541030884, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1037.3422, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2374, + "mcp_result_bytes": 2495, + "wire_bytes": 2531, + "reported_used_tokens": 2495, + "working_set_bytes": 288342016, + "peak_working_set_bytes": 289263616 + }, + { + "query": "we cannot pull an async runtime into the agent just to talk to AWS", + "ranked": [ + "tokio-blocking-in-async", + "tokio-runtime-in-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ4GH1JNHZ64XPEQW4NT3", + "id": "01M1X0180QCECCW6MAZJ7DQBYF", + "kind": "memory", + "score": 0.7520647644996643, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + }, + { + "expansion_handle": "memory:01M1WZZ4HSK9D41PCJ519ETJAR", + "id": "01M1X0180Q115DZ7XS2GD1QDAS", + "kind": "memory", + "score": 0.7233642935752869, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1059.2492, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1369, + "mcp_result_bytes": 1476, + "wire_bytes": 1512, + "reported_used_tokens": 1476, + "working_set_bytes": 288350208, + "peak_working_set_bytes": 289263616 + }, + { + "query": "users should be able to tell which build variant they installed from the version output", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1057.2743, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288350208, + "peak_working_set_bytes": 289267712 + }, + { + "query": "what gotchas should I expect writing process-inspection code that works on both Windows and Unix?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1037.1798999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288350208, + "peak_working_set_bytes": 289271808 + }, + { + "query": "why might tests behave differently on my machine than in the full CI run?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1039.0159999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288350208, + "peak_working_set_bytes": 289271808 + }, + { + "query": "what do I need to know before wiring kimetsu into a brand new host agent?", + "ranked": [ + "bridge-target-enum-seams", + "kimetsu-daemon-lifecycle", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ1Q6GK5HGXX12DMJGNPG", + "id": "01M1X01C3WEWDVR5Q0W9GN7MXN", + "kind": "memory", + "score": 0.9741999506950378, + "summary": "project:fact - [2026-09-07] [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + }, + { + "expansion_handle": "memory:01M1WZZ63RSHWZWENZZS7EACQ8", + "id": "01M1X01C3W7X7XPKC367J5HWYE", + "kind": "memory", + "score": 0.9637662768363952, + "summary": "project:fact - [2026-09-07] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1WZZ1GX3CC0CD5ED0GKG0BA", + "id": "01M1X01C3WVS8AY6G32NHE8J0K", + "kind": "memory", + "score": 0.4149944484233856, + "summary": "project:fact - [2026-09-07] [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 0.6666666666666666, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1078.8491, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2390, + "mcp_result_bytes": 2555, + "wire_bytes": 2591, + "reported_used_tokens": 2555, + "working_set_bytes": 288350208, + "peak_working_set_bytes": 289271808 + }, + { + "query": "tell me everything relevant to running kimetsu against AWS", + "ranked": [ + "kimetsu-mrr-metric", + "aws-credentials-chain", + "cargo-feature-unification-embeddings", + "kimetsu-eval-fixture-shape" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ6KAP16S8FCEM27T11Y7", + "id": "01M1X01D5EBQTETG3YCQS93QYG", + "kind": "memory", + "score": 0.984548270702362, + "summary": "project:fact - [tags: kimetsu bench mrr recall metrics evaluation] kimetsu bench reports MRR (Mean Reciprocal Rank) and Recall@K. MRR is 1/rank_of_first_relevant_result, averaged across cases; it penalizes models that rank the correct answer 2nd or 3rd. Recall@K is the fraction of cases where at least one relevant answer appears in the top K." + }, + { + "expansion_handle": "memory:01M1WZZ5MGVMP2RVNC21FBGSEM", + "id": "01M1X01D5EK054GY0CSAZV0GR4", + "kind": "memory", + "score": 0.9737622141838074, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + }, + { + "expansion_handle": "memory:01M1WZZ1K677X60WYRNNJPYS14", + "id": "01M1X01D5EW462R2S2R9FAC4ZJ", + "kind": "memory", + "score": 0.9726329445838928, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1WZZ6HXX18R7HWCVWNBJA54", + "id": "01M1X01D5E2KX8VA0BJ3MHET53", + "kind": "memory", + "score": 0.9641559720039368, + "summary": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` \u2014 a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases)." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1054.4881, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2883, + "mcp_result_bytes": 3066, + "wire_bytes": 3102, + "reported_used_tokens": 3066, + "working_set_bytes": 288354304, + "peak_working_set_bytes": 289271808 + }, + { + "query": "ingesting a cloned repo when the brain lives under a different root", + "ranked": [ + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ1EG91C0J27K1WTBHJX3", + "id": "01M1X01E6M691JKHA8AN2JAGM5", + "kind": "memory", + "score": 0.9995300769805908, + "summary": "project:fact - [2026-09-07] [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1005.6444, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1274, + "mcp_result_bytes": 1355, + "wire_bytes": 1391, + "reported_used_tokens": 1355, + "working_set_bytes": 288354304, + "peak_working_set_bytes": 289271808 + }, + { + "query": "streamable-http transport entry for openclaw.json with a bearer token", + "ranked": [ + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ1GX3CC0CD5ED0GKG0BA", + "id": "01M1X01F67QNJWFMW4DCNCN0FC", + "kind": "memory", + "score": 0.9921918511390686, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1034.0763, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 996, + "mcp_result_bytes": 1125, + "wire_bytes": 1161, + "reported_used_tokens": 1125, + "working_set_bytes": 288649216, + "peak_working_set_bytes": 289566720 + }, + { + "query": "serializing ingests with a tokio mutex to avoid checkout races", + "ranked": [ + "remote-ingest-split-roots", + "testing-serial-vs-parallel", + "tokio-select-cancellation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ1EG91C0J27K1WTBHJX3", + "id": "01M1X01G67M2A5BN48C5QQQ4NQ", + "kind": "memory", + "score": 0.9795480966567992, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1WZZ57Z18NV7YTG4DH94FQA", + "id": "01M1X01G67FJSVBMTFMBME5N98", + "kind": "memory", + "score": 0.9425267577171326, + "summary": "project:fact - [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`)." + }, + { + "expansion_handle": "memory:01M1WZZ4K825RN30808GSPCHKA", + "id": "01M1X01G67TTPX7ME9TVFK9058", + "kind": "memory", + "score": 0.5619664192199707, + "summary": "project:fact - [tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1067.9596, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2376, + "mcp_result_bytes": 2493, + "wire_bytes": 2529, + "reported_used_tokens": 2493, + "working_set_bytes": 288653312, + "peak_working_set_bytes": 289574912 + }, + { + "query": "percent-encoding the colon in the bedrock model id for the invoke URL", + "ranked": [ + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ1MW1QYZSCVN2RX0C797", + "id": "01M1X01H7QP0MDZ120W3Z2ZZC2", + "kind": "memory", + "score": 0.8341025710105896, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1004.9203, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1204, + "mcp_result_bytes": 1293, + "wire_bytes": 1329, + "reported_used_tokens": 1293, + "working_set_bytes": 288657408, + "peak_working_set_bytes": 289574912 + }, + { + "query": "deduplicating re-imported memories against pre-existing ids", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ23XYD7MWE2X5XH68JZ7", + "id": "01M1X01J6XMB92JNYWFP1PC7GQ", + "kind": "memory", + "score": 0.9991393089294434, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount \u2014 both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1055.1793, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 966, + "mcp_result_bytes": 1047, + "wire_bytes": 1083, + "reported_used_tokens": 1047, + "working_set_bytes": 288661504, + "peak_working_set_bytes": 289574912 + }, + { + "query": "parsing DMTF datetimes", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ26EP3QDDE71Z8HBYTF9", + "id": "01M1X01K823878XR8MEND654B4", + "kind": "memory", + "score": 0.9934942126274108, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 823.0744, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 924, + "mcp_result_bytes": 1013, + "wire_bytes": 1049, + "reported_used_tokens": 1013, + "working_set_bytes": 288661504, + "peak_working_set_bytes": 289574912 + }, + { + "query": "how should install derive a stable identifier from the git remote URL?", + "ranked": [ + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ1GX3CC0CD5ED0GKG0BA", + "id": "01M1X01M2JDNRMQDX6D1APKAYQ", + "kind": "memory", + "score": 0.98285174369812, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1058.9766, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 995, + "mcp_result_bytes": 1124, + "wire_bytes": 1160, + "reported_used_tokens": 1124, + "working_set_bytes": 288665600, + "peak_working_set_bytes": 289579008 + }, + { + "query": "the secret token must not end up written into the host config file", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1084.1452, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 228, + "mcp_result_bytes": 291, + "wire_bytes": 327, + "reported_used_tokens": 291, + "working_set_bytes": 288669696, + "peak_working_set_bytes": 289587200 + }, + { + "query": "keep the cleanup logic unit-testable without touching environment variables", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1026.3234000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288776192, + "peak_working_set_bytes": 289681408 + }, + { + "query": "how do we stop the server from cloning arbitrary repos clients request?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1023.6116, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 228, + "mcp_result_bytes": 291, + "wire_bytes": 327, + "reported_used_tokens": 291, + "working_set_bytes": 288792576, + "peak_working_set_bytes": 289710080 + }, + { + "query": "make sure a wrong guess about a host plugin API never breaks that host", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ1RGKJFG9SJC46KXFY3S", + "id": "01M1X01R593VGQWN1MVR3BV5ME", + "kind": "memory", + "score": 0.928434193134308, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 904.984, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 803, + "mcp_result_bytes": 892, + "wire_bytes": 928, + "reported_used_tokens": 892, + "working_set_bytes": 288792576, + "peak_working_set_bytes": 289714176 + }, + { + "query": "which wire-format trick lets us reuse the existing Anthropic request builder for AWS?", + "ranked": [ + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ1MW1QYZSCVN2RX0C797", + "id": "01M1X01S19HFNZ09Y4A9PKD50Z", + "kind": "memory", + "score": 0.9748817682266236, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1087.7402, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1203, + "mcp_result_bytes": 1292, + "wire_bytes": 1328, + "reported_used_tokens": 1292, + "working_set_bytes": 288804864, + "peak_working_set_bytes": 289722368 + }, + { + "query": "the self-update froze because something was still holding the executable", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1028.1628, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288804864, + "peak_working_set_bytes": 289722368 + }, + { + "query": "our notes about the extension API turned out wrong once we read the actual repo", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1041.4542, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288804864, + "peak_working_set_bytes": 289722368 + }, + { + "query": "half the benchmark trials die right after the first one finishes", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 997.5309, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288808960, + "peak_working_set_bytes": 289726464 + }, + { + "query": "I need this parser visible to tests on every OS even though only one OS calls it", + "ranked": [ + "cfg-cross-platform-dead-code" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ296M2XPY4YBW470F4P4", + "id": "01M1X01X3285S0A8JG61RPWH24", + "kind": "memory", + "score": 0.36490198969841, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1003.8338, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 939, + "reported_used_tokens": 903, + "working_set_bytes": 288931840, + "peak_working_set_bytes": 289837056 + }, + { + "query": "the config file content refuses to parse even though the TOML looks valid", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ2580T5T548TEDG08EKK", + "id": "01M1X01Y2GX608PZR7CGHNBWD4", + "kind": "memory", + "score": 0.6614054441452026, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1050.6880999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 733, + "mcp_result_bytes": 814, + "wire_bytes": 850, + "reported_used_tokens": 814, + "working_set_bytes": 288931840, + "peak_working_set_bytes": 289849344 + }, + { + "query": "the remote server must refresh its checkout before answering file queries", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1060.7869, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 228, + "mcp_result_bytes": 291, + "wire_bytes": 327, + "reported_used_tokens": 291, + "working_set_bytes": 288931840, + "peak_working_set_bytes": 289849344 + }, + { + "query": "tests must not climb to a parent git repository when resolving project paths", + "ranked": [ + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ1YV2PYW23FMEBR45V15", + "id": "01M1X0204GCJV09VFXM2D5KHQ2", + "kind": "memory", + "score": 0.9839988350868224, + "summary": "project:fact - [2026-09-07] [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1048.9539000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 794, + "mcp_result_bytes": 875, + "wire_bytes": 911, + "reported_used_tokens": 875, + "working_set_bytes": 288931840, + "peak_working_set_bytes": 289849344 + }, + { + "query": "how do I test request signing deterministically when timestamps change every run?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1075.6480999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288931840, + "peak_working_set_bytes": 289849344 + }, + { + "query": "adding a new variant to the host target enum - which places will I forget to update?", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ1Q6GK5HGXX12DMJGNPG", + "id": "01M1X0227ARGMAQ8VSMDM1Y1X4", + "kind": "memory", + "score": 0.885076105594635, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1108.5472, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1058, + "mcp_result_bytes": 1139, + "wire_bytes": 1175, + "reported_used_tokens": 1139, + "working_set_bytes": 288935936, + "peak_working_set_bytes": 289849344 + }, + { + "query": "how do I enable GPU acceleration for kimetsu embedding inference", + "ranked": [ + "mcp-tool-timeouts", + "kimetsu-proactive-hooks" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ5DZ3AYV5QZT44CPHATJ", + "id": "01M1X023A1QFPV5AN139EA53DF", + "kind": "memory", + "score": 0.9826309084892272, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + }, + { + "expansion_handle": "memory:01M1WZZ68TKQ37R82181S467NF", + "id": "01M1X023A14NB2F3145SG2Q1N8", + "kind": "memory", + "score": 0.8807981610298157, + "summary": "project:fact - [tags: kimetsu proactive hooks context injection] kimetsu's proactive context injection runs before each agent turn (pre-turn hook) and injects relevant memories into the system prompt prefix. The hook invocation adds latency to the first token: embedding inference + vector search + reranking + context formatting. On a cold start, this can be 1-3 seconds." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 1068.8271, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1377, + "mcp_result_bytes": 1476, + "wire_bytes": 1512, + "reported_used_tokens": 1476, + "working_set_bytes": 288944128, + "peak_working_set_bytes": 289849344 + }, + { + "query": "how do I throttle kimetsu API spend per month", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 1006.2221, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 289202176, + "peak_working_set_bytes": 290119680 + }, + { + "query": "can the kimetsu brain database be stored in S3 instead of on disk", + "ranked": [ + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ5RRGGVC9C9M0RSEG4PW", + "id": "01M1X025ADX3S3GFZCQ28H001Q", + "kind": "memory", + "score": 0.38596054911613464, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 955.5739, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 875, + "mcp_result_bytes": 956, + "wire_bytes": 992, + "reported_used_tokens": 956, + "working_set_bytes": 289206272, + "peak_working_set_bytes": 290123776 + }, + { + "query": "how do I plug a custom tokenizer into the FTS index", + "ranked": [ + "sqlite-fts5-tokenizer" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ2EQWFZQP2Q94N3RB1Q7", + "id": "01M1X0268FY4MJTPCD7M70W36N", + "kind": "memory", + "score": 0.9691632390022278, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 1047.1471000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 671, + "mcp_result_bytes": 756, + "wire_bytes": 792, + "reported_used_tokens": 756, + "working_set_bytes": 289488896, + "peak_working_set_bytes": 290394112 + }, + { + "query": "what should I check when kimetsu behaves differently on Windows than on Linux?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1037.3596, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 289488896, + "peak_working_set_bytes": 290402304 + }, + { + "query": "what are the moving parts of the kimetsu remote deployment story?", + "ranked": [ + "kimetsu-write-tools-gate", + "ci-secrets-masking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ6A18T5J8D9KB9Z9T85D", + "id": "01M1X0289SX29CATMYKGBMDP16", + "kind": "memory", + "score": 0.9729357361793518, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level \u2014 disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1WZZ600C6G9PAFDBBTQMHHG", + "id": "01M1X0289S92VZ94AMQFYAV800", + "kind": "memory", + "score": 0.8412115573883057, + "summary": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output \u2014 but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1021.9206, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1410, + "mcp_result_bytes": 1509, + "wire_bytes": 1546, + "reported_used_tokens": 1509, + "working_set_bytes": 289492992, + "peak_working_set_bytes": 290410496 + }, + { + "query": "which lessons cover guarding behavior behind environment variables?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 957.5437999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 289492992, + "peak_working_set_bytes": 290410496 + }, + { + "query": "SQLite BUSY error under concurrent writes", + "ranked": [ + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ2A8XJX26X64WAX094EF", + "id": "01M1X02A8G2WK64M1S0R2ZZSWJ", + "kind": "memory", + "score": 0.9978362917900084, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 937.5567, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 898, + "mcp_result_bytes": 979, + "wire_bytes": 1016, + "reported_used_tokens": 979, + "working_set_bytes": 289497088, + "peak_working_set_bytes": 290410496 + }, + { + "query": "SQLite WAL mode breaks when the database is on a network share", + "ranked": [ + "sqlite-wal-network-drive", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ2DHWWPARX2BHC1Y0VCX", + "id": "01M1X02B50VMGN6NF629371Z5Q", + "kind": "memory", + "score": 0.999302864074707, + "summary": "project:fact - [2026-09-07] [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + }, + { + "expansion_handle": "memory:01M1WZZ2A8XJX26X64WAX094EF", + "id": "01M1X02B50C9DP1H4Z0B2ST5FV", + "kind": "memory", + "score": 0.9966553449630736, + "summary": "project:fact - [2026-09-07] [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1068.0718, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1448, + "mcp_result_bytes": 1547, + "wire_bytes": 1584, + "reported_used_tokens": 1547, + "working_set_bytes": 289497088, + "peak_working_set_bytes": 290410496 + }, + { + "query": "my SQLite WAL database causes SQLITE_IOERR_LOCK on a mapped drive", + "ranked": [ + "sqlite-wal-network-drive" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ2DHWWPARX2BHC1Y0VCX", + "id": "01M1X02C695NNJCDFZQ3T2BSKN", + "kind": "memory", + "score": 0.99892657995224, + "summary": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1098.0256, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 748, + "mcp_result_bytes": 829, + "wire_bytes": 866, + "reported_used_tokens": 829, + "working_set_bytes": 289783808, + "peak_working_set_bytes": 290693120 + }, + { + "query": "FTS5 tokenizer configuration for Rust identifiers with underscores", + "ranked": [ + "sqlite-fts5-tokenizer", + "kimetsu-query-stemming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ2EQWFZQP2Q94N3RB1Q7", + "id": "01M1X02D8QH4D1W2SA59B9D4RN", + "kind": "memory", + "score": 0.998104453086853, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + }, + { + "expansion_handle": "memory:01M1WZZ6E1RM4TJDASRHG08A1T", + "id": "01M1X02D8QEF9Y7V5MGP857M02", + "kind": "memory", + "score": 0.7023860812187195, + "summary": "project:fact - [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1032.6597, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1212, + "mcp_result_bytes": 1331, + "wire_bytes": 1368, + "reported_used_tokens": 1331, + "working_set_bytes": 289783808, + "peak_working_set_bytes": 290693120 + }, + { + "query": "I switched the FTS5 tokenizer but search stopped returning results", + "ranked": [ + "sqlite-fts5-tokenizer" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ2EQWFZQP2Q94N3RB1Q7", + "id": "01M1X02E998FT53ZQM66N4NQ8X", + "kind": "memory", + "score": 0.8194089531898499, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1054.7215999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 670, + "mcp_result_bytes": 755, + "wire_bytes": 792, + "reported_used_tokens": 755, + "working_set_bytes": 289783808, + "peak_working_set_bytes": 290693120 + }, + { + "query": "optimal SQLite page size for storing embedding vectors", + "ranked": [ + "sqlite-page-size", + "onnx-dim-mismatch", + "onnx-cosine-vs-dot" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ2FWCJX7GRAM5C23PVAG", + "id": "01M1X02F9YP37KKDGY00FDXYQ6", + "kind": "memory", + "score": 0.9990121126174928, + "summary": "project:fact - [tags: sqlite page_size performance rusqlite] SQLite's default page_size is 4096 bytes. For a write-heavy brain database with large BLOB payloads (embedding vectors), raising page_size to 16384 reduces fragmentation and improves sequential scan throughput. `PRAGMA page_size = 16384;` must be set BEFORE the first table is created \u2014 changing it on an existing database requires a VACUUM afterward to rebuild all pages." + }, + { + "expansion_handle": "memory:01M1WZZ44A5F5ZGR0Y9X4XD8QQ", + "id": "01M1X02F9YE12N06116BWKYVJR", + "kind": "memory", + "score": 0.9881643056869508, + "summary": "project:fact - [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results \u2014 the ANN index shape mismatch isn't always caught at runtime." + }, + { + "expansion_handle": "memory:01M1WZZ432MR78DQYD3HG90V4X", + "id": "01M1X02F9Y9S2Q8R4NYX01G69W", + "kind": "memory", + "score": 0.9425415992736816, + "summary": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing \u2014 double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 969.3054, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1860, + "mcp_result_bytes": 1977, + "wire_bytes": 2014, + "reported_used_tokens": 1977, + "working_set_bytes": 289787904, + "peak_working_set_bytes": 290693120 + }, + { + "query": "ON DELETE CASCADE in SQLite does nothing \u2014 foreign keys not enforced", + "ranked": [ + "sqlite-foreign-keys-default-off" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ2GYG9DC90TEME1XG89Z", + "id": "01M1X02G8DKF6B82RRRZWB6E8K", + "kind": "memory", + "score": 0.9996858835220336, + "summary": "project:fact - [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting \u2014 every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1090.8628, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 736, + "mcp_result_bytes": 817, + "wire_bytes": 854, + "reported_used_tokens": 817, + "working_set_bytes": 289927168, + "peak_working_set_bytes": 290836480 + }, + { + "query": "indexing a JSON metadata column in SQLite without a schema migration", + "ranked": [ + "sqlite-json1-extract", + "testing-fixture-drift", + "onnx-dim-mismatch", + "sqlite-partial-index" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ2HZBAAZWWGV95510A3J", + "id": "01M1X02HBDX72HV31JT506WJV4", + "kind": "memory", + "score": 0.9955366849899292, + "summary": "project:fact - [tags: sqlite json1 json_extract rusqlite] SQLite's json1 extension (built in since 3.38.0) lets you index and query JSONB columns with `json_extract(col, '$.field')`. To create a partial index over a JSON field: `CREATE INDEX idx ON memories (json_extract(metadata, '$.scope')) WHERE json_extract(metadata, '$.scope') IS NOT NULL;`. Use `json_each` for array fields." + }, + { + "expansion_handle": "memory:01M1WZZ5BM8Y427BTMNJXYD0CF", + "id": "01M1X02HBDAS7E95WK65TR5F3S", + "kind": "memory", + "score": 0.8227390646934509, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + }, + { + "expansion_handle": "memory:01M1WZZ44A5F5ZGR0Y9X4XD8QQ", + "id": "01M1X02HBDZR947KX335M2Y9XX", + "kind": "memory", + "score": 0.38374292850494385, + "summary": "project:fact - [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results \u2014 the ANN index shape mismatch isn't always caught at runtime." + }, + { + "expansion_handle": "memory:01M1WZZ2MD5WSD2TWV7422QP9X", + "id": "01M1X02HBD0VEW8P5S68P153GQ", + "kind": "memory", + "score": 0.3276048004627228, + "summary": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query \u2014 the planner uses the partial index only when the WHERE clause matches." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1054.088, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2381, + "mcp_result_bytes": 2516, + "wire_bytes": 2553, + "reported_used_tokens": 2516, + "working_set_bytes": 289951744, + "peak_working_set_bytes": 290861056 + }, + { + "query": "prepare() vs prepare_cached() in rusqlite hot insert loop", + "ranked": [ + "sqlite-prepared-stmt-cache" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ2K6DJK81T9S4NNH7G55", + "id": "01M1X02JBGAZKGJ5M98SJAKPAY", + "kind": "memory", + "score": 0.9993672966957092, + "summary": "project:fact - [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1001.3045000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 689, + "mcp_result_bytes": 770, + "wire_bytes": 807, + "reported_used_tokens": 770, + "working_set_bytes": 289951744, + "peak_working_set_bytes": 290861056 + }, + { + "query": "speed up bulk memory ingest by caching SQL statements", + "ranked": [ + "sqlite-prepared-stmt-cache" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ2K6DJK81T9S4NNH7G55", + "id": "01M1X02KBGF019KXQBS60QWE8P", + "kind": "memory", + "score": 0.9823396801948548, + "summary": "project:fact - [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1033.7736, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 688, + "mcp_result_bytes": 769, + "wire_bytes": 806, + "reported_used_tokens": 769, + "working_set_bytes": 289951744, + "peak_working_set_bytes": 290861056 + }, + { + "query": "partial index on deleted_at IS NULL for faster active memory queries", + "ranked": [ + "sqlite-partial-index" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ2MD5WSD2TWV7422QP9X", + "id": "01M1X02MB8AF209NPAVBFEGMVM", + "kind": "memory", + "score": 0.9988954067230223, + "summary": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query \u2014 the planner uses the partial index only when the WHERE clause matches." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1052.1901, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 794, + "mcp_result_bytes": 875, + "wire_bytes": 912, + "reported_used_tokens": 875, + "working_set_bytes": 289951744, + "peak_working_set_bytes": 290861056 + }, + { + "query": "the brain query is slow because it scans all rows including soft-deleted ones", + "ranked": [ + "sqlite-partial-index" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ2MD5WSD2TWV7422QP9X", + "id": "01M1X02NCD61FZVSPK0CSKSXVD", + "kind": "memory", + "score": 0.5760471224784851, + "summary": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query \u2014 the planner uses the partial index only when the WHERE clause matches." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1072.4029, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 793, + "mcp_result_bytes": 874, + "wire_bytes": 911, + "reported_used_tokens": 874, + "working_set_bytes": 289951744, + "peak_working_set_bytes": 290869248 + }, + { + "query": "Cargo.lock changed unexpectedly after adding a new workspace crate", + "ranked": [ + "cargo-lockfile-drift", + "cargo-feature-unification-embeddings", + "cargo-target-dir-sharing", + "cargo-patch-section" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ2NHJ86ZPHBHATQ212S6", + "id": "01M1X02PDG9EK11J1HP9J091VJ", + "kind": "memory", + "score": 0.9991374015808104, + "summary": "project:fact - [2026-09-07] [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this \u2014 it errors on any lockfile diff." + }, + { + "expansion_handle": "memory:01M1WZZ1K677X60WYRNNJPYS14", + "id": "01M1X02PDG4D549DPERBJ3ST58", + "kind": "memory", + "score": 0.9968542456626892, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1WZZ2S6K8TNKFY1QH1BAHFM", + "id": "01M1X02PDGJ2NGE886K742SJ2Y", + "kind": "memory", + "score": 0.9829630851745604, + "summary": "project:fact - [2026-09-07] [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps \u2014 use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + }, + { + "expansion_handle": "memory:01M1WZZ2WHE6FWSPTX3RTV098G", + "id": "01M1X02PDGC495JW0GD35GAGJ0", + "kind": "memory", + "score": 0.9262890815734864, + "summary": "project:fact - [2026-09-07] [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace \u2014 including transitive deps \u2014 that depend on `my-crate`. Remove the patch before publishing." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 915.1988, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3010, + "mcp_result_bytes": 3153, + "wire_bytes": 3190, + "reported_used_tokens": 3153, + "working_set_bytes": 290078720, + "peak_working_set_bytes": 290996224 + }, + { + "query": "how do I prevent CI from accepting a modified lockfile silently?", + "ranked": [ + "cargo-lockfile-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ2NHJ86ZPHBHATQ212S6", + "id": "01M1X02QA37394SR9PWQTD3B7F", + "kind": "memory", + "score": 0.9125379323959352, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this \u2014 it errors on any lockfile diff." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1051.0711, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 765, + "mcp_result_bytes": 846, + "wire_bytes": 883, + "reported_used_tokens": 846, + "working_set_bytes": 290082816, + "peak_working_set_bytes": 291000320 + }, + { + "query": "build.rs reruns on every incremental build even when nothing changed", + "ranked": [ + "cargo-build-script-rerun" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ2PP58KMGQTQJ1P25J4T", + "id": "01M1X02RB9TWBFCDJMX1A7PRDK", + "kind": "memory", + "score": 0.9996689558029176, + "summary": "project:fact - [2026-09-07] [tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1106.8446000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 698, + "mcp_result_bytes": 779, + "wire_bytes": 816, + "reported_used_tokens": 779, + "working_set_bytes": 290344960, + "peak_working_set_bytes": 291262464 + }, + { + "query": "incremental cargo build is slow because build script runs every time", + "ranked": [ + "cargo-build-script-rerun" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ2PP58KMGQTQJ1P25J4T", + "id": "01M1X02SDXENE20XD7AXC2FY0N", + "kind": "memory", + "score": 0.9978280663490297, + "summary": "project:fact - [tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1057.0, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 685, + "mcp_result_bytes": 766, + "wire_bytes": 803, + "reported_used_tokens": 766, + "working_set_bytes": 290344960, + "peak_working_set_bytes": 291262464 + }, + { + "query": "a dev-dependency is activating an embeddings feature in my production build", + "ranked": [ + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ2QYQ6TW5R3E8C8F0K12", + "id": "01M1X02TEP75EPHAKED62D9VH2", + "kind": "memory", + "score": 0.9944193959236144, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1034.9102, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 931, + "mcp_result_bytes": 1012, + "wire_bytes": 1049, + "reported_used_tokens": 1012, + "working_set_bytes": 290353152, + "peak_working_set_bytes": 291266560 + }, + { + "query": "how do I prevent a test-only feature from bleeding into the non-test compilation?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 993.6117, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 290357248, + "peak_working_set_bytes": 291270656 + }, + { + "query": "linker errors in target/ caused by antivirus holding the exe file", + "ranked": [ + "windows-file-locking-av", + "cargo-target-dir-sharing" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ3P7RTE1G7D4JQV0P44R", + "id": "01M1X02WEH5NWAJDWPQKD9T36S", + "kind": "memory", + "score": 0.9997633099555968, + "summary": "project:fact - [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + }, + { + "expansion_handle": "memory:01M1WZZ2S6K8TNKFY1QH1BAHFM", + "id": "01M1X02WEHS52K8EC1N0PH38KY", + "kind": "memory", + "score": 0.7463976740837097, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps \u2014 use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1069.6945, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1523, + "mcp_result_bytes": 1622, + "wire_bytes": 1659, + "reported_used_tokens": 1622, + "working_set_bytes": 290418688, + "peak_working_set_bytes": 291328000 + }, + { + "query": "Access is denied (os error 5) when linking on Windows \u2014 how do I fix this?", + "ranked": [ + "windows-file-locking-av" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ3P7RTE1G7D4JQV0P44R", + "id": "01M1X02XG1DDB11B8PR4XJWVNP", + "kind": "memory", + "score": 0.9977193474769592, + "summary": "project:fact - [2026-09-07] [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1067.0276999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 769, + "mcp_result_bytes": 850, + "wire_bytes": 887, + "reported_used_tokens": 850, + "working_set_bytes": 290484224, + "peak_working_set_bytes": 291393536 + }, + { + "query": "incremental build broke with a type mismatch after switching branches", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ2TAMD45HZP50Q7AZK21", + "id": "01M1X02YHAZPRKD9FXD76MCJTB", + "kind": "memory", + "score": 0.7971777319908142, + "summary": "project:fact - [2026-09-07] [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1049.9750999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 890, + "mcp_result_bytes": 971, + "wire_bytes": 1008, + "reported_used_tokens": 971, + "working_set_bytes": 290979840, + "peak_working_set_bytes": 291893248 + }, + { + "query": "cargo reports a type error that references a type not in the codebase", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ2TAMD45HZP50Q7AZK21", + "id": "01M1X02ZHNA7XCFCD8P9RDX69K", + "kind": "memory", + "score": 0.7925198078155518, + "summary": "project:fact - [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 991.3356, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 877, + "mcp_result_bytes": 958, + "wire_bytes": 995, + "reported_used_tokens": 958, + "working_set_bytes": 290975744, + "peak_working_set_bytes": 291897344 + }, + { + "query": "compile fastembed at O2 in debug builds to avoid slow embedding inference", + "ranked": [ + "cargo-profile-override", + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ2VAR23ZNYW4BWEJDVMH", + "id": "01M1X030GVN6KC94JFTBJ2E2F4", + "kind": "memory", + "score": 0.9932281374931335, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1WZZ5DZ3AYV5QZT44CPHATJ", + "id": "01M1X030GV0YMZE4CK7Z95753X", + "kind": "memory", + "score": 0.987656831741333, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1059.4589999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1322, + "mcp_result_bytes": 1421, + "wire_bytes": 1458, + "reported_used_tokens": 1421, + "working_set_bytes": 290988032, + "peak_working_set_bytes": 291905536 + }, + { + "query": "override compilation profile for a single crate in a Cargo workspace", + "ranked": [ + "cargo-patch-section", + "cargo-profile-override", + "cargo-target-dir-sharing", + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ2WHE6FWSPTX3RTV098G", + "id": "01M1X031J47YZZ04685VW0WJ01", + "kind": "memory", + "score": 0.9984123706817628, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace \u2014 including transitive deps \u2014 that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1WZZ2VAR23ZNYW4BWEJDVMH", + "id": "01M1X031J4KJFNFKZ2694M1RHJ", + "kind": "memory", + "score": 0.9979992508888244, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1WZZ2S6K8TNKFY1QH1BAHFM", + "id": "01M1X031J4KDZ1EWNJ80Z9HJRY", + "kind": "memory", + "score": 0.9956549406051636, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps \u2014 use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + }, + { + "expansion_handle": "memory:01M1WZZ2QYQ6TW5R3E8C8F0K12", + "id": "01M1X031J4BSJWR9V40QKDMV6Z", + "kind": "memory", + "score": 0.9820712208747864, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 0.5, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1081.1245000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2682, + "mcp_result_bytes": 2821, + "wire_bytes": 2858, + "reported_used_tokens": 2821, + "working_set_bytes": 290988032, + "peak_working_set_bytes": 291909632 + }, + { + "query": "[patch.crates-io] workspace dependency override", + "ranked": [ + "cargo-patch-section", + "cargo-lockfile-drift", + "cargo-dev-dep-leak", + "cargo-target-dir-sharing" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ2WHE6FWSPTX3RTV098G", + "id": "01M1X032KJESPPY64F41S997PR", + "kind": "memory", + "score": 0.9999405145645142, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace \u2014 including transitive deps \u2014 that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1WZZ2NHJ86ZPHBHATQ212S6", + "id": "01M1X032KJWG0E9S0C0RA1WS59", + "kind": "memory", + "score": 0.9975811243057252, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this \u2014 it errors on any lockfile diff." + }, + { + "expansion_handle": "memory:01M1WZZ2QYQ6TW5R3E8C8F0K12", + "id": "01M1X032KJ7BNBDY6QT9VXP3HS", + "kind": "memory", + "score": 0.994149684906006, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + }, + { + "expansion_handle": "memory:01M1WZZ2S6K8TNKFY1QH1BAHFM", + "id": "01M1X032KK43VGM705MG4JKYQ8", + "kind": "memory", + "score": 0.7471600770950317, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps \u2014 use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 829.6623, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2755, + "mcp_result_bytes": 2894, + "wire_bytes": 2931, + "reported_used_tokens": 2894, + "working_set_bytes": 291061760, + "peak_working_set_bytes": 291966976 + }, + { + "query": "pin minimum supported Rust version in Cargo.toml", + "ranked": [ + "cargo-msrv" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ2XPPYK5BWDS90VBENDV", + "id": "01M1X033DYNB8SY1VHB5KPCBCE", + "kind": "memory", + "score": 0.999652862548828, + "summary": "project:fact - [tags: cargo rust msrv edition compatibility] Set `rust-version` in each `Cargo.toml` to declare the minimum supported Rust version (MSRV). Cargo enforces this with `--check`: `cargo check` fails if the toolchain is older than `rust-version`. Keep MSRV as old as your oldest supported deployment target." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 949.7282, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 693, + "mcp_result_bytes": 774, + "wire_bytes": 811, + "reported_used_tokens": 774, + "working_set_bytes": 291061760, + "peak_working_set_bytes": 291979264 + }, + { + "query": "Windows path over 260 characters causes OS error 3 during Cargo build", + "ranked": [ + "windows-long-paths", + "windows-file-locking-av" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ3N0W9HMTPNG87PPZAMW", + "id": "01M1X034B9K9NAJY45RVEQ6S16", + "kind": "memory", + "score": 0.9964189529418944, + "summary": "project:fact - [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe." + }, + { + "expansion_handle": "memory:01M1WZZ3P7RTE1G7D4JQV0P44R", + "id": "01M1X034BATSG124Z6F2Y450EE", + "kind": "memory", + "score": 0.9571694135665894, + "summary": "project:fact - [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 948.6348999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1297, + "mcp_result_bytes": 1406, + "wire_bytes": 1443, + "reported_used_tokens": 1406, + "working_set_bytes": 291061760, + "peak_working_set_bytes": 291979264 + }, + { + "query": "how do I enable long file paths for Cargo on Windows?", + "ranked": [ + "windows-long-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ3N0W9HMTPNG87PPZAMW", + "id": "01M1X03597V0FWZT45YR05CZNT", + "kind": "memory", + "score": 0.9998334646224976, + "summary": "project:fact - [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1029.2355, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 769, + "mcp_result_bytes": 860, + "wire_bytes": 897, + "reported_used_tokens": 860, + "working_set_bytes": 291065856, + "peak_working_set_bytes": 291983360 + }, + { + "query": "intermittent sharing violation errors when Rust linker writes the exe on Windows", + "ranked": [ + "windows-file-locking-av", + "windows-long-paths", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ3P7RTE1G7D4JQV0P44R", + "id": "01M1X0369J3BSR8XGHMV3P03ED", + "kind": "memory", + "score": 0.999750316143036, + "summary": "project:fact - [2026-09-07] [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + }, + { + "expansion_handle": "memory:01M1WZZ3N0W9HMTPNG87PPZAMW", + "id": "01M1X0369KYVC9KE9KBQ72Z3BH", + "kind": "memory", + "score": 0.4757097661495209, + "summary": "project:fact - [2026-09-07] [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe." + }, + { + "expansion_handle": "memory:01M1WZZ2A8XJX26X64WAX094EF", + "id": "01M1X0369KG09F5EP91NRJ9TCZ", + "kind": "memory", + "score": 0.38107830286026, + "summary": "project:fact - [2026-09-07] [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 994.3535, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2006, + "mcp_result_bytes": 2133, + "wire_bytes": 2170, + "reported_used_tokens": 2133, + "working_set_bytes": 291065856, + "peak_working_set_bytes": 291983360 + }, + { + "query": "Rust walkdir follows junctions differently from symlinks on Windows", + "ranked": [ + "windows-junctions-vs-symlinks" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ3T0DFMMF71XN3EKK1P2", + "id": "01M1X03789AC4KDVRDB5VH8FCP", + "kind": "memory", + "score": 0.9996020197868348, + "summary": "project:fact - [tags: windows junctions symlinks rust std::fs] On Windows, directory junctions (NTFS reparse points) behave like symlinks for directory traversal but `std::fs::symlink_metadata` returns `FileType::is_symlink() = false` for junctions (only true for regular symlinks). Use `std::fs::read_link` \u2014 it succeeds for both junction and symlink. `walkdir` crate's `follow_links` follows both, but its `is_symlink()` method correctly reports only actual symlinks." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 999.2693, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 845, + "mcp_result_bytes": 926, + "wire_bytes": 963, + "reported_used_tokens": 926, + "working_set_bytes": 291069952, + "peak_working_set_bytes": 291983360 + }, + { + "query": "UNC path canonicalize returns verbatim prefix \u2014 how do I strip it?", + "ranked": [ + "windows-unc-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ3QAGESDVJ9ZV39NZHT6", + "id": "01M1X0387QM691H0KRCKMYNP4X", + "kind": "memory", + "score": 0.9988629817962646, + "summary": "project:fact - [tags: windows unc-paths rust std::fs] Windows UNC paths (`\\\\server\\share\\...`) are not supported by most Rust `std::fs` operations unless passed through the extended-length prefix `\\\\?\\UNC\\server\\share\\...`. `std::path::Path::new(\"\\\\\\\\server\\\\share\")` works for basic operations but breaks with `canonicalize()` which returns the verbatim prefix form. When walking directory trees that may start on UNC paths, use the `dunce` crate to strip the verbatim prefix before comparing or displaying paths." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1054.9421000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 908, + "mcp_result_bytes": 1025, + "wire_bytes": 1062, + "reported_used_tokens": 1025, + "working_set_bytes": 291344384, + "peak_working_set_bytes": 292270080 + }, + { + "query": "UTF-8 memory text prints as mojibake in the Windows console", + "ranked": [ + "windows-console-encoding" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ3RPRWPQ38ERZW2PRMYK", + "id": "01M1X0398KEKT7P6MMV260PS7B", + "kind": "memory", + "score": 0.9996604919433594, + "summary": "project:fact - [tags: windows console encoding utf8 rust] Windows console code page defaults to the system ANSI code page (usually CP1252 or CP932), not UTF-8. Rust's `println!` writes UTF-8 bytes which display as mojibake in a non-UTF-8 console. Fix at process startup: call `SetConsoleOutputCP(65001)` via `winapi` or `windows-sys`, or set `PYTHONUTF8=1`/`RUST_LOG` before launch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1029.0218, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 757, + "mcp_result_bytes": 838, + "wire_bytes": 875, + "reported_used_tokens": 838, + "working_set_bytes": 291319808, + "peak_working_set_bytes": 292270080 + }, + { + "query": "process exit code is 4294967295 instead of -1 on Windows", + "ranked": [ + "windows-exit-codes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ3V9A9XDES6YY78S5R8V", + "id": "01M1X03A960W5S6VTYV0CC6S08", + "kind": "memory", + "score": 0.9966622591018676, + "summary": "project:fact - [tags: windows exit-codes rust process child] On Windows, process exit codes are 32-bit unsigned integers (DWORD). Rust's `ExitStatus::code()` returns `Option` \u2014 it's `None` if the process was killed by a signal (which Windows doesn't use; instead, TerminateProcess with a code). Conventional codes: 0=success, 1=generic error, 0xC0000005=access violation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1027.4728, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 753, + "mcp_result_bytes": 834, + "wire_bytes": 871, + "reported_used_tokens": 834, + "working_set_bytes": 291336192, + "peak_working_set_bytes": 292270080 + }, + { + "query": "tokenizer.json must match the ONNX model \u2014 what breaks if it doesn't?", + "ranked": [ + "onnx-tokenizer-mismatch" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ3Y5TTC9EAF9P8W37TEA", + "id": "01M1X03B8S9WB8MCAFZZPYYH90", + "kind": "memory", + "score": 0.9991299510002136, + "summary": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly \u2014 specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings \u2014 cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1054.7475, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 959, + "mcp_result_bytes": 1040, + "wire_bytes": 1077, + "reported_used_tokens": 1040, + "working_set_bytes": 291602432, + "peak_working_set_bytes": 292515840 + }, + { + "query": "embedding quality degraded after I swapped in the INT8 quantized model", + "ranked": [ + "onnx-quantization-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ3ZENJB98071GPSSCBFP", + "id": "01M1X03C9XYGEM4J84QY029FPA", + "kind": "memory", + "score": 0.997980535030365, + "summary": "project:fact - [2026-09-07] [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals \u2014 cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1054.8025, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 990, + "mcp_result_bytes": 1071, + "wire_bytes": 1108, + "reported_used_tokens": 1071, + "working_set_bytes": 291610624, + "peak_working_set_bytes": 292528128 + }, + { + "query": "missing attention mask causes low-norm embeddings in batch inference", + "ranked": [ + "onnx-batch-padding" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ40KRTKVKK3S2FDV4XV0", + "id": "01M1X03DATTMR71G1BD3KQS33A", + "kind": "memory", + "score": 0.9998397827148438, + "summary": "project:fact - [tags: onnx batch padding attention-mask embeddings] When running batch inference with an ONNX model, all inputs in the batch must be padded to the same sequence length. The `attention_mask` tensor marks which tokens are real (1) and which are padding (0). Failing to pass `attention_mask` causes the model to average-pool over padding tokens, producing systematically lower-norm embeddings." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1027.3384999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 781, + "mcp_result_bytes": 862, + "wire_bytes": 899, + "reported_used_tokens": 862, + "working_set_bytes": 291639296, + "peak_working_set_bytes": 292544512 + }, + { + "query": "ONNX model download fails in a Docker container with no home directory", + "ranked": [ + "onnx-model-cache-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ41Y9E5H30V4AF49H1DS", + "id": "01M1X03ED7A2N2KW2NQ86EHZXM", + "kind": "memory", + "score": 0.9887272119522096, + "summary": "project:fact - [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1189.6111, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 755, + "mcp_result_bytes": 838, + "wire_bytes": 875, + "reported_used_tokens": 838, + "working_set_bytes": 291639296, + "peak_working_set_bytes": 292548608 + }, + { + "query": "fastembed cache path environment variable for CI", + "ranked": [ + "onnx-model-cache-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ41Y9E5H30V4AF49H1DS", + "id": "01M1X03FGGA685H00ASZBZC9WY", + "kind": "memory", + "score": 0.9995118379592896, + "summary": "project:fact - [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1072.4913000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 756, + "mcp_result_bytes": 839, + "wire_bytes": 876, + "reported_used_tokens": 839, + "working_set_bytes": 291639296, + "peak_working_set_bytes": 292548608 + }, + { + "query": "cosine similarity vs dot product for L2-normalized embedding vectors", + "ranked": [ + "onnx-cosine-vs-dot", + "onnx-tokenizer-mismatch", + "onnx-quantization-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ432MR78DQYD3HG90V4X", + "id": "01M1X03GHX343ACENS0QJDDGMD", + "kind": "memory", + "score": 0.9999407529830932, + "summary": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing \u2014 double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + }, + { + "expansion_handle": "memory:01M1WZZ3Y5TTC9EAF9P8W37TEA", + "id": "01M1X03GHXZB541QG76C44H3JD", + "kind": "memory", + "score": 0.9514977931976318, + "summary": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly \u2014 specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings \u2014 cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo." + }, + { + "expansion_handle": "memory:01M1WZZ3ZENJB98071GPSSCBFP", + "id": "01M1X03GHX3QWXDBHVG7P31XFB", + "kind": "memory", + "score": 0.941756010055542, + "summary": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals \u2014 cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1023.2314, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2245, + "mcp_result_bytes": 2362, + "wire_bytes": 2399, + "reported_used_tokens": 2362, + "working_set_bytes": 291643392, + "peak_working_set_bytes": 292556800 + }, + { + "query": "stored vectors have wrong dimension after switching embedding models", + "ranked": [ + "onnx-dim-mismatch", + "onnx-cosine-vs-dot", + "onnx-tokenizer-mismatch" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ44A5F5ZGR0Y9X4XD8QQ", + "id": "01M1X03HHMCT1Q174TSF99D2JK", + "kind": "memory", + "score": 0.9997621178627014, + "summary": "project:fact - [2026-09-07] [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results \u2014 the ANN index shape mismatch isn't always caught at runtime." + }, + { + "expansion_handle": "memory:01M1WZZ432MR78DQYD3HG90V4X", + "id": "01M1X03HHMQE879B6EZ62YY7X3", + "kind": "memory", + "score": 0.997715711593628, + "summary": "project:fact - [2026-09-07] [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing \u2014 double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + }, + { + "expansion_handle": "memory:01M1WZZ3Y5TTC9EAF9P8W37TEA", + "id": "01M1X03HHM0K9VRQ4A0AGZQJ4V", + "kind": "memory", + "score": 0.9388805031776428, + "summary": "project:fact - [2026-09-07] [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly \u2014 specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings \u2014 cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 888.4686, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2049, + "mcp_result_bytes": 2166, + "wire_bytes": 2203, + "reported_used_tokens": 2166, + "working_set_bytes": 291643392, + "peak_working_set_bytes": 292556800 + }, + { + "query": "E5 and Instructor models need a query prefix \u2014 what happens without it?", + "ranked": [ + "onnx-prefix-instructions", + "onnx-cosine-vs-dot" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ46Q0ZQPF1ZGVM8SNCCQ", + "id": "01M1X03JDKS41BYGF0BZJ5PDP4", + "kind": "memory", + "score": 0.996955633163452, + "summary": "project:fact - [tags: onnx embeddings prefix instruction e5 query passage] E5 and Instructor family models require a text prefix on BOTH query and passage sides to produce meaningful similarities: query prefix `\"query: \"`, passage prefix `\"passage: \"`. Omitting the prefix can drop MRR by 10-15 percentage points on out-of-domain datasets. Check the model's README for the exact prefix string \u2014 it varies by model family." + }, + { + "expansion_handle": "memory:01M1WZZ432MR78DQYD3HG90V4X", + "id": "01M1X03JDKR90BHE97XAK1MF0J", + "kind": "memory", + "score": 0.9543967247009276, + "summary": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing \u2014 double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1046.5412000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1339, + "mcp_result_bytes": 1446, + "wire_bytes": 1483, + "reported_used_tokens": 1446, + "working_set_bytes": 291643392, + "peak_working_set_bytes": 292556800 + }, + { + "query": "ORT thread pool contention when running multiple bench processes in parallel", + "ranked": [ + "onnx-ort-threading" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ4807ER27Q3A0RY9J051", + "id": "01M1X03KEDBM18EQQ3YAZSN2JN", + "kind": "memory", + "score": 0.9998078942298888, + "summary": "project:fact - [2026-09-07] [tags: onnx ort thread-pool parallelism cpu] ORT (ONNX Runtime) creates its own inter-op and intra-op thread pools. In a multi-process bench setup, each child inherits these pools and they compete for CPU cores. Set `SessionOptionsBuilder::with_intra_threads(1).with_inter_threads(1)` if you're running many parallel bench processes \u2014 this sacrifices per-inference throughput for lower contention." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1044.2381, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 802, + "mcp_result_bytes": 883, + "wire_bytes": 920, + "reported_used_tokens": 883, + "working_set_bytes": 291643392, + "peak_working_set_bytes": 292556800 + }, + { + "query": "git worktrees share the .kimetsu brain \u2014 how do I isolate test runs?", + "ranked": [ + "git-worktree-brain-isolation", + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ499RMTPQ3NXPQSC99G9", + "id": "01M1X03MEZ46Q4M61BY20XS1KD", + "kind": "memory", + "score": 0.9996256828308104, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root \u2014 if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + }, + { + "expansion_handle": "memory:01M1WZZ1YV2PYW23FMEBR45V15", + "id": "01M1X03MEZHNGHECTP5CHCDNF3", + "kind": "memory", + "score": 0.9904396533966064, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1036.334, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1435, + "mcp_result_bytes": 1534, + "wire_bytes": 1571, + "reported_used_tokens": 1534, + "working_set_bytes": 291667968, + "peak_working_set_bytes": 292585472 + }, + { + "query": "when is it safe to use --no-verify on git commit?", + "ranked": [ + "git-hooks-bypass", + "git-reflog-rescue" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ4AFBM2RVVK1ET2A68DJ", + "id": "01M1X03NFH5T9W87952XA3W7QW", + "kind": "memory", + "score": 0.9956986904144288, + "summary": "project:fact - [2026-09-07] [tags: git hooks bypass pre-commit skip] `git commit --no-verify` skips ALL hooks (pre-commit and commit-msg). Never use this in shared team repos where hooks enforce quality gates (lint, tests, memory harvest). Instead, fix the failing hook." + }, + { + "expansion_handle": "memory:01M1WZZ4FDY5EF0WR9C6SDZ6BC", + "id": "01M1X03NFJ772PNVE7HXM18RX6", + "kind": "memory", + "score": 0.5084817409515381, + "summary": "project:fact - [2026-09-07] [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone \u2014 they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only \u2014 remote reflog is not accessible via normal git commands." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1063.5722999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1193, + "mcp_result_bytes": 1292, + "wire_bytes": 1329, + "reported_used_tokens": 1292, + "working_set_bytes": 291667968, + "peak_working_set_bytes": 292585472 + }, + { + "query": "reduce clone size and bandwidth for server-side repo ingest", + "ranked": [ + "git-sparse-checkout", + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ4BMTJGYHXYA7FXWQ5CY", + "id": "01M1X03PGMS9T1G0V2AK4Y5RCJ", + "kind": "memory", + "score": 0.9969936609268188, + "summary": "project:fact - [tags: git sparse-checkout partial-clone bandwidth] `git sparse-checkout init --cone` combined with `git clone --filter=blob:none` (partial clone) fetches only the commit graph and tree objects, not blobs. Individual blobs are fetched on demand when accessed. This cuts clone time for large repos from minutes to seconds." + }, + { + "expansion_handle": "memory:01M1WZZ1EG91C0J27K1WTBHJX3", + "id": "01M1X03PGMMH79WEW7AHFHXVH2", + "kind": "memory", + "score": 0.8199672698974609, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1122.1167, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1744, + "mcp_result_bytes": 1843, + "wire_bytes": 1880, + "reported_used_tokens": 1843, + "working_set_bytes": 291672064, + "peak_working_set_bytes": 292585472 + }, + { + "query": "spurious diffs from Windows CRLF line ending conversion in git", + "ranked": [ + "git-line-endings-windows" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ4CXN483F23RSK3GTRCV", + "id": "01M1X03QMJVTKVFRYWS1BD68N9", + "kind": "memory", + "score": 0.9993343949317932, + "summary": "project:fact - [tags: git line-endings windows crlf autocrlf] On Windows, `core.autocrlf=true` (git's default for Windows installs) converts LF to CRLF on checkout and CRLF to LF on commit. This causes spurious diffs when files are edited on Windows then committed \u2014 the content is identical but the line endings differ in the index vs the working tree. Fix: set `core.autocrlf=false` and `.gitattributes` with `* text=auto eol=lf` for the repo." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1092.6331, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 940, + "reported_used_tokens": 903, + "working_set_bytes": 291672064, + "peak_working_set_bytes": 292585472 + }, + { + "query": "git submodule always gets the wrong commit in CI", + "ranked": [ + "git-submodule-pinning", + "git-hooks-bypass" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ4E6PDJV8HT6DEDHAH4Y", + "id": "01M1X03RP9X18D1CEP1PFXVXPP", + "kind": "memory", + "score": 0.9992856383323668, + "summary": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip \u2014 this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version." + }, + { + "expansion_handle": "memory:01M1WZZ4AFBM2RVVK1ET2A68DJ", + "id": "01M1X03RP997PDWX5QGDEHSXGP", + "kind": "memory", + "score": 0.6295387744903564, + "summary": "project:fact - [tags: git hooks bypass pre-commit skip] `git commit --no-verify` skips ALL hooks (pre-commit and commit-msg). Never use this in shared team repos where hooks enforce quality gates (lint, tests, memory harvest). Instead, fix the failing hook." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1065.4435, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1157, + "mcp_result_bytes": 1256, + "wire_bytes": 1293, + "reported_used_tokens": 1256, + "working_set_bytes": 291672064, + "peak_working_set_bytes": 292585472 + }, + { + "query": "accidentally ran git reset --hard and lost commits \u2014 can I recover?", + "ranked": [ + "git-reflog-rescue" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ4FDY5EF0WR9C6SDZ6BC", + "id": "01M1X03SQ6CBDS6HWYY3T5E70Z", + "kind": "memory", + "score": 0.9995450377464294, + "summary": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone \u2014 they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only \u2014 remote reflog is not accessible via normal git commands." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1082.0418, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 762, + "mcp_result_bytes": 843, + "wire_bytes": 880, + "reported_used_tokens": 843, + "working_set_bytes": 291676160, + "peak_working_set_bytes": 292585472 + }, + { + "query": "blocking SQLite call from an async tokio handler causes latency spikes", + "ranked": [ + "tokio-blocking-in-async" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ4GH1JNHZ64XPEQW4NT3", + "id": "01M1X03TS5VJWQ8RC4T3DWNJ8S", + "kind": "memory", + "score": 0.9996535778045654, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 989.5702, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 766, + "mcp_result_bytes": 847, + "wire_bytes": 884, + "reported_used_tokens": 847, + "working_set_bytes": 291676160, + "peak_working_set_bytes": 292585472 + }, + { + "query": "Cannot start a runtime from within a runtime in a tokio test", + "ranked": [ + "tokio-runtime-in-tests", + "tokio-blocking-in-async" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ4HSK9D41PCJ519ETJAR", + "id": "01M1X03VR9G453DRSGXZ3CCT1P", + "kind": "memory", + "score": 0.9997126460075378, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + }, + { + "expansion_handle": "memory:01M1WZZ4GH1JNHZ64XPEQW4NT3", + "id": "01M1X03VR90G2006MA89DZRXNY", + "kind": "memory", + "score": 0.5779464840888977, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1006.4474000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1370, + "mcp_result_bytes": 1477, + "wire_bytes": 1514, + "reported_used_tokens": 1477, + "working_set_bytes": 291680256, + "peak_working_set_bytes": 292593664 + }, + { + "query": "tokio select cancels the other branch and loses the value in the channel", + "ranked": [ + "tokio-select-cancellation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ4K825RN30808GSPCHKA", + "id": "01M1X03WQVJ296FF5H8YASVSM9", + "kind": "memory", + "score": 0.9981033802032472, + "summary": "project:fact - [tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1044.1476, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 751, + "mcp_result_bytes": 832, + "wire_bytes": 869, + "reported_used_tokens": 832, + "working_set_bytes": 291700736, + "peak_working_set_bytes": 292618240 + }, + { + "query": "mpsc channel backpressure causing senders to stall", + "ranked": [ + "tokio-channel-backpressure" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ4MGMJYPYG1CNQ31Z4F1", + "id": "01M1X03XR1NY4KRCK8YYRXJ8SK", + "kind": "memory", + "score": 0.9999104738235474, + "summary": "project:fact - [tags: tokio mpsc channel backpressure async rust] `tokio::sync::mpsc::channel(N)` with a bounded buffer provides backpressure: senders block when the buffer is full. This prevents unbounded memory growth but can cause sender tasks to stall. Choosing N: too small causes frequent backpressure (throughput drops); too large defeats the purpose." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1098.9373, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 733, + "mcp_result_bytes": 814, + "wire_bytes": 851, + "reported_used_tokens": 814, + "working_set_bytes": 291700736, + "peak_working_set_bytes": 292618240 + }, + { + "query": "overhead from calling spawn_blocking on every single query request", + "ranked": [ + "tokio-spawn-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ4NRXWZ5DKJ9NT2T1QNF", + "id": "01M1X03YTJY12D2XH7S99KF9V3", + "kind": "memory", + "score": 0.9961729645729064, + "summary": "project:fact - [tags: tokio spawn_blocking thread-pool rust blocking] `tokio::task::spawn_blocking` places work on a dedicated blocking thread pool (default up to 512 threads, configurable via `Builder::max_blocking_threads`). Each call creates or reuses a thread \u2014 there's no true pooling, threads may be created on demand. For many short-duration blocking calls (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1042.3248, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 746, + "mcp_result_bytes": 827, + "wire_bytes": 864, + "reported_used_tokens": 827, + "working_set_bytes": 291700736, + "peak_working_set_bytes": 292618240 + }, + { + "query": "axum server panics during shutdown because the DB pool is already closed", + "ranked": [ + "tokio-shutdown-ordering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ4SA9NJZKJZBGK77G2C8", + "id": "01M1X03ZVE656VTWQRJ1K6PA98", + "kind": "memory", + "score": 0.98052579164505, + "summary": "project:fact - [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries \u2014 the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1084.5885, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 931, + "mcp_result_bytes": 1012, + "wire_bytes": 1049, + "reported_used_tokens": 1012, + "working_set_bytes": 291700736, + "peak_working_set_bytes": 292618240 + }, + { + "query": "reqwest Client created per-request defeats connection pooling", + "ranked": [ + "http-connection-pooling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ4TM46XVF6GNKPSJTA0S", + "id": "01M1X040XCFGCR9TTK1TQVTANN", + "kind": "memory", + "score": 0.9998082518577576, + "summary": "project:fact - [tags: http reqwest connection-pool keep-alive rust] reqwest's `Client` holds a connection pool; always create ONE `Client` instance and clone it for each handler \u2014 cloning is cheap (Arc under the hood). Creating a `Client::new()` per request defeats connection pooling and causes TCP connection exhaustion under load. The default pool settings: max_idle_per_host=usize::MAX (unbounded), idle_timeout=90s." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1007.9841, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 797, + "mcp_result_bytes": 878, + "wire_bytes": 915, + "reported_used_tokens": 878, + "working_set_bytes": 291700736, + "peak_working_set_bytes": 292618240 + }, + { + "query": "LLM request times out during streaming \u2014 which timeout setting applies?", + "ranked": [ + "http-timeout-layering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ4W0RY1A7KP4RJ9CZEDQ", + "id": "01M1X041X0WQRG1Y9A3BHN188A", + "kind": "memory", + "score": 0.9987107515335084, + "summary": "project:fact - [tags: http reqwest timeout connect read total rust] reqwest has three distinct timeout knobs: `connect_timeout`, `read_timeout`, and `timeout` (total). They compose: if all three are set, the request fails at whichever fires first. For LLM API calls with streaming responses, `read_timeout` must be larger than the slowest expected token (often 30-60s) while `connect_timeout` can be tight (3-5s)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 919.9476, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 788, + "mcp_result_bytes": 869, + "wire_bytes": 906, + "reported_used_tokens": 869, + "working_set_bytes": 291704832, + "peak_working_set_bytes": 292618240 + }, + { + "query": "how do I safely retry a POST to the LLM API without creating duplicates?", + "ranked": [ + "http-retry-idempotency" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ4XA8MRXMAN2368YCP4X", + "id": "01M1X042SAJ69WR79VCJYSQYCW", + "kind": "memory", + "score": 0.9995805621147156, + "summary": "project:fact - [tags: http retry idempotency post put reqwest] Only retry idempotent requests automatically. GET, HEAD, PUT, DELETE are idempotent. POST is NOT \u2014 retrying a POST may create duplicate resources." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1091.6948, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 585, + "mcp_result_bytes": 666, + "wire_bytes": 703, + "reported_used_tokens": 666, + "working_set_bytes": 291704832, + "peak_working_set_bytes": 292622336 + }, + { + "query": "custom enterprise root CA not trusted by rustls on Windows", + "ranked": [ + "http-tls-roots", + "http-proxy-env" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ4YPZJFFS0XN2956MYJ9", + "id": "01M1X043V86VGG0NR3B7VKEBP1", + "kind": "memory", + "score": 0.9998220801353456, + "summary": "project:fact - [tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle \u2014 the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle." + }, + { + "expansion_handle": "memory:01M1WZZ51D60CXEEXFVERMVA26", + "id": "01M1X043V8N4K5ZKAB8QJ3X6MD", + "kind": "memory", + "score": 0.38715291023254395, + "summary": "project:fact - [tags: http proxy environment reqwest rust corporate] reqwest respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` environment variables by default (with `default-tls` or `rustls-tls`). In a corporate network, these may redirect traffic through an intercepting proxy that breaks mTLS or adds latency. To disable proxy usage entirely: `reqwest::ClientBuilder::no_proxy()`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1050.9339, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1311, + "mcp_result_bytes": 1410, + "wire_bytes": 1447, + "reported_used_tokens": 1410, + "working_set_bytes": 291704832, + "peak_working_set_bytes": 292622336 + }, + { + "query": "parsing server-sent events when a single TCP chunk contains a partial SSE frame", + "ranked": [ + "http-streaming-bodies" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ5016DMTSBPC3FW1HSCF", + "id": "01M1X044WDCQ02CJYEF7B029PN", + "kind": "memory", + "score": 0.9667426943778992, + "summary": "project:fact - [2026-09-07] [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding \u2014 a chunk may split across frame boundaries." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 977.2144000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 859, + "mcp_result_bytes": 940, + "wire_bytes": 977, + "reported_used_tokens": 940, + "working_set_bytes": 291704832, + "peak_working_set_bytes": 292622336 + }, + { + "query": "reqwest does not use the system proxy settings on Windows", + "ranked": [ + "http-proxy-env", + "http-tls-roots", + "http-connection-pooling", + "http-streaming-bodies" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ51D60CXEEXFVERMVA26", + "id": "01M1X045TYXZN5K8C3SQ4RZ34Z", + "kind": "memory", + "score": 0.9997830986976624, + "summary": "project:fact - [tags: http proxy environment reqwest rust corporate] reqwest respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` environment variables by default (with `default-tls` or `rustls-tls`). In a corporate network, these may redirect traffic through an intercepting proxy that breaks mTLS or adds latency. To disable proxy usage entirely: `reqwest::ClientBuilder::no_proxy()`." + }, + { + "expansion_handle": "memory:01M1WZZ4YPZJFFS0XN2956MYJ9", + "id": "01M1X045TYQQPVP4M1FRMDNQXB", + "kind": "memory", + "score": 0.9808586239814758, + "summary": "project:fact - [tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle \u2014 the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle." + }, + { + "expansion_handle": "memory:01M1WZZ4TM46XVF6GNKPSJTA0S", + "id": "01M1X045TYXB9Z0XCVK4YMA61A", + "kind": "memory", + "score": 0.719273030757904, + "summary": "project:fact - [tags: http reqwest connection-pool keep-alive rust] reqwest's `Client` holds a connection pool; always create ONE `Client` instance and clone it for each handler \u2014 cloning is cheap (Arc under the hood). Creating a `Client::new()` per request defeats connection pooling and causes TCP connection exhaustion under load. The default pool settings: max_idle_per_host=usize::MAX (unbounded), idle_timeout=90s." + }, + { + "expansion_handle": "memory:01M1WZZ5016DMTSBPC3FW1HSCF", + "id": "01M1X045TYGXY3CHMZCK1PHQ00", + "kind": "memory", + "score": 0.7009692192077637, + "summary": "project:fact - [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding \u2014 a chunk may split across frame boundaries." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1054.645, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2497, + "mcp_result_bytes": 2632, + "wire_bytes": 2669, + "reported_used_tokens": 2632, + "working_set_bytes": 291704832, + "peak_working_set_bytes": 292622336 + }, + { + "query": "insta snapshot tests fail in CI because output includes a timestamp", + "ranked": [ + "testing-snapshot-churn", + "ci-flaky-quarantine" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ52PSFB771CF2T2XTXMK", + "id": "01M1X046WD46734P1DG101EV0A", + "kind": "memory", + "score": 0.999855637550354, + "summary": "project:fact - [tags: testing snapshot insta assert churn rust] Snapshot tests (e.g. with the `insta` crate) fail whenever the output changes, even for intended changes. In CI, they fail loudly; locally, `cargo insta review` walks you through accepting or rejecting changes." + }, + { + "expansion_handle": "memory:01M1WZZ62DSH248PKJ5DS9QJP5", + "id": "01M1X046WEAG0BPN8M6SDR3KTB", + "kind": "memory", + "score": 0.5997360348701477, + "summary": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal \u2014 a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 983.9423, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1196, + "mcp_result_bytes": 1295, + "wire_bytes": 1332, + "reported_used_tokens": 1295, + "working_set_bytes": 291704832, + "peak_working_set_bytes": 292622336 + }, + { + "query": "two test workers writing to the same temp directory path race each other", + "ranked": [ + "testing-temp-dirs-ci" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ5401JXZW9Q74D7VHBDA", + "id": "01M1X047V0P44GHFTA5GCSW0WN", + "kind": "memory", + "score": 0.9889234900474548, + "summary": "project:fact - [tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1043.0105999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 755, + "mcp_result_bytes": 836, + "wire_bytes": 873, + "reported_used_tokens": 836, + "working_set_bytes": 291704832, + "peak_working_set_bytes": 292622336 + }, + { + "query": "test passes locally but fails on a slow CI runner due to a 100ms sleep", + "ranked": [ + "testing-time-dependent-flakes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ55EAP3DZVDAT9TJF59S", + "id": "01M1X048VNYY3J9WB21W5FS5P8", + "kind": "memory", + "score": 0.808289110660553, + "summary": "project:fact - [tags: testing time flaky clock mock rust] Tests that depend on wall-clock time are inherently flaky under load (slow CI runners, GC pauses). Abstract time behind a trait (`Clock: Fn() -> SystemTime`) injected at construction, and supply a fake in tests. For tests checking that something happened \"within N seconds\", use a generous multiple of the expected duration (10x is not unreasonable for CI)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1084.2992, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 790, + "mcp_result_bytes": 875, + "wire_bytes": 912, + "reported_used_tokens": 875, + "working_set_bytes": 291704832, + "peak_working_set_bytes": 292626432 + }, + { + "query": "proptest found a hash collision in text normalization that example tests missed", + "ranked": [ + "testing-property-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ56R1M7MTG9SC024XMJN", + "id": "01M1X049XC0K2RKQDACTQDXA8N", + "kind": "memory", + "score": 0.9994783997535706, + "summary": "project:fact - [tags: testing property-based proptest quickcheck rust] Property-based tests (proptest, quickcheck) find edge cases that example-based tests miss. For kimetsu's memory text normalization, proptest found that zero-width joiner characters and right-to-left marks caused hash collisions. Run proptest with `PROPTEST_CASES=10000` in CI for thorough coverage." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1093.0925, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 744, + "mcp_result_bytes": 825, + "wire_bytes": 862, + "reported_used_tokens": 825, + "working_set_bytes": 291704832, + "peak_working_set_bytes": 292626432 + }, + { + "query": "set_var in tests races when cargo test runs them in parallel", + "ranked": [ + "testing-serial-vs-parallel" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ57Z18NV7YTG4DH94FQA", + "id": "01M1X04B0B01MGBWSZ3NEPN2WB", + "kind": "memory", + "score": 0.9997344613075256, + "summary": "project:fact - [2026-09-07] [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1092.2337, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 832, + "mcp_result_bytes": 913, + "wire_bytes": 950, + "reported_used_tokens": 913, + "working_set_bytes": 291708928, + "peak_working_set_bytes": 292630528 + }, + { + "query": "hardcoded JSON fixtures broke after a schema migration", + "ranked": [ + "testing-fixture-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ5BM8Y427BTMNJXYD0CF", + "id": "01M1X04C29DGWW8Q9KZBYCKXBV", + "kind": "memory", + "score": 0.9998371601104736, + "summary": "project:fact - [2026-09-07] [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 921.2427, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 783, + "mcp_result_bytes": 864, + "wire_bytes": 901, + "reported_used_tokens": 864, + "working_set_bytes": 291708928, + "peak_working_set_bytes": 292630528 + }, + { + "query": "debug print in the MCP handler corrupts the JSON-Lines protocol stream", + "ranked": [ + "mcp-stdout-protocol" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ5CP43ZTFN7JS5HMR0CE", + "id": "01M1X04CYRB39TT5ZY7MC8A4GC", + "kind": "memory", + "score": 0.9997472167015076, + "summary": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1074.4228, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 705, + "mcp_result_bytes": 786, + "wire_bytes": 823, + "reported_used_tokens": 786, + "working_set_bytes": 291831808, + "peak_working_set_bytes": 292745216 + }, + { + "query": "kimetsu MCP tool call times out because embedding model is re-initialized every call", + "ranked": [ + "mcp-tool-timeouts", + "mcp-schema-validation", + "kimetsu-bench-remote-embedder-singleton" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ5DZ3AYV5QZT44CPHATJ", + "id": "01M1X04E00MTQ118TVHQMDNPZS", + "kind": "memory", + "score": 0.9995898604393004, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + }, + { + "expansion_handle": "memory:01M1WZZ5GKPQ49EE9CYXWGFZMA", + "id": "01M1X04E008ZSNBQHMNHTT9WXB", + "kind": "memory", + "score": 0.6027993559837341, + "summary": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array \u2014 omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error." + }, + { + "expansion_handle": "memory:01M1WZZ6GJM9T3GS7347GS9914", + "id": "01M1X04E00D6MN558A3C25JMH3", + "kind": "memory", + "score": 0.5117799639701843, + "summary": "project:fact - [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 961.7979, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2085, + "mcp_result_bytes": 2202, + "wire_bytes": 2239, + "reported_used_tokens": 2202, + "working_set_bytes": 291831808, + "peak_working_set_bytes": 292745216 + }, + { + "query": "env var set after host launch is not visible to the MCP server process", + "ranked": [ + "mcp-env-propagation", + "kimetsu-daemon-lifecycle" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ5F9J8SA788P3MW0M540", + "id": "01M1X04EY4RF3J9PB55BW2B9N1", + "kind": "memory", + "score": 0.9984827637672424, + "summary": "project:fact - [2026-09-07] [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment \u2014 changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate." + }, + { + "expansion_handle": "memory:01M1WZZ63RSHWZWENZZS7EACQ8", + "id": "01M1X04EY4SYTF69CVKKZ2575J", + "kind": "memory", + "score": 0.9977922439575196, + "summary": "project:fact - [2026-09-07] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1109.2829, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1267, + "mcp_result_bytes": 1366, + "wire_bytes": 1403, + "reported_used_tokens": 1366, + "working_set_bytes": 291831808, + "peak_working_set_bytes": 292753408 + }, + { + "query": "MCP tool call fails because a required field is missing from the JSON input", + "ranked": [ + "mcp-schema-validation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ5GKPQ49EE9CYXWGFZMA", + "id": "01M1X04G0WZ8W7BEG0F93Z4J4F", + "kind": "memory", + "score": 0.998538613319397, + "summary": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array \u2014 omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1059.4194, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 798, + "mcp_result_bytes": 879, + "wire_bytes": 916, + "reported_used_tokens": 879, + "working_set_bytes": 291831808, + "peak_working_set_bytes": 292753408 + }, + { + "query": "Claude Code rejects the tool name with a hyphen in it", + "ranked": [ + "mcp-tool-naming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ5HTMVKATVGA5KN68RBM", + "id": "01M1X04H1VNV2VZWT2F83V33EB", + "kind": "memory", + "score": 0.9982439279556274, + "summary": "project:fact - [tags: mcp tool naming convention kimetsu] MCP tool names must be valid identifiers for all host agents. Claude Code restricts tool names to `[a-zA-Z0-9_-]` and max 64 chars. Use `snake_case` (kimetsu_brain_context, kimetsu_brain_record) \u2014 hyphen is technically allowed but some hosts reject it." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1009.9370999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 687, + "mcp_result_bytes": 768, + "wire_bytes": 805, + "reported_used_tokens": 768, + "working_set_bytes": 291934208, + "peak_working_set_bytes": 292847616 + }, + { + "query": "MCP response path uses backslashes and the host rejects it", + "ranked": [ + "mcp-transcript-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ5K4VEJD5ZMCZFYJ4F78", + "id": "01M1X04J1P7764DK18B81ASKB6", + "kind": "memory", + "score": 0.9984637498855592, + "summary": "project:fact - [tags: mcp transcript paths kimetsu hooks runs] kimetsu writes run transcripts to `/.kimetsu/runs//`. The post-session hook reads the latest run's transcript to trigger memory harvest. On Windows, the path uses backslashes internally but the MCP JSON must use forward slashes or the host may reject path-type arguments." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1073.2968999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 724, + "mcp_result_bytes": 805, + "wire_bytes": 842, + "reported_used_tokens": 805, + "working_set_bytes": 291934208, + "peak_working_set_bytes": 292847616 + }, + { + "query": "AWS credentials not found \u2014 which env var does kimetsu read for Bedrock?", + "ranked": [ + "aws-credentials-chain", + "aws-region-resolution", + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ5MGVMP2RVNC21FBGSEM", + "id": "01M1X04K3HR5QTRJKB2YGRYYAN", + "kind": "memory", + "score": 0.9990235567092896, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + }, + { + "expansion_handle": "memory:01M1WZZ5P307DHRY74HHY467GP", + "id": "01M1X04K3H75YY02EHGBNZBREN", + "kind": "memory", + "score": 0.9968422651290894, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1WZZ1MW1QYZSCVN2RX0C797", + "id": "01M1X04K3HJ7TXHC6RRZNGX2GV", + "kind": "memory", + "score": 0.9849756360054016, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1WZZ1VNAZKMYF51RBAAKDDG", + "id": "01M1X04K3H6SMA7Z74PMYACMEN", + "kind": "memory", + "score": 0.9203452467918396, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1144.5137, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3455, + "mcp_result_bytes": 3618, + "wire_bytes": 3655, + "reported_used_tokens": 3618, + "working_set_bytes": 292196352, + "peak_working_set_bytes": 293109760 + }, + { + "query": "Bedrock InvokeModel fails because the region is not configured", + "ranked": [ + "aws-region-resolution", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ5P307DHRY74HHY467GP", + "id": "01M1X04M8799S80Z9VGXBM9E6T", + "kind": "memory", + "score": 0.99688321352005, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1WZZ1VNAZKMYF51RBAAKDDG", + "id": "01M1X04M87ZPTBBRVT3D391AC1", + "kind": "memory", + "score": 0.6450709104537964, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1210.5622, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1810, + "mcp_result_bytes": 1929, + "wire_bytes": 1966, + "reported_used_tokens": 1929, + "working_set_bytes": 292196352, + "peak_working_set_bytes": 293113856 + }, + { + "query": "how do I handle ThrottlingException from Bedrock with exponential backoff?", + "ranked": [ + "aws-retry-throttling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ5QDWAP2895ZJ5T5H7M3", + "id": "01M1X04NCWFHMAN2AARCW0P8NG", + "kind": "memory", + "score": 0.9997082352638244, + "summary": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with \u00b125% jitter." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1131.3284, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 771, + "mcp_result_bytes": 868, + "wire_bytes": 905, + "reported_used_tokens": 868, + "working_set_bytes": 292196352, + "peak_working_set_bytes": 293113856 + }, + { + "query": "generating a presigned S3 URL for brain export without exposing credentials", + "ranked": [ + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ5RRGGVC9C9M0RSEG4PW", + "id": "01M1X04PGR4YG5MBC20FCS1BZX", + "kind": "memory", + "score": 0.9990487694740297, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1077.1729, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 875, + "mcp_result_bytes": 956, + "wire_bytes": 993, + "reported_used_tokens": 956, + "working_set_bytes": 292196352, + "peak_working_set_bytes": 293113856 + }, + { + "query": "IMDSv2 token required for instance metadata \u2014 PUT before GET", + "ranked": [ + "aws-instance-metadata" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ5T44WV4Y4P7JPQFNMD4", + "id": "01M1X04QJ1Q0W6RBDCY2CT74KC", + "kind": "memory", + "score": 0.9997182488441468, + "summary": "project:fact - [2026-09-07] [tags: aws imds instance-metadata ec2 token] The AWS Instance Metadata Service v2 (IMDSv2) requires a session token: PUT `http://169.254.169.254/latest/api/token` with `X-aws-ec2-metadata-token-ttl-seconds: 21600` to get a token, then GET metadata with `X-aws-ec2-metadata-token: `. IMDSv1 (no token) is disabled on hardened instances. The metadata endpoint is only reachable from within EC2 \u2014 a connection timeout means you're not on EC2." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1068.3947, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 851, + "mcp_result_bytes": 932, + "wire_bytes": 969, + "reported_used_tokens": 932, + "working_set_bytes": 292204544, + "peak_working_set_bytes": 293117952 + }, + { + "query": "Cargo cache key strategy for GitHub Actions to avoid toolchain version collisions", + "ranked": [ + "ci-cache-keys" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ5XKP7ZX9EZT4AV05CDX", + "id": "01M1X04RK8TQQNHCTF4MNHJ5QA", + "kind": "memory", + "score": 0.998869240283966, + "summary": "project:fact - [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key \u2014 macOS and Windows have incompatible artifact formats." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1093.1035, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 788, + "mcp_result_bytes": 869, + "wire_bytes": 906, + "reported_used_tokens": 869, + "working_set_bytes": 292204544, + "peak_working_set_bytes": 293117952 + }, + { + "query": "CI matrix has 18 jobs and costs too much \u2014 how do I reduce it?", + "ranked": [ + "ci-matrix-explosion" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ5YTV7DATANVV0HQG891", + "id": "01M1X04SNFQAYPM01VZMAHJ2J9", + "kind": "memory", + "score": 0.999057948589325, + "summary": "project:fact - [tags: ci github-actions matrix jobs resources] A CI matrix combining OS (3) x Rust toolchain (3) x features (2) = 18 jobs. Each spawns a runner; at $0.008/min for Ubuntu and $0.016/min for Windows, a 10-minute build costs $2.40 per push. Reduce: test the full matrix only on PRs to main; on feature branches, test only Linux+stable." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1091.7891, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 722, + "mcp_result_bytes": 803, + "wire_bytes": 840, + "reported_used_tokens": 803, + "working_set_bytes": 292204544, + "peak_working_set_bytes": 293122048 + }, + { + "query": "GitHub Actions secret accidentally printed in build logs", + "ranked": [ + "ci-secrets-masking", + "ci-cache-keys", + "ci-artifact-retention" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ600C6G9PAFDBBTQMHHG", + "id": "01M1X04TQAJ53VH318TWA6Q0X7", + "kind": "memory", + "score": 0.9963951706886292, + "summary": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output \u2014 but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable." + }, + { + "expansion_handle": "memory:01M1WZZ5XKP7ZX9EZT4AV05CDX", + "id": "01M1X04TQBAH1QZESEGFEM5N42", + "kind": "memory", + "score": 0.4342843890190125, + "summary": "project:fact - [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key \u2014 macOS and Windows have incompatible artifact formats." + }, + { + "expansion_handle": "memory:01M1WZZ61BTACBHGKSZ62TXNJK", + "id": "01M1X04TQAZ42XHW2M46ZDWQ4G", + "kind": "memory", + "score": 0.3422144949436188, + "summary": "project:fact - [tags: ci github-actions artifacts retention benchmark] GitHub Actions artifacts are retained for 90 days (default). For benchmark results, use `actions/upload-artifact` with `retention-days: 365` for long-term tracking. The free tier has 500MB storage \u2014 per-combo JSON files from kimetsu bench (each ~60KB) add up fast if you upload them on every push." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1044.3248999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1791, + "mcp_result_bytes": 1908, + "wire_bytes": 1945, + "reported_used_tokens": 1908, + "working_set_bytes": 292241408, + "peak_working_set_bytes": 293154816 + }, + { + "query": "how long do GitHub Actions artifacts persist and what's the storage limit?", + "ranked": [ + "ci-artifact-retention" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ61BTACBHGKSZ62TXNJK", + "id": "01M1X04VRD75CX4PJHG0SMBMEA", + "kind": "memory", + "score": 0.999624252319336, + "summary": "project:fact - [tags: ci github-actions artifacts retention benchmark] GitHub Actions artifacts are retained for 90 days (default). For benchmark results, use `actions/upload-artifact` with `retention-days: 365` for long-term tracking. The free tier has 500MB storage \u2014 per-combo JSON files from kimetsu bench (each ~60KB) add up fast if you upload them on every push." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1131.2424, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 744, + "mcp_result_bytes": 825, + "wire_bytes": 862, + "reported_used_tokens": 825, + "working_set_bytes": 292265984, + "peak_working_set_bytes": 293183488 + }, + { + "query": "timing-based test flake in CI \u2014 quarantine or fix?", + "ranked": [ + "ci-flaky-quarantine", + "testing-time-dependent-flakes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ62DSH248PKJ5DS9QJP5", + "id": "01M1X04WVMXHJ6GH567RDKYES3", + "kind": "memory", + "score": 0.9994743466377258, + "summary": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal \u2014 a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output." + }, + { + "expansion_handle": "memory:01M1WZZ55EAP3DZVDAT9TJF59S", + "id": "01M1X04WVM29XR6G5MTXJR5S7K", + "kind": "memory", + "score": 0.9849997162818908, + "summary": "project:fact - [tags: testing time flaky clock mock rust] Tests that depend on wall-clock time are inherently flaky under load (slow CI runners, GC pauses). Abstract time behind a trait (`Clock: Fn() -> SystemTime`) injected at construction, and supply a fake in tests. For tests checking that something happened \"within N seconds\", use a generous multiple of the expected duration (10x is not unreasonable for CI)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1059.1924, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1340, + "mcp_result_bytes": 1443, + "wire_bytes": 1480, + "reported_used_tokens": 1443, + "working_set_bytes": 292352000, + "peak_working_set_bytes": 293273600 + }, + { + "query": "kimetsu doctor says the MCP server is running \u2014 how do I stop it before an update?", + "ranked": [ + "kimetsu-daemon-lifecycle", + "mcp-env-propagation", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ63RSHWZWENZZS7EACQ8", + "id": "01M1X04XXX8F946WET0KTREY5C", + "kind": "memory", + "score": 0.9989782571792604, + "summary": "project:fact - [2026-09-07] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1WZZ5F9J8SA788P3MW0M540", + "id": "01M1X04XXYDVP1PSWXSYA4T5Z6", + "kind": "memory", + "score": 0.9049031734466552, + "summary": "project:fact - [2026-09-07] [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment \u2014 changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate." + }, + { + "expansion_handle": "memory:01M1WZZ1GX3CC0CD5ED0GKG0BA", + "id": "01M1X04XXYV3GD2S24JFYTFENB", + "kind": "memory", + "score": 0.4812128245830536, + "summary": "project:fact - [2026-09-07] [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1570.8085, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2046, + "mcp_result_bytes": 2211, + "wire_bytes": 2248, + "reported_used_tokens": 2211, + "working_set_bytes": 292352000, + "peak_working_set_bytes": 293273600 + }, + { + "query": "noise capsules consuming token budget without contributing retrieval signal", + "ranked": [ + "kimetsu-capsule-budgets" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ6501SFBQCDRZZ8XC3M1", + "id": "01M1X04ZDXWCEEH43G41XP4QTW", + "kind": "memory", + "score": 0.9997420907020568, + "summary": "project:fact - [tags: kimetsu capsule tokens budget retrieval] kimetsu retrieval enforces a token budget per capsule type: memory capsules are capped at 6000 tokens total (across all retrieved memories), file capsules at 3000 tokens. When a memory is large and would exceed the budget, it is truncated at a sentence boundary. The budget is enforced AFTER reranking \u2014 reranking may reorder results so that a truncated high-ranked memory displaces a full lower-ranked one." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 925.6015, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 847, + "mcp_result_bytes": 928, + "wire_bytes": 965, + "reported_used_tokens": 928, + "working_set_bytes": 292352000, + "peak_working_set_bytes": 293273600 + }, + { + "query": "kimetsu_brain_record writes to the wrong brain location \u2014 user vs project scope", + "ranked": [ + "kimetsu-memory-scopes", + "kimetsu-write-tools-gate", + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ6638MCJY1KN4ZANDNJY", + "id": "01M1X050AXZMJS8Z5W255JH69J", + "kind": "memory", + "score": 0.999030828475952, + "summary": "project:fact - [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available \u2014 if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope." + }, + { + "expansion_handle": "memory:01M1WZZ6A18T5J8D9KB9Z9T85D", + "id": "01M1X050AXS08KY6TDD5BX213Y", + "kind": "memory", + "score": 0.9838979840278624, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level \u2014 disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1WZZ1YV2PYW23FMEBR45V15", + "id": "01M1X050AXEKVHK1975DGEFBKY", + "kind": "memory", + "score": 0.3852712512016296, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1030.5857, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2098, + "mcp_result_bytes": 2215, + "wire_bytes": 2252, + "reported_used_tokens": 2215, + "working_set_bytes": 292352000, + "peak_working_set_bytes": 293273600 + }, + { + "query": "how do I configure kimetsu to use Claude Haiku for harvesting but Opus for the agent?", + "ranked": [ + "kimetsu-distiller-config" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ67EZK34Q5KJFJPAN30M", + "id": "01M1X051B05XFGVSDDFEDE70E5", + "kind": "memory", + "score": 0.9989088773727416, + "summary": "project:fact - [tags: kimetsu distiller harvest config provider] The kimetsu distiller (auto-harvester) uses a SEPARATE provider configuration from the main agent: `distiller.provider`, `distiller.model`, `distiller.api_key`. This allows running the agent on an expensive model (Claude Opus) while harvesting with a cheap model (Claude Haiku). If `distiller.provider` is not set, it inherits `provider`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1067.543, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 778, + "mcp_result_bytes": 859, + "wire_bytes": 896, + "reported_used_tokens": 859, + "working_set_bytes": 292352000, + "peak_working_set_bytes": 293273600 + }, + { + "query": "first agent turn is slow because kimetsu proactive hook runs embedding inference", + "ranked": [ + "kimetsu-proactive-hooks", + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ68TKQ37R82181S467NF", + "id": "01M1X052CGJXJ7A4YPCJSSDW06", + "kind": "memory", + "score": 0.999568521976471, + "summary": "project:fact - [2026-09-07] [tags: kimetsu proactive hooks context injection] kimetsu's proactive context injection runs before each agent turn (pre-turn hook) and injects relevant memories into the system prompt prefix. The hook invocation adds latency to the first token: embedding inference + vector search + reranking + context formatting. On a cold start, this can be 1-3 seconds." + }, + { + "expansion_handle": "memory:01M1WZZ5DZ3AYV5QZT44CPHATJ", + "id": "01M1X052CGGBH4HE1AB0S7R33X", + "kind": "memory", + "score": 0.9405298233032228, + "summary": "project:fact - [2026-09-07] [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1113.2481, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1403, + "mcp_result_bytes": 1502, + "wire_bytes": 1539, + "reported_used_tokens": 1502, + "working_set_bytes": 292462592, + "peak_working_set_bytes": 293376000 + }, + { + "query": "make the kimetsu brain read-only for certain repos on a shared remote server", + "ranked": [ + "kimetsu-write-tools-gate", + "remote-ingest-split-roots", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ6A18T5J8D9KB9Z9T85D", + "id": "01M1X053FEJ7VWS1A8X3NZKEWQ", + "kind": "memory", + "score": 0.997682809829712, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level \u2014 disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1WZZ1EG91C0J27K1WTBHJX3", + "id": "01M1X053FETVJTRYB3DDQW8T0M", + "kind": "memory", + "score": 0.9957050681114196, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1WZZ1GX3CC0CD5ED0GKG0BA", + "id": "01M1X053FE8V1YK4KQB3FTF3X6", + "kind": "memory", + "score": 0.9909282326698304, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1031.5673000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2725, + "mcp_result_bytes": 2890, + "wire_bytes": 2927, + "reported_used_tokens": 2890, + "working_set_bytes": 292835328, + "peak_working_set_bytes": 293748736 + }, + { + "query": "kimetsu FTS search misses 'deadlocking' when memory says 'deadlock'", + "ranked": [ + "kimetsu-query-stemming", + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ6E1RM4TJDASRHG08A1T", + "id": "01M1X054FHS5PSW11SBPGGJEMD", + "kind": "memory", + "score": 0.9904030561447144, + "summary": "project:fact - [2026-09-07] [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression." + }, + { + "expansion_handle": "memory:01M1WZZ1D64KNHB9FWWN4YQGH6", + "id": "01M1X054FH9DHJDEW2T7190MCK", + "kind": "memory", + "score": 0.91664320230484, + "summary": "project:fact - [2026-09-07] [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure \u2014 `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1005.2103999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1363, + "mcp_result_bytes": 1478, + "wire_bytes": 1515, + "reported_used_tokens": 1478, + "working_set_bytes": 292835328, + "peak_working_set_bytes": 293748736 + }, + { + "query": "how does pool size affect retrieval recall and latency in the bench?", + "ranked": [ + "kimetsu-rerank-pool" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ6FCZW2H7Y803N5YXV7S", + "id": "01M1X055F41S6N8RG90JXW1145", + "kind": "memory", + "score": 0.9998373985290528, + "summary": "project:fact - [tags: kimetsu reranker pool size ann retrieval] kimetsu's retrieval pipeline: ANN (approximate nearest neighbor) retrieves a pool of candidates, then the reranker reorders them, then the top-K are returned. The pool size (default 6 for production, 12 in bench) controls the recall-latency tradeoff: larger pool = higher recall = more reranker calls = more latency. For the jina-tiny reranker, pool 12 adds ~80ms vs pool 6." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1049.3669000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 813, + "mcp_result_bytes": 894, + "wire_bytes": 931, + "reported_used_tokens": 894, + "working_set_bytes": 293003264, + "peak_working_set_bytes": 293920768 + }, + { + "query": "second embedder in a remote bench run gets worse results than the first", + "ranked": [ + "kimetsu-bench-remote-embedder-singleton" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ6GJM9T3GS7347GS9914", + "id": "01M1X056FZ0AETYKDBSV0QQKYQ", + "kind": "memory", + "score": 0.9939629435539246, + "summary": "project:fact - [2026-09-07] [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1058.06, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 895, + "mcp_result_bytes": 976, + "wire_bytes": 1013, + "reported_used_tokens": 976, + "working_set_bytes": 293003264, + "peak_working_set_bytes": 293920768 + }, + { + "query": "what is the expected JSON schema for kimetsu brain bench dataset files?", + "ranked": [ + "kimetsu-eval-fixture-shape", + "testing-fixture-drift", + "kimetsu-mrr-metric", + "mcp-schema-validation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ6HXX18R7HWCVWNBJA54", + "id": "01M1X057H46VVGYNYRHZR3KP4W", + "kind": "memory", + "score": 0.9996767044067384, + "summary": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` \u2014 a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases)." + }, + { + "expansion_handle": "memory:01M1WZZ5BM8Y427BTMNJXYD0CF", + "id": "01M1X057H4PBNVK0B4T7FSMCRD", + "kind": "memory", + "score": 0.9682880640029908, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + }, + { + "expansion_handle": "memory:01M1WZZ6KAP16S8FCEM27T11Y7", + "id": "01M1X057H4WBQ05RZDCDEVMBDY", + "kind": "memory", + "score": 0.8818408250808716, + "summary": "project:fact - [tags: kimetsu bench mrr recall metrics evaluation] kimetsu bench reports MRR (Mean Reciprocal Rank) and Recall@K. MRR is 1/rank_of_first_relevant_result, averaged across cases; it penalizes models that rank the correct answer 2nd or 3rd. Recall@K is the fraction of cases where at least one relevant answer appears in the top K." + }, + { + "expansion_handle": "memory:01M1WZZ5GKPQ49EE9CYXWGFZMA", + "id": "01M1X057H44AFZ1881VASNTZ87", + "kind": "memory", + "score": 0.6527947187423706, + "summary": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array \u2014 omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1091.0179, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2424, + "mcp_result_bytes": 2603, + "wire_bytes": 2640, + "reported_used_tokens": 2603, + "working_set_bytes": 293007360, + "peak_working_set_bytes": 293920768 + }, + { + "query": "what does MRR mean and how do I interpret a 0.01 difference between combos?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1099.6024, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 228, + "mcp_result_bytes": 291, + "wire_bytes": 328, + "reported_used_tokens": 291, + "working_set_bytes": 293003264, + "peak_working_set_bytes": 293920768 + }, + { + "query": "SQLITE_BUSY keeps appearing even with WAL mode enabled", + "ranked": [ + "sqlite-busy-timeout-wal", + "sqlite-wal-network-drive" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ2A8XJX26X64WAX094EF", + "id": "01M1X059NGJGPDFN9SH2R8G4A6", + "kind": "memory", + "score": 0.9982662796974182, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + }, + { + "expansion_handle": "memory:01M1WZZ2DHWWPARX2BHC1Y0VCX", + "id": "01M1X059NGERR89MV476PP70SK", + "kind": "memory", + "score": 0.7844027280807495, + "summary": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1119.3816000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1423, + "mcp_result_bytes": 1522, + "wire_bytes": 1559, + "reported_used_tokens": 1522, + "working_set_bytes": 293122048, + "peak_working_set_bytes": 294035456 + }, + { + "query": "my brain file got huge again right after I compacted it", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1029.8418, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 293122048, + "peak_working_set_bytes": 294035456 + }, + { + "query": "all my FTS queries stopped returning results after I changed the tokenizer config", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1082.6368, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 293122048, + "peak_working_set_bytes": 294035456 + }, + { + "query": "something is preventing the kimetsu binary from being replaced during update", + "ranked": [ + "kimetsu-daemon-lifecycle", + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ63RSHWZWENZZS7EACQ8", + "id": "01M1X05CTKDMV0TBZVXCK66GJM", + "kind": "memory", + "score": 0.9678457975387572, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1WZZ27WAVWVRRC3DCDD6JTV", + "id": "01M1X05CTK0DMXNCR78BV9J3NE", + "kind": "memory", + "score": 0.9395453929901124, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 0.6666666666666666, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 981.9392, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1657, + "mcp_result_bytes": 1756, + "wire_bytes": 1793, + "reported_used_tokens": 1756, + "working_set_bytes": 293122048, + "peak_working_set_bytes": 294035456 + }, + { + "query": "tool call results not appearing in the context \u2014 is the semantic floor too high?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1088.999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 228, + "mcp_result_bytes": 291, + "wire_bytes": 328, + "reported_used_tokens": 291, + "working_set_bytes": 293122048, + "peak_working_set_bytes": 294043648 + }, + { + "query": "CARGO_INCREMENTAL=0 in CI prevents a class of spurious compilation errors", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ2TAMD45HZP50Q7AZK21", + "id": "01M1X05EVA9ERWGDZTXQTB3779", + "kind": "memory", + "score": 0.7995238304138184, + "summary": "project:fact - [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1055.9126, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 877, + "mcp_result_bytes": 958, + "wire_bytes": 995, + "reported_used_tokens": 958, + "working_set_bytes": 293122048, + "peak_working_set_bytes": 294043648 + }, + { + "query": "how do I check whether my Cargo workspace respects the MSRV constraint?", + "ranked": [ + "cargo-msrv", + "cargo-dev-dep-leak", + "cargo-patch-section", + "cargo-target-dir-sharing" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ2XPPYK5BWDS90VBENDV", + "id": "01M1X05FW8QAF4X18RXQPQAZR7", + "kind": "memory", + "score": 0.9921064376831056, + "summary": "project:fact - [tags: cargo rust msrv edition compatibility] Set `rust-version` in each `Cargo.toml` to declare the minimum supported Rust version (MSRV). Cargo enforces this with `--check`: `cargo check` fails if the toolchain is older than `rust-version`. Keep MSRV as old as your oldest supported deployment target." + }, + { + "expansion_handle": "memory:01M1WZZ2QYQ6TW5R3E8C8F0K12", + "id": "01M1X05FW8N1X7PFXGVFCSGH2H", + "kind": "memory", + "score": 0.887407660484314, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + }, + { + "expansion_handle": "memory:01M1WZZ2WHE6FWSPTX3RTV098G", + "id": "01M1X05FW8SW8TVMSF4DJKA0VQ", + "kind": "memory", + "score": 0.7220955491065979, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace \u2014 including transitive deps \u2014 that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1WZZ2S6K8TNKFY1QH1BAHFM", + "id": "01M1X05FW8GABX1B6SQGCYKJ1F", + "kind": "memory", + "score": 0.4095200598239898, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps \u2014 use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1080.9635, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2682, + "mcp_result_bytes": 2821, + "wire_bytes": 2858, + "reported_used_tokens": 2821, + "working_set_bytes": 293122048, + "peak_working_set_bytes": 294043648 + }, + { + "query": "rusqlite connection opened but ON DELETE CASCADE cascade never fires", + "ranked": [ + "sqlite-foreign-keys-default-off" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ2GYG9DC90TEME1XG89Z", + "id": "01M1X05GYCDFXJEKGTDB5BG0MN", + "kind": "memory", + "score": 0.9922945499420166, + "summary": "project:fact - [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting \u2014 every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1091.928, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 735, + "mcp_result_bytes": 816, + "wire_bytes": 853, + "reported_used_tokens": 816, + "working_set_bytes": 293122048, + "peak_working_set_bytes": 294043648 + }, + { + "query": "I cannot connect to kimetsu-remote \u2014 something about TLS cert validation failed", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1072.2773000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 293122048, + "peak_working_set_bytes": 294043648 + }, + { + "query": "graceful shutdown fails because in-flight SQLite queries are still running when pool closes", + "ranked": [ + "tokio-shutdown-ordering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ4SA9NJZKJZBGK77G2C8", + "id": "01M1X05K1MV1HS60868HRFM7YH", + "kind": "memory", + "score": 0.9996342658996582, + "summary": "project:fact - [2026-09-07] [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries \u2014 the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1022.6287999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 947, + "mcp_result_bytes": 1028, + "wire_bytes": 1065, + "reported_used_tokens": 1028, + "working_set_bytes": 293134336, + "peak_working_set_bytes": 294047744 + }, + { + "query": "kimetsu-remote response takes 8 seconds \u2014 which stage is slow?", + "ranked": [ + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ5DZ3AYV5QZT44CPHATJ", + "id": "01M1X05M1JCS2BZKAMAG3HV1DC", + "kind": "memory", + "score": 0.9876242876052856, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1087.9165, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 858, + "mcp_result_bytes": 939, + "wire_bytes": 976, + "reported_used_tokens": 939, + "working_set_bytes": 293142528, + "peak_working_set_bytes": 294047744 + }, + { + "query": "git reflog to rescue accidentally deleted branch", + "ranked": [ + "git-reflog-rescue" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ4FDY5EF0WR9C6SDZ6BC", + "id": "01M1X05N4NDK7CNGMXR9Z01701", + "kind": "memory", + "score": 0.998464822769165, + "summary": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone \u2014 they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only \u2014 remote reflog is not accessible via normal git commands." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1169.9219999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 761, + "mcp_result_bytes": 842, + "wire_bytes": 879, + "reported_used_tokens": 842, + "working_set_bytes": 293142528, + "peak_working_set_bytes": 294051840 + }, + { + "query": "git submodule --remote advances the pinned SHA unexpectedly", + "ranked": [ + "git-submodule-pinning", + "git-reflog-rescue", + "ci-secrets-masking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ4E6PDJV8HT6DEDHAH4Y", + "id": "01M1X05P8FBQK0APZMHDA2J6N8", + "kind": "memory", + "score": 0.9998551607131958, + "summary": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip \u2014 this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version." + }, + { + "expansion_handle": "memory:01M1WZZ4FDY5EF0WR9C6SDZ6BC", + "id": "01M1X05P8GM8QWG1HAPHHJ5SBE", + "kind": "memory", + "score": 0.8857361078262329, + "summary": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone \u2014 they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only \u2014 remote reflog is not accessible via normal git commands." + }, + { + "expansion_handle": "memory:01M1WZZ600C6G9PAFDBBTQMHHG", + "id": "01M1X05P8GHX1Q3GYEA1227RPV", + "kind": "memory", + "score": 0.8434544205665588, + "summary": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output \u2014 but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1039.5924, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1771, + "mcp_result_bytes": 1888, + "wire_bytes": 1925, + "reported_used_tokens": 1888, + "working_set_bytes": 293146624, + "peak_working_set_bytes": 294051840 + }, + { + "query": "axum SSE streaming drops the last event when client disconnects", + "ranked": [ + "http-streaming-bodies" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ5016DMTSBPC3FW1HSCF", + "id": "01M1X05Q8Z9FRRG34K96CM0EDW", + "kind": "memory", + "score": 0.9926375150680542, + "summary": "project:fact - [2026-09-07] [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding \u2014 a chunk may split across frame boundaries." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1112.773, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 859, + "mcp_result_bytes": 940, + "wire_bytes": 977, + "reported_used_tokens": 940, + "working_set_bytes": 293146624, + "peak_working_set_bytes": 294060032 + }, + { + "query": "how do I detect that I am running inside a git worktree vs the main checkout?", + "ranked": [ + "git-worktree-brain-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ499RMTPQ3NXPQSC99G9", + "id": "01M1X05RBRB2SD3HYAZEP80ZKQ", + "kind": "memory", + "score": 0.9857924580574036, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root \u2014 if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1079.6266, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 881, + "mcp_result_bytes": 962, + "wire_bytes": 999, + "reported_used_tokens": 962, + "working_set_bytes": 293146624, + "peak_working_set_bytes": 294064128 + }, + { + "query": "ONNX Runtime intra-op threads causing CPU contention during parallel bench", + "ranked": [ + "onnx-ort-threading", + "tokio-blocking-in-async" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ4807ER27Q3A0RY9J051", + "id": "01M1X05SDDWHQ3WQGFSQR14KW7", + "kind": "memory", + "score": 0.9999210834503174, + "summary": "project:fact - [tags: onnx ort thread-pool parallelism cpu] ORT (ONNX Runtime) creates its own inter-op and intra-op thread pools. In a multi-process bench setup, each child inherits these pools and they compete for CPU cores. Set `SessionOptionsBuilder::with_intra_threads(1).with_inter_threads(1)` if you're running many parallel bench processes \u2014 this sacrifices per-inference throughput for lower contention." + }, + { + "expansion_handle": "memory:01M1WZZ4GH1JNHZ64XPEQW4NT3", + "id": "01M1X05SDDKS6C5PYRXW80D93E", + "kind": "memory", + "score": 0.5390238761901855, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 993.6228, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1328, + "mcp_result_bytes": 1427, + "wire_bytes": 1464, + "reported_used_tokens": 1427, + "working_set_bytes": 293146624, + "peak_working_set_bytes": 294064128 + }, + { + "query": "what is the right way to supply AWS session token alongside access key and secret?", + "ranked": [ + "aws-credentials-chain" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1WZZ5MGVMP2RVNC21FBGSEM", + "id": "01M1X05TCR2PRH020B69G6VQM5", + "kind": "memory", + "score": 0.9493365287780762, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1060.0779, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 895, + "mcp_result_bytes": 976, + "wire_bytes": 1013, + "reported_used_tokens": 976, + "working_set_bytes": 293146624, + "peak_working_set_bytes": 294064128 + } + ], + "id": "existing-development-100", + "dimension": "retrieval", + "tier": "hard", + "score": 0.8182539682539681, + "skipped": false, + "detail": "positive-recall@4=0.84 mrr=0.85 stale-hit=n/a resolution=n/a false-injection=0.538 (n=13) positive-n=197 negative-n=13 (210 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 0.8182539682539681, + 1 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 0.8182539682539681, + "n": 1, + "ci95": null + } + }, + "overall_index": 0.8182539682539681, + "scenario_weighted_index": 0.8182539682539681 +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-retrieval/development/1-candidate.json b/docs/audits/2026-09-07-retrieval/development/1-candidate.json new file mode 100644 index 0000000..7138508 --- /dev/null +++ b/docs/audits/2026-09-07-retrieval/development/1-candidate.json @@ -0,0 +1,6391 @@ +{ + "generated_at": "2026-09-07T04:01:58.1685821Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\tmp-tests\\brainbench-development-100.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "test_env_lock inside with_user_brain_disabled deadlock", + "ranked": [ + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05WHEZZFA615D9M78AXGX", + "id": "01M1X063NFRCFKT1EE5KQGHXPY", + "kind": "memory", + "score": 0.9999797344207764, + "summary": "project:fact - [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure \u2014 `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2726.6567, + "first_query": true, + "server_startup_ms": 97.7907, + "model_text_bytes": 796, + "mcp_result_bytes": 877, + "wire_bytes": 912, + "reported_used_tokens": 877, + "working_set_bytes": 682508288, + "peak_working_set_bytes": 684503040 + }, + { + "query": "why does my test hang after calling with_user_brain_disabled when I also lock test_env_lock?", + "ranked": [ + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05WHEZZFA615D9M78AXGX", + "id": "01M1X064Q50CCET9BW2SR414FE", + "kind": "memory", + "score": 0.9996507167816162, + "summary": "project:fact - [2026-09-07] [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure \u2014 `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1272.3303, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 808, + "mcp_result_bytes": 889, + "wire_bytes": 924, + "reported_used_tokens": 889, + "working_set_bytes": 699133952, + "peak_working_set_bytes": 700063744 + }, + { + "query": "ingest_repo_at_root brain_root files_root kimetsu remote", + "ranked": [ + "remote-ingest-split-roots", + "kimetsu-write-tools-gate", + "remote-mcp-host-wiring", + "kimetsu-bench-remote-embedder-singleton" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05WJQ71HE168C1JX89W7A", + "id": "01M1X065Z90NF8RD81FD04ZH2N", + "kind": "memory", + "score": 0.9999620914459229, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1X0619ZNMV3ST0HFAD5K0PN", + "id": "01M1X065ZA0A3D5N0S5FFPS4DZ", + "kind": "memory", + "score": 0.9088719487190248, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level \u2014 disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1X05WN6EB9RPFK8EF9QTXFD", + "id": "01M1X065Z94AV1DAT6A81APRVM", + "kind": "memory", + "score": 0.8598380088806152, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + }, + { + "expansion_handle": "memory:01M1X061FH4AQNYP3Q7X2RV0MJ", + "id": "01M1X065ZA4MD7DSZWP1PA998X", + "kind": "memory", + "score": 0.6798340678215027, + "summary": "project:fact - [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1702.7848, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3381, + "mcp_result_bytes": 3564, + "wire_bytes": 3599, + "reported_used_tokens": 3564, + "working_set_bytes": 824184832, + "peak_working_set_bytes": 825094144 + }, + { + "query": "why does the remote server index the wrong directory when I run kimetsu brain ingest?", + "ranked": [ + "remote-ingest-split-roots", + "git-sparse-checkout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05WJQ71HE168C1JX89W7A", + "id": "01M1X067MR88GXCHYE7D6QJX9B", + "kind": "memory", + "score": 0.9676534533500672, + "summary": "project:fact - [2026-09-07] [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1X05ZCMYMCY0306J19N23J2", + "id": "01M1X067MRWT22X0GKHMXP34Y9", + "kind": "memory", + "score": 0.6956315040588379, + "summary": "project:fact - [2026-09-07] [tags: git sparse-checkout partial-clone bandwidth] `git sparse-checkout init --cone` combined with `git clone --filter=blob:none` (partial clone) fetches only the commit graph and tree objects, not blobs. Individual blobs are fetched on demand when accessed. This cuts clone time for large repos from minutes to seconds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1794.6225, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1770, + "mcp_result_bytes": 1869, + "wire_bytes": 1904, + "reported_used_tokens": 1869, + "working_set_bytes": 916463616, + "peak_working_set_bytes": 917385216 + }, + { + "query": "kimetsu plugin install --remote mcp.json authorization bearer token", + "ranked": [ + "remote-mcp-host-wiring", + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05WN6EB9RPFK8EF9QTXFD", + "id": "01M1X069CJZCMFSFGK0XGXHS81", + "kind": "memory", + "score": 0.9999654293060304, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + }, + { + "expansion_handle": "memory:01M1X05WWMK1W8X29RJHCH6WVK", + "id": "01M1X069CJB7N8HC9V8DK3KGZ6", + "kind": "memory", + "score": 0.9509484171867372, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1720.4694, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1572, + "mcp_result_bytes": 1727, + "wire_bytes": 1762, + "reported_used_tokens": 1727, + "working_set_bytes": 916754432, + "peak_working_set_bytes": 917671936 + }, + { + "query": "how do I wire a remote kimetsu brain into Claude Code without storing the token in the config file?", + "ranked": [ + "remote-mcp-host-wiring", + "bedrock-kimetsu-provider", + "mcp-tool-naming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05WN6EB9RPFK8EF9QTXFD", + "id": "01M1X06B29NFVWWTCG52NMQV78", + "kind": "memory", + "score": 0.9997368454933168, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + }, + { + "expansion_handle": "memory:01M1X05WS4SP2GV31Y7Z83MPR1", + "id": "01M1X06B2A1VM304M36ZJ2H6YD", + "kind": "memory", + "score": 0.9267048239707948, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X060HMFXE9A894YPA74PVR", + "id": "01M1X06B2ADWY3A1CWQTZHA3JE", + "kind": "memory", + "score": 0.6635663509368896, + "summary": "project:fact - [tags: mcp tool naming convention kimetsu] MCP tool names must be valid identifiers for all host agents. Claude Code restricts tool names to `[a-zA-Z0-9_-]` and max 64 chars. Use `snake_case` (kimetsu_brain_context, kimetsu_brain_record) \u2014 hyphen is technically allowed but some hosts reject it." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1549.3681000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2430, + "mcp_result_bytes": 2603, + "wire_bytes": 2638, + "reported_used_tokens": 2603, + "working_set_bytes": 919322624, + "peak_working_set_bytes": 920256512 + }, + { + "query": "cargo feature unification kimetsu-brain embeddings fastembed test failure", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-dev-dep-leak", + "cargo-profile-override", + "clap-version-build-flavor" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05WQEADDZVS78GM78YF9F", + "id": "01M1X06CKFJBEST5882QFG36FZ", + "kind": "memory", + "score": 0.9999688863754272, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X05XVN17RZ1ZWVR1CH6N4A", + "id": "01M1X06CKFG8SEW4QKCYMVQ91F", + "kind": "memory", + "score": 0.6766564249992371, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + }, + { + "expansion_handle": "memory:01M1X05XZ1Y4BCQ76CJV97H2GQ", + "id": "01M1X06CKFDY0DVPN52X87SWQ9", + "kind": "memory", + "score": 0.667348325252533, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1X05X3Z1E6HBX2QQKX4RS2R", + "id": "01M1X06CKFVTXEJ3Z5REWFGXXB", + "kind": "memory", + "score": 0.6029285192489624, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1431.9421000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3090, + "mcp_result_bytes": 3245, + "wire_bytes": 3280, + "reported_used_tokens": 3245, + "working_set_bytes": 919375872, + "peak_working_set_bytes": 920297472 + }, + { + "query": "my integration tests pass in isolation but break when I run cargo test --workspace \u2014 embedder changed?", + "ranked": [ + "cargo-feature-unification-embeddings" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05WQEADDZVS78GM78YF9F", + "id": "01M1X06DZWDXYJYBMYGY15WND9", + "kind": "memory", + "score": 0.9974289536476136, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1476.4353999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1147, + "mcp_result_bytes": 1232, + "wire_bytes": 1267, + "reported_used_tokens": 1232, + "working_set_bytes": 919519232, + "peak_working_set_bytes": 920440832 + }, + { + "query": "build_anthropic_body bedrock-2023-05-31 InvokeModel blocking reqwest", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05WS4SP2GV31Y7Z83MPR1", + "id": "01M1X06FDKD2X9CBN32KBH3PGH", + "kind": "memory", + "score": 0.9999423027038574, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X05WZN8M0WJBHX8WAN1FW1", + "id": "01M1X06FDK2HWDB0JHB5GZC4QY", + "kind": "memory", + "score": 0.9967412352561952, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1327.5664, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2193, + "mcp_result_bytes": 2320, + "wire_bytes": 2356, + "reported_used_tokens": 2320, + "working_set_bytes": 920211456, + "peak_working_set_bytes": 921124864 + }, + { + "query": "how do I add AWS Bedrock as a model provider in Kimetsu without pulling in the aws-sdk?", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-region-resolution", + "aws-sigv4-bedrock-blocking", + "aws-retry-throttling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05WS4SP2GV31Y7Z83MPR1", + "id": "01M1X06GQ10734Q26ETBS595CY", + "kind": "memory", + "score": 0.9999690055847168, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X060NPXN24BW4FY2DMGRH1", + "id": "01M1X06GQ2RMSSVTN7FTDNN0PD", + "kind": "memory", + "score": 0.9984136819839478, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X05WZN8M0WJBHX8WAN1FW1", + "id": "01M1X06GQ2MA6CKGRF5Q8GZH0E", + "kind": "memory", + "score": 0.9974077343940736, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1X060PXDF79R4DP7JK4GGQ6", + "id": "01M1X06GQ2EF71PSTKJH3GMETS", + "kind": "memory", + "score": 0.9369313716888428, + "summary": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with \u00b125% jitter." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1521.8325, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3330, + "mcp_result_bytes": 3509, + "wire_bytes": 3545, + "reported_used_tokens": 3509, + "working_set_bytes": 920883200, + "peak_working_set_bytes": 921800704 + }, + { + "query": "BridgeTarget enum seams plugin_install_inner plugin_status_inner resolve_setup_hosts", + "ranked": [ + "bridge-target-enum-seams", + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05WV9R2ZXNMMJDH1K8ANT", + "id": "01M1X06J6S8BAP5ATNP9FA370S", + "kind": "memory", + "score": 0.9999773502349854, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + }, + { + "expansion_handle": "memory:01M1X05WWMK1W8X29RJHCH6WVK", + "id": "01M1X06J6S3SHFTGXXPMKHWVE2", + "kind": "memory", + "score": 0.7292461395263672, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1559.9449000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1636, + "mcp_result_bytes": 1743, + "wire_bytes": 1779, + "reported_used_tokens": 1743, + "working_set_bytes": 920977408, + "peak_working_set_bytes": 921890816 + }, + { + "query": "I added a new host to the bridge enum but cargo gives me compile errors in five different match arms \u2014 what did I miss?", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05WV9R2ZXNMMJDH1K8ANT", + "id": "01M1X06KQDG5KPCSM8SVXHZEN2", + "kind": "memory", + "score": 0.997710347175598, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1752.5138, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1059, + "mcp_result_bytes": 1140, + "wire_bytes": 1176, + "reported_used_tokens": 1140, + "working_set_bytes": 921464832, + "peak_working_set_bytes": 922378240 + }, + { + "query": "Pi extension factory defineExtension agent_end session_shutdown kimetsu.ts", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05WWMK1W8X29RJHCH6WVK", + "id": "01M1X06NE3MDN9KG1YBQBHPC5G", + "kind": "memory", + "score": 0.9995530247688292, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1752.2296000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 804, + "mcp_result_bytes": 893, + "wire_bytes": 929, + "reported_used_tokens": 893, + "working_set_bytes": 921604096, + "peak_working_set_bytes": 922517504 + }, + { + "query": "how does Pi (earendil-works/pi) load plugins and what lifecycle hooks does it expose?", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05WWMK1W8X29RJHCH6WVK", + "id": "01M1X06Q564ZRX9GR902HWM1QZ", + "kind": "memory", + "score": 0.9957007765769958, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1778.1620999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 803, + "mcp_result_bytes": 892, + "wire_bytes": 928, + "reported_used_tokens": 892, + "working_set_bytes": 922267648, + "peak_working_set_bytes": 923176960 + }, + { + "query": "aws-sigv4 SigningParams apply_to_request_http1x reqwest sign-http", + "ranked": [ + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider", + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05WZN8M0WJBHX8WAN1FW1", + "id": "01M1X06RWQA4049NWKPT176A5P", + "kind": "memory", + "score": 0.999910831451416, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1X05WS4SP2GV31Y7Z83MPR1", + "id": "01M1X06RWQADSY83XFZ1YYDRFC", + "kind": "memory", + "score": 0.9909924268722534, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X060R52T1PEXSFG92WM307", + "id": "01M1X06RWQWN7ZFS6T2R61855Z", + "kind": "memory", + "score": 0.9447544813156128, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1370.8385999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2839, + "mcp_result_bytes": 2984, + "wire_bytes": 3020, + "reported_used_tokens": 2984, + "working_set_bytes": 922583040, + "peak_working_set_bytes": 923484160 + }, + { + "query": "how do I sign a Bedrock InvokeModel request with aws-sigv4 in blocking Rust?", + "ranked": [ + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider", + "aws-presigned-urls", + "aws-credentials-chain" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05WZN8M0WJBHX8WAN1FW1", + "id": "01M1X06T83W1M32ZEBHFZ8S6MQ", + "kind": "memory", + "score": 0.99993896484375, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1X05WS4SP2GV31Y7Z83MPR1", + "id": "01M1X06T83VZRHXDWSH9RAP4Z0", + "kind": "memory", + "score": 0.9999256134033204, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X060R52T1PEXSFG92WM307", + "id": "01M1X06T831W280861RHDGEHW7", + "kind": "memory", + "score": 0.9729819893836976, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + }, + { + "expansion_handle": "memory:01M1X060M5GC897T5Z6GA6S8YY", + "id": "01M1X06T83923W4AE5KPJMMQ3C", + "kind": "memory", + "score": 0.5573575496673584, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1521.5992, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3506, + "mcp_result_bytes": 3669, + "wire_bytes": 3705, + "reported_used_tokens": 3669, + "working_set_bytes": 922910720, + "peak_working_set_bytes": 923828224 + }, + { + "query": "KIMETSU_RUNS_GC env opt-out TraceWriter create gc_old_runs caller", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05X1YVGZWW2HXWBYC2WGX", + "id": "01M1X06VR4HXQKVSG2ZYFG83H9", + "kind": "memory", + "score": 0.9999783039093018, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1212.2203, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 762, + "mcp_result_bytes": 843, + "wire_bytes": 879, + "reported_used_tokens": 843, + "working_set_bytes": 922959872, + "peak_working_set_bytes": 923873280 + }, + { + "query": "where should I put the KIMETSU_RUNS_GC=0 guard \u2014 inside the GC function or at the call site?", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05X1YVGZWW2HXWBYC2WGX", + "id": "01M1X06WWYMBZAR8908PN5Q9X5", + "kind": "memory", + "score": 0.9999699592590332, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1314.8932, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 762, + "mcp_result_bytes": 843, + "wire_bytes": 879, + "reported_used_tokens": 843, + "working_set_bytes": 923234304, + "peak_working_set_bytes": 924155904 + }, + { + "query": "git_init_boundary ProjectPaths::discover temp dir user brain isolation", + "ranked": [ + "init-project-git-boundary", + "git-worktree-brain-isolation", + "testing-temp-dirs-ci" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05X2XE8B8NBDATM3RYT6N", + "id": "01M1X06Y64MR0ZXT1R691HFWSA", + "kind": "memory", + "score": 0.9999781847000122, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + }, + { + "expansion_handle": "memory:01M1X05ZA9QQ47DPP3Q0PVXQSH", + "id": "01M1X06Y64NHA70540W71YEZ9T", + "kind": "memory", + "score": 0.998727023601532, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root \u2014 if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + }, + { + "expansion_handle": "memory:01M1X06045GR186PNBCJEQVZFJ", + "id": "01M1X06Y642CVB9FYA6SSNCRYS", + "kind": "memory", + "score": 0.8577821850776672, + "summary": "project:fact - [tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1293.123, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1961, + "mcp_result_bytes": 2078, + "wire_bytes": 2114, + "reported_used_tokens": 2078, + "working_set_bytes": 923504640, + "peak_working_set_bytes": 924418048 + }, + { + "query": "my test calls init_project but it writes to the real ~/.kimetsu instead of the temp folder \u2014 why?", + "ranked": [ + "init-project-git-boundary", + "cargo-feature-unification-embeddings", + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05X2XE8B8NBDATM3RYT6N", + "id": "01M1X06ZEK318NDMYRKMT9A4TE", + "kind": "memory", + "score": 0.9999724626541138, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + }, + { + "expansion_handle": "memory:01M1X05WQEADDZVS78GM78YF9F", + "id": "01M1X06ZEKW1VS3E9A8K3TTJE1", + "kind": "memory", + "score": 0.5625059604644775, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X05WWMK1W8X29RJHCH6WVK", + "id": "01M1X06ZEM9RJG7T04NBHZBK27", + "kind": "memory", + "score": 0.5564239621162415, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1785.0711000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2263, + "mcp_result_bytes": 2392, + "wire_bytes": 2428, + "reported_used_tokens": 2392, + "working_set_bytes": 923545600, + "peak_working_set_bytes": 924459008 + }, + { + "query": "clap command version KIMETSU_VERSION_DISPLAY cfg feature embeddings", + "ranked": [ + "clap-version-build-flavor", + "cargo-feature-unification-embeddings" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05X3Z1E6HBX2QQKX4RS2R", + "id": "01M1X0716E034AK0S9P77CN7HX", + "kind": "memory", + "score": 0.9999767541885376, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + }, + { + "expansion_handle": "memory:01M1X05WQEADDZVS78GM78YF9F", + "id": "01M1X0716E6X2B0DWWV6RT5F4Z", + "kind": "memory", + "score": 0.9207596778869628, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1238.3541, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1922, + "mcp_result_bytes": 2041, + "wire_bytes": 2077, + "reported_used_tokens": 2041, + "working_set_bytes": 923545600, + "peak_working_set_bytes": 924459008 + }, + { + "query": "how do I show the build flavor (lean vs embeddings) in the kimetsu --version output?", + "ranked": [ + "clap-version-build-flavor", + "cargo-feature-unification-embeddings" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05X3Z1E6HBX2QQKX4RS2R", + "id": "01M1X072D7BWKRMC652C5JKSXB", + "kind": "memory", + "score": 0.9999544620513916, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + }, + { + "expansion_handle": "memory:01M1X05WQEADDZVS78GM78YF9F", + "id": "01M1X072D7T4EVCVJH7MENK702", + "kind": "memory", + "score": 0.8056868314743042, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1480.4558000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1923, + "mcp_result_bytes": 2042, + "wire_bytes": 2078, + "reported_used_tokens": 2042, + "working_set_bytes": 923746304, + "peak_working_set_bytes": 924663808 + }, + { + "query": "Harbor pyiceberg os.getcwd stale WSL2 DrvFs worker-result subprocess re-exec", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05X5FTM5KRD3BV5T455BF", + "id": "01M1X073XJSQHDESWGK8BT32SH", + "kind": "memory", + "score": 0.9999732971191406, + "summary": "project:fact - [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1553.1192, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1026, + "mcp_result_bytes": 1107, + "wire_bytes": 1143, + "reported_used_tokens": 1107, + "working_set_bytes": 924057600, + "peak_working_set_bytes": 924966912 + }, + { + "query": "why does my kbench sweep crash after the first trial with 'result.json missing' on WSL2?", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05X5FTM5KRD3BV5T455BF", + "id": "01M1X075C8ZGCT7SE6JNEV2GZ2", + "kind": "memory", + "score": 0.9995384216308594, + "summary": "project:fact - [2026-09-07] [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1478.8541, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1039, + "mcp_result_bytes": 1120, + "wire_bytes": 1156, + "reported_used_tokens": 1120, + "working_set_bytes": 924069888, + "peak_working_set_bytes": 924991488 + }, + { + "query": "rusqlite VACUUM transaction WAL checkpoint wal_checkpoint TRUNCATE", + "ranked": [ + "sqlite-vacuum-wal-checkpoint", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05X72N3X1MXQDR7GT0DHX", + "id": "01M1X076TXVCWSAFK67D2KJPAV", + "kind": "memory", + "score": 0.9998983144760132, + "summary": "project:fact - [tags: rust sqlite vacuum rusqlite windows] When implementing SQLite VACUUM in rusqlite: VACUUM cannot run inside a transaction. rusqlite's Connection does not hold an implicit transaction, so `conn.execute_batch(\"VACUUM;\")` works directly. After VACUUM, run `PRAGMA wal_checkpoint(TRUNCATE);` before measuring file size \u2014 on Windows the WAL file can hold significant space that isn't reflected in the main db file until the checkpoint runs." + }, + { + "expansion_handle": "memory:01M1X05XEEYRHK31DS2Q07F462", + "id": "01M1X076TXCESMZQBMWY6G2334", + "kind": "memory", + "score": 0.9661141633987428, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1438.5652, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1507, + "mcp_result_bytes": 1610, + "wire_bytes": 1646, + "reported_used_tokens": 1610, + "working_set_bytes": 925368320, + "peak_working_set_bytes": 926269440 + }, + { + "query": "my SQLite VACUUM reports the file shrank but the disk usage stayed the same \u2014 Windows WAL?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1333.8675, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 925446144, + "peak_working_set_bytes": 926363648 + }, + { + "query": "add_memory import dedup seen_ids snapshot pre-existing active memory IDs", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05X81KPDSPB1TQVAZC1S6", + "id": "01M1X079GZBJ82RJHMTMJKNZ0D", + "kind": "memory", + "score": 0.9999771118164062, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount \u2014 both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1310.2041, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 966, + "mcp_result_bytes": 1047, + "wire_bytes": 1083, + "reported_used_tokens": 1047, + "working_set_bytes": 925573120, + "peak_working_set_bytes": 926482432 + }, + { + "query": "brain import re-imports the same JSON file but the deduplication counter is wrong \u2014 why?", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05X81KPDSPB1TQVAZC1S6", + "id": "01M1X07ASWF40NQA7BCXPQPEHA", + "kind": "memory", + "score": 0.7890511751174927, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount \u2014 both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1366.3064, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 965, + "mcp_result_bytes": 1046, + "wire_bytes": 1082, + "reported_used_tokens": 1046, + "working_set_bytes": 925663232, + "peak_working_set_bytes": 926580736 + }, + { + "query": "toml::from_str Value parse document unexpected content str.parse", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05X9CMX71JQVA19Z3N8D5", + "id": "01M1X07C4GVNKSK46VGGDSBNBP", + "kind": "memory", + "score": 0.9994491934776306, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1144.8267, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 734, + "mcp_result_bytes": 815, + "wire_bytes": 851, + "reported_used_tokens": 815, + "working_set_bytes": 925888512, + "peak_working_set_bytes": 926801920 + }, + { + "query": "how do I parse a TOML configuration file into a toml::Value in toml 0.9?", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05X9CMX71JQVA19Z3N8D5", + "id": "01M1X07D8D55QZF2JFHM8YXY4F", + "kind": "memory", + "score": 0.9999773502349854, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1240.3846, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 733, + "mcp_result_bytes": 814, + "wire_bytes": 850, + "reported_used_tokens": 814, + "working_set_bytes": 926093312, + "peak_working_set_bytes": 927006720 + }, + { + "query": "CIM CreationDate DMTF WMI ps etimes started_at assess_mcp_skew", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05XAHD3AJ4JMWCBQZ402J", + "id": "01M1X07EFE51Z0EWEQ9TAC45FM", + "kind": "memory", + "score": 0.9999735355377196, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1189.4102, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 924, + "mcp_result_bytes": 1013, + "wire_bytes": 1049, + "reported_used_tokens": 1013, + "working_set_bytes": 926150656, + "peak_working_set_bytes": 927055872 + }, + { + "query": "how do I read a process start time on both Windows and Linux in pure Rust?", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05XAHD3AJ4JMWCBQZ402J", + "id": "01M1X07FMKW4HZTQ2ECKECRWD7", + "kind": "memory", + "score": 0.9942069053649902, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1309.362, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 923, + "mcp_result_bytes": 1012, + "wire_bytes": 1048, + "reported_used_tokens": 1012, + "working_set_bytes": 926384128, + "peak_working_set_bytes": 927309824 + }, + { + "query": "processes_locking_target decide_preflight_action BufRead Write update.rs", + "ranked": [ + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05XC02KE6DF94YEWD03PB", + "id": "01M1X07GXGBZE8X2A2W1BG27MX", + "kind": "memory", + "score": 0.999950647354126, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1190.1389, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1132, + "mcp_result_bytes": 1213, + "wire_bytes": 1249, + "reported_used_tokens": 1213, + "working_set_bytes": 926449664, + "peak_working_set_bytes": 927367168 + }, + { + "query": "how should I reuse the existing process enumerator in the update preflight check to avoid a second PowerShell query?", + "ranked": [ + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05XC02KE6DF94YEWD03PB", + "id": "01M1X07J386Q97N049DN0S32HW", + "kind": "memory", + "score": 0.9999661445617676, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1416.9994, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1132, + "mcp_result_bytes": 1213, + "wire_bytes": 1249, + "reported_used_tokens": 1213, + "working_set_bytes": 926674944, + "peak_working_set_bytes": 927592448 + }, + { + "query": "cfg_attr windows allow dead_code parse_unix_ps cross-platform tests", + "ranked": [ + "cfg-cross-platform-dead-code", + "process-start-time-cross-platform", + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05XDC40RB4SW1AT2GB9Z4", + "id": "01M1X07KETC6CMYVABF9JCZAVF", + "kind": "memory", + "score": 0.9999802112579346, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + }, + { + "expansion_handle": "memory:01M1X05XAHD3AJ4JMWCBQZ402J", + "id": "01M1X07KETTJP1SX749QZCWKWE", + "kind": "memory", + "score": 0.8274164795875549, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + }, + { + "expansion_handle": "memory:01M1X05XC02KE6DF94YEWD03PB", + "id": "01M1X07KET4S2D7ADPEKBSEN4D", + "kind": "memory", + "score": 0.7637738585472107, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1147.5801, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2422, + "mcp_result_bytes": 2547, + "wire_bytes": 2583, + "reported_used_tokens": 2547, + "working_set_bytes": 926691328, + "peak_working_set_bytes": 927604736 + }, + { + "query": "how do I keep a function that is only called on Unix from triggering dead_code warnings on Windows?", + "ranked": [ + "cfg-cross-platform-dead-code" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05XDC40RB4SW1AT2GB9Z4", + "id": "01M1X07MJMYNK8K4CRDJK20RX1", + "kind": "memory", + "score": 0.9998409748077391, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1293.6631, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 939, + "reported_used_tokens": 903, + "working_set_bytes": 926806016, + "peak_working_set_bytes": 927719424 + }, + { + "query": "deadlocking a Rust mutex in integration tests", + "ranked": [ + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05WHEZZFA615D9M78AXGX", + "id": "01M1X07NVCH6EM7D1H8GZP0FJ6", + "kind": "memory", + "score": 0.9991264939308168, + "summary": "project:fact - [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure \u2014 `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1236.8986, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 796, + "mcp_result_bytes": 877, + "wire_bytes": 913, + "reported_used_tokens": 877, + "working_set_bytes": 927100928, + "peak_working_set_bytes": 928014336 + }, + { + "query": "benchmarking retrieval quality across embedders", + "ranked": [ + "onnx-quantization-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05YZKN4Y3GJBMHHAJ3NK7", + "id": "01M1X07Q1XPW0PAM5YF28QBJK9", + "kind": "memory", + "score": 0.7459741234779358, + "summary": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals \u2014 cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 1229.5629999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 978, + "mcp_result_bytes": 1059, + "wire_bytes": 1095, + "reported_used_tokens": 1059, + "working_set_bytes": 927154176, + "peak_working_set_bytes": 928063488 + }, + { + "query": "process memory working set RSS peak measurement Windows", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1418.4565, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 932438016, + "peak_working_set_bytes": 933330944 + }, + { + "query": "cloning a git repository server-side into a managed checkout", + "ranked": [ + "remote-ingest-split-roots", + "git-sparse-checkout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05WJQ71HE168C1JX89W7A", + "id": "01M1X07SNDEESG457Y8Z9JDZ56", + "kind": "memory", + "score": 0.9940817952156068, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1X05ZCMYMCY0306J19N23J2", + "id": "01M1X07SNDNKR7AM3Z6MFPYTNV", + "kind": "memory", + "score": 0.9041922092437744, + "summary": "project:fact - [tags: git sparse-checkout partial-clone bandwidth] `git sparse-checkout init --cone` combined with `git clone --filter=blob:none` (partial clone) fetches only the commit graph and tree objects, not blobs. Individual blobs are fetched on demand when accessed. This cuts clone time for large repos from minutes to seconds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1518.2597, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1744, + "mcp_result_bytes": 1843, + "wire_bytes": 1879, + "reported_used_tokens": 1843, + "working_set_bytes": 932438016, + "peak_working_set_bytes": 933343232 + }, + { + "query": "SigV4 signing HTTP requests in Rust", + "ranked": [ + "aws-sigv4-bedrock-blocking", + "aws-presigned-urls", + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05WZN8M0WJBHX8WAN1FW1", + "id": "01M1X07V4MEX66R025EVTGWF2M", + "kind": "memory", + "score": 0.995133101940155, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1X060R52T1PEXSFG92WM307", + "id": "01M1X07V4M98KTE45A4D76HBC3", + "kind": "memory", + "score": 0.9750049114227296, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + }, + { + "expansion_handle": "memory:01M1X05WS4SP2GV31Y7Z83MPR1", + "id": "01M1X07V4MG0C5WF4YT3T0ZHN0", + "kind": "memory", + "score": 0.936759352684021, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1529.6779000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2838, + "mcp_result_bytes": 2983, + "wire_bytes": 3019, + "reported_used_tokens": 2983, + "working_set_bytes": 932380672, + "peak_working_set_bytes": 933343232 + }, + { + "query": "cargo test --workspace feature flag changes broke my unit tests", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05WQEADDZVS78GM78YF9F", + "id": "01M1X07WM9XNNKEFR96FDY7EXE", + "kind": "memory", + "score": 0.9825970530509948, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X05XVN17RZ1ZWVR1CH6N4A", + "id": "01M1X07WM95FE89N4FJX2EHW2T", + "kind": "memory", + "score": 0.8159734606742859, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1289.9003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1837, + "mcp_result_bytes": 1940, + "wire_bytes": 1976, + "reported_used_tokens": 1940, + "working_set_bytes": 932499456, + "peak_working_set_bytes": 933412864 + }, + { + "query": "how do I make pasta carbonara?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 1672.7581, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 932687872, + "peak_working_set_bytes": 933601280 + }, + { + "query": "what is the offside rule in football?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 1608.0599, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 933105664, + "peak_working_set_bytes": 934014976 + }, + { + "query": "best way to train for a half marathon", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 1538.8393, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 933437440, + "peak_working_set_bytes": 934359040 + }, + { + "query": "my test passes when I run it alone but fails under cargo test --workspace", + "ranked": [ + "cargo-feature-unification-embeddings" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05WQEADDZVS78GM78YF9F", + "id": "01M1X082KH2XG7GWR0ZVY2CYRP", + "kind": "memory", + "score": 0.9996844530105592, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1498.0509, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1148, + "mcp_result_bytes": 1233, + "wire_bytes": 1269, + "reported_used_tokens": 1233, + "working_set_bytes": 933568512, + "peak_working_set_bytes": 934486016 + }, + { + "query": "all the project tests started hanging forever after I added my new test", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1405.3462, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 933593088, + "peak_working_set_bytes": 934502400 + }, + { + "query": "my integration test silently wrote memories into my real home brain instead of the temp workspace", + "ranked": [ + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05X2XE8B8NBDATM3RYT6N", + "id": "01M1X085E3KV9QB40183MEHQ86", + "kind": "memory", + "score": 0.7656484842300415, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1804.9654, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 780, + "mcp_result_bytes": 861, + "wire_bytes": 897, + "reported_used_tokens": 861, + "working_set_bytes": 933601280, + "peak_working_set_bytes": 934518784 + }, + { + "query": "where should the env-var opt-out check live for a cleanup feature triggered from a hot code path", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05X1YVGZWW2HXWBYC2WGX", + "id": "01M1X0876GSW8F4MG3F9CNYK1B", + "kind": "memory", + "score": 0.9995137453079224, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1338.2575000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 761, + "mcp_result_bytes": 842, + "wire_bytes": 878, + "reported_used_tokens": 842, + "working_set_bytes": 933609472, + "peak_working_set_bytes": 934526976 + }, + { + "query": "the brain database file stays huge on Windows even after deleting most rows", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1271.7581, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 933613568, + "peak_working_set_bytes": 934535168 + }, + { + "query": "re-importing the same exported memories file counts them as new instead of deduplicated", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05X81KPDSPB1TQVAZC1S6", + "id": "01M1X089R0ZB7XRF9DE0T3WXMF", + "kind": "memory", + "score": 0.9581347703933716, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount \u2014 both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1762.7979, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 965, + "mcp_result_bytes": 1046, + "wire_bytes": 1082, + "reported_used_tokens": 1046, + "working_set_bytes": 933621760, + "peak_working_set_bytes": 934539264 + }, + { + "query": "a helper function only called on Unix at runtime fails the dead-code lint on the Windows build", + "ranked": [ + "cfg-cross-platform-dead-code" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05XDC40RB4SW1AT2GB9Z4", + "id": "01M1X08BFFZXRCGQ0M9YWYSCBA", + "kind": "memory", + "score": 0.9773045778274536, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1312.8619, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 939, + "reported_used_tokens": 903, + "working_set_bytes": 933634048, + "peak_working_set_bytes": 934555648 + }, + { + "query": "the second Terminal-Bench trial always crashes even though the first one passes", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05X5FTM5KRD3BV5T455BF", + "id": "01M1X08CQTKRB3AHV9J8WVXPQM", + "kind": "memory", + "score": 0.9474697113037108, + "summary": "project:fact - [2026-09-07] [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1420.1721, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1038, + "mcp_result_bytes": 1119, + "wire_bytes": 1155, + "reported_used_tokens": 1119, + "working_set_bytes": 933638144, + "peak_working_set_bytes": 934555648 + }, + { + "query": "how does doctor tell a running MCP server process is older than the kimetsu binary on disk", + "ranked": [ + "kimetsu-daemon-lifecycle" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0613T09134FY71BP84VZG", + "id": "01M1X08E4HXMMH4QZFK4MG58X2", + "kind": "memory", + "score": 0.9223618507385254, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1462.7285, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 752, + "mcp_result_bytes": 833, + "wire_bytes": 869, + "reported_used_tokens": 833, + "working_set_bytes": 933638144, + "peak_working_set_bytes": 934555648 + }, + { + "query": "the self-update preflight needs the list of running kimetsu processes without re-running the OS query", + "ranked": [ + "windows-update-process-locking", + "kimetsu-daemon-lifecycle" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05XC02KE6DF94YEWD03PB", + "id": "01M1X08FJ5F7S513H9CA6Q5C1P", + "kind": "memory", + "score": 0.9925383925437928, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + }, + { + "expansion_handle": "memory:01M1X0613T09134FY71BP84VZG", + "id": "01M1X08FJ5E6V7GDY21EB2NRWF", + "kind": "memory", + "score": 0.5589243769645691, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1304.6035, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1658, + "mcp_result_bytes": 1757, + "wire_bytes": 1793, + "reported_used_tokens": 1757, + "working_set_bytes": 933638144, + "peak_working_set_bytes": 934555648 + }, + { + "query": "parsing the WMI DMTF CreationDate timestamp into epoch seconds without extra crates", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05XAHD3AJ4JMWCBQZ402J", + "id": "01M1X08GV7KJM4SYQJ3VSHFPVB", + "kind": "memory", + "score": 0.9751563668251038, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1603.7668, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 924, + "mcp_result_bytes": 1013, + "wire_bytes": 1049, + "reported_used_tokens": 1013, + "working_set_bytes": 933646336, + "peak_working_set_bytes": 934559744 + }, + { + "query": "calling Bedrock InvokeModel from blocking reqwest without the aws sdk", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking", + "aws-region-resolution" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05WS4SP2GV31Y7Z83MPR1", + "id": "01M1X08JDRQSSNKN618M2R2JW4", + "kind": "memory", + "score": 0.99994158744812, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X05WZN8M0WJBHX8WAN1FW1", + "id": "01M1X08JDRDWAE0R4KKKEF2SV2", + "kind": "memory", + "score": 0.9989731311798096, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1X060NPXN24BW4FY2DMGRH1", + "id": "01M1X08JDSXHXX58KHGYBQA7D6", + "kind": "memory", + "score": 0.6464323401451111, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1607.9994, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2787, + "mcp_result_bytes": 2932, + "wire_bytes": 2968, + "reported_used_tokens": 2932, + "working_set_bytes": 933650432, + "peak_working_set_bytes": 934567936 + }, + { + "query": "how do I rotate the encryption key protecting the kimetsu brain database", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 1317.3627, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 933658624, + "peak_working_set_bytes": 934567936 + }, + { + "query": "which tokio runtime worker-thread settings does the kimetsu MCP server use", + "ranked": [ + "tokio-blocking-in-async", + "tokio-runtime-in-tests", + "tokio-spawn-blocking", + "mcp-stdout-protocol" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05ZHKS6ZS872XPKZ5N763", + "id": "01M1X08N8SGTCASRNSS552VX86", + "kind": "memory", + "score": 0.9897258877754213, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + }, + { + "expansion_handle": "memory:01M1X05ZJXG8K42RHMEJTR53F4", + "id": "01M1X08N8SBCEEDWW5RNWEZ7ZD", + "kind": "memory", + "score": 0.9347747564315796, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + }, + { + "expansion_handle": "memory:01M1X05ZQ2WBH9P65YQCV6XM2J", + "id": "01M1X08N8SXSW3BW2RK1RCVN1S", + "kind": "memory", + "score": 0.840076208114624, + "summary": "project:fact - [tags: tokio spawn_blocking thread-pool rust blocking] `tokio::task::spawn_blocking` places work on a dedicated blocking thread pool (default up to 512 threads, configurable via `Builder::max_blocking_threads`). Each call creates or reuses a thread \u2014 there's no true pooling, threads may be created on demand. For many short-duration blocking calls (e.g." + }, + { + "expansion_handle": "memory:01M1X060CNDKDV0G74BPVB9WQW", + "id": "01M1X08N8SN9AKF6VFJ37QBJX0", + "kind": "memory", + "score": 0.6246721744537354, + "summary": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 1326.4688, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2364, + "mcp_result_bytes": 2507, + "wire_bytes": 2543, + "reported_used_tokens": 2507, + "working_set_bytes": 933658624, + "peak_working_set_bytes": 934572032 + }, + { + "query": "how does kimetsu sync memories between two machines over the network", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 1275.4302, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 933658624, + "peak_working_set_bytes": 934572032 + }, + { + "query": "recovering a corrupted usearch ANN index after a power loss", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 1184.69, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 933662720, + "peak_working_set_bytes": 934576128 + }, + { + "query": "what postgres schema should I use to store kimetsu memories", + "ranked": [ + "onnx-dim-mismatch" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05Z4DT1C1RCDZHTSN5X5N", + "id": "01M1X08RZ3VVE6T02WD8JXRD8A", + "kind": "memory", + "score": 0.6243454217910767, + "summary": "project:fact - [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results \u2014 the ANN index shape mismatch isn't always caught at runtime." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 1222.9958000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 740, + "mcp_result_bytes": 821, + "wire_bytes": 857, + "reported_used_tokens": 821, + "working_set_bytes": 933666816, + "peak_working_set_bytes": 934576128 + }, + { + "query": "the whole CI job just froze forever with no failure output after my latest test PR", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1258.7005, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 933707776, + "peak_working_set_bytes": 934625280 + }, + { + "query": "running the test suite left junk state in my home directory", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1268.3688, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 933707776, + "peak_working_set_bytes": 934625280 + }, + { + "query": "I deleted a bunch of old rows but the file on disk is still the same size", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1246.2737, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 933715968, + "peak_working_set_bytes": 934633472 + }, + { + "query": "adding one new crate quietly changed how the whole workspace builds", + "ranked": [ + "cargo-lockfile-drift", + "cargo-feature-unification-embeddings" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05XS87CVAXTDXJWSMWWVQ", + "id": "01M1X08XV5RDK2E86ZM496BY5B", + "kind": "memory", + "score": 0.9958756566047668, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this \u2014 it errors on any lockfile diff." + }, + { + "expansion_handle": "memory:01M1X05WQEADDZVS78GM78YF9F", + "id": "01M1X08XV56XJE12Y1TGKPY1V3", + "kind": "memory", + "score": 0.9790327548980712, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 0.5, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1449.1175, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1671, + "mcp_result_bytes": 1774, + "wire_bytes": 1810, + "reported_used_tokens": 1774, + "working_set_bytes": 933715968, + "peak_working_set_bytes": 934637568 + }, + { + "query": "we cannot pull an async runtime into the agent just to talk to AWS", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1769.236, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 933715968, + "peak_working_set_bytes": 934637568 + }, + { + "query": "users should be able to tell which build variant they installed from the version output", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1563.981, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 933715968, + "peak_working_set_bytes": 934637568 + }, + { + "query": "what gotchas should I expect writing process-inspection code that works on both Windows and Unix?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1532.2429, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 933715968, + "peak_working_set_bytes": 934637568 + }, + { + "query": "why might tests behave differently on my machine than in the full CI run?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1429.463, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 933715968, + "peak_working_set_bytes": 934637568 + }, + { + "query": "what do I need to know before wiring kimetsu into a brand new host agent?", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05WV9R2ZXNMMJDH1K8ANT", + "id": "01M1X095D9JTFA5JP8PEAG3AS5", + "kind": "memory", + "score": 0.8340779542922974, + "summary": "project:fact - [2026-09-07] [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 0.3333333333333333, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1589.1416000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1072, + "mcp_result_bytes": 1153, + "wire_bytes": 1189, + "reported_used_tokens": 1153, + "working_set_bytes": 933969920, + "peak_working_set_bytes": 934887424 + }, + { + "query": "tell me everything relevant to running kimetsu against AWS", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1436.4473, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 933974016, + "peak_working_set_bytes": 934887424 + }, + { + "query": "ingesting a cloned repo when the brain lives under a different root", + "ranked": [ + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05WJQ71HE168C1JX89W7A", + "id": "01M1X098C5XAAACKJ0AJA650KN", + "kind": "memory", + "score": 0.9854778051376344, + "summary": "project:fact - [2026-09-07] [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1616.3935000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1274, + "mcp_result_bytes": 1355, + "wire_bytes": 1391, + "reported_used_tokens": 1355, + "working_set_bytes": 935022592, + "peak_working_set_bytes": 935931904 + }, + { + "query": "streamable-http transport entry for openclaw.json with a bearer token", + "ranked": [ + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05WN6EB9RPFK8EF9QTXFD", + "id": "01M1X099YM1BE060CPES4EDWK1", + "kind": "memory", + "score": 0.9984819293022156, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1767.8703, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 996, + "mcp_result_bytes": 1125, + "wire_bytes": 1161, + "reported_used_tokens": 1125, + "working_set_bytes": 935022592, + "peak_working_set_bytes": 935940096 + }, + { + "query": "serializing ingests with a tokio mutex to avoid checkout races", + "ranked": [ + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05WJQ71HE168C1JX89W7A", + "id": "01M1X09BP2706KN4TRAD8B7J61", + "kind": "memory", + "score": 0.9989351630210876, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1651.5988, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1261, + "mcp_result_bytes": 1342, + "wire_bytes": 1378, + "reported_used_tokens": 1342, + "working_set_bytes": 935026688, + "peak_working_set_bytes": 935948288 + }, + { + "query": "percent-encoding the colon in the bedrock model id for the invoke URL", + "ranked": [ + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05WS4SP2GV31Y7Z83MPR1", + "id": "01M1X09D9Q5QJG7RXG2WFF124P", + "kind": "memory", + "score": 0.9490103721618652, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1540.786, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1204, + "mcp_result_bytes": 1293, + "wire_bytes": 1329, + "reported_used_tokens": 1293, + "working_set_bytes": 935026688, + "peak_working_set_bytes": 935948288 + }, + { + "query": "deduplicating re-imported memories against pre-existing ids", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05X81KPDSPB1TQVAZC1S6", + "id": "01M1X09ESW6DTCDHM954MTME1W", + "kind": "memory", + "score": 0.996511161327362, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount \u2014 both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1448.3905, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 966, + "mcp_result_bytes": 1047, + "wire_bytes": 1083, + "reported_used_tokens": 1047, + "working_set_bytes": 935026688, + "peak_working_set_bytes": 935948288 + }, + { + "query": "parsing DMTF datetimes", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05XAHD3AJ4JMWCBQZ402J", + "id": "01M1X09G6SZP996H4KMN1G45Q3", + "kind": "memory", + "score": 0.990456759929657, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1086.8818, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 923, + "mcp_result_bytes": 1012, + "wire_bytes": 1048, + "reported_used_tokens": 1012, + "working_set_bytes": 935038976, + "peak_working_set_bytes": 935948288 + }, + { + "query": "how should install derive a stable identifier from the git remote URL?", + "ranked": [ + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05WN6EB9RPFK8EF9QTXFD", + "id": "01M1X09H931B5D0BBEW6CFFGM6", + "kind": "memory", + "score": 0.9954527020454408, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1633.0891, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 995, + "mcp_result_bytes": 1124, + "wire_bytes": 1160, + "reported_used_tokens": 1124, + "working_set_bytes": 935178240, + "peak_working_set_bytes": 936091648 + }, + { + "query": "the secret token must not end up written into the host config file", + "ranked": [ + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05WN6EB9RPFK8EF9QTXFD", + "id": "01M1X09JW04Q5R87ZMBY99JNS1", + "kind": "memory", + "score": 0.8757492899894714, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1829.2432000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 996, + "mcp_result_bytes": 1125, + "wire_bytes": 1161, + "reported_used_tokens": 1125, + "working_set_bytes": 935182336, + "peak_working_set_bytes": 936099840 + }, + { + "query": "keep the cleanup logic unit-testable without touching environment variables", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1312.3862, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 935182336, + "peak_working_set_bytes": 936099840 + }, + { + "query": "how do we stop the server from cloning arbitrary repos clients request?", + "ranked": [ + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05WJQ71HE168C1JX89W7A", + "id": "01M1X09NY3PC3ZMT8FS92H5AK7", + "kind": "memory", + "score": 0.6168935894966125, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1598.0899, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1261, + "mcp_result_bytes": 1342, + "wire_bytes": 1378, + "reported_used_tokens": 1342, + "working_set_bytes": 935182336, + "peak_working_set_bytes": 936099840 + }, + { + "query": "make sure a wrong guess about a host plugin API never breaks that host", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05WWMK1W8X29RJHCH6WVK", + "id": "01M1X09QG1T3AX5BXNMMDKFW8H", + "kind": "memory", + "score": 0.9010460376739502, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1617.3755, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 803, + "mcp_result_bytes": 892, + "wire_bytes": 928, + "reported_used_tokens": 892, + "working_set_bytes": 935194624, + "peak_working_set_bytes": 936116224 + }, + { + "query": "which wire-format trick lets us reuse the existing Anthropic request builder for AWS?", + "ranked": [ + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05WS4SP2GV31Y7Z83MPR1", + "id": "01M1X09S2V5NGDWZHQYD3XTG7F", + "kind": "memory", + "score": 0.9861636757850648, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1613.6931, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1203, + "mcp_result_bytes": 1292, + "wire_bytes": 1328, + "reported_used_tokens": 1292, + "working_set_bytes": 935325696, + "peak_working_set_bytes": 936243200 + }, + { + "query": "the self-update froze because something was still holding the executable", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1308.3585, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 935329792, + "peak_working_set_bytes": 936243200 + }, + { + "query": "our notes about the extension API turned out wrong once we read the actual repo", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05WWMK1W8X29RJHCH6WVK", + "id": "01M1X09VYPFCZE5BVM9N0HD1RF", + "kind": "memory", + "score": 0.7192176580429077, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1810.6789, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 803, + "mcp_result_bytes": 892, + "wire_bytes": 928, + "reported_used_tokens": 892, + "working_set_bytes": 935329792, + "peak_working_set_bytes": 936243200 + }, + { + "query": "half the benchmark trials die right after the first one finishes", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1510.2285, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 935333888, + "peak_working_set_bytes": 936251392 + }, + { + "query": "I need this parser visible to tests on every OS even though only one OS calls it", + "ranked": [ + "cfg-cross-platform-dead-code" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05XDC40RB4SW1AT2GB9Z4", + "id": "01M1X09Z77P6PYJ7K2TZ06GXAB", + "kind": "memory", + "score": 0.5910465121269226, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1555.5303000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 821, + "mcp_result_bytes": 902, + "wire_bytes": 938, + "reported_used_tokens": 902, + "working_set_bytes": 935342080, + "peak_working_set_bytes": 936251392 + }, + { + "query": "the config file content refuses to parse even though the TOML looks valid", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05X9CMX71JQVA19Z3N8D5", + "id": "01M1X0A0PS27T4QDD4Q0N4W7TE", + "kind": "memory", + "score": 0.8025842905044556, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1572.3221, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 733, + "mcp_result_bytes": 814, + "wire_bytes": 850, + "reported_used_tokens": 814, + "working_set_bytes": 935665664, + "peak_working_set_bytes": 936583168 + }, + { + "query": "the remote server must refresh its checkout before answering file queries", + "ranked": [ + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05WJQ71HE168C1JX89W7A", + "id": "01M1X0A27EFH1PAKVH11PRR02F", + "kind": "memory", + "score": 0.7441006898880005, + "summary": "project:fact - [2026-09-07] [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1636.6743000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1274, + "mcp_result_bytes": 1355, + "wire_bytes": 1391, + "reported_used_tokens": 1355, + "working_set_bytes": 935665664, + "peak_working_set_bytes": 936583168 + }, + { + "query": "tests must not climb to a parent git repository when resolving project paths", + "ranked": [ + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05X2XE8B8NBDATM3RYT6N", + "id": "01M1X0A3TW2K6WJ0Q983AFHW2W", + "kind": "memory", + "score": 0.9996840953826904, + "summary": "project:fact - [2026-09-07] [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1668.7975000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 794, + "mcp_result_bytes": 875, + "wire_bytes": 911, + "reported_used_tokens": 875, + "working_set_bytes": 936734720, + "peak_working_set_bytes": 937680896 + }, + { + "query": "how do I test request signing deterministically when timestamps change every run?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1561.0924000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 936734720, + "peak_working_set_bytes": 937680896 + }, + { + "query": "adding a new variant to the host target enum - which places will I forget to update?", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05WV9R2ZXNMMJDH1K8ANT", + "id": "01M1X0A70HJY3S61PX19KH2ECT", + "kind": "memory", + "score": 0.885578989982605, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1819.3446, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1058, + "mcp_result_bytes": 1139, + "wire_bytes": 1175, + "reported_used_tokens": 1139, + "working_set_bytes": 936853504, + "peak_working_set_bytes": 937766912 + }, + { + "query": "how do I enable GPU acceleration for kimetsu embedding inference", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 1302.6813, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 936939520, + "peak_working_set_bytes": 937832448 + }, + { + "query": "how do I throttle kimetsu API spend per month", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 1765.0409, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 937197568, + "peak_working_set_bytes": 938115072 + }, + { + "query": "can the kimetsu brain database be stored in S3 instead of on disk", + "ranked": [ + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X060R52T1PEXSFG92WM307", + "id": "01M1X0ABRJ360MVCXHEC410X8R", + "kind": "memory", + "score": 0.6605784893035889, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 1198.6885, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 874, + "mcp_result_bytes": 955, + "wire_bytes": 991, + "reported_used_tokens": 955, + "working_set_bytes": 937250816, + "peak_working_set_bytes": 938168320 + }, + { + "query": "how do I plug a custom tokenizer into the FTS index", + "ranked": [ + "sqlite-fts5-tokenizer" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05XJ63VKM0Q9FZZK5DSZ5", + "id": "01M1X0ACZ4NSKMK59GZSB9WRK7", + "kind": "memory", + "score": 0.8707897067070007, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 1783.6298, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 671, + "mcp_result_bytes": 756, + "wire_bytes": 792, + "reported_used_tokens": 756, + "working_set_bytes": 937713664, + "peak_working_set_bytes": 938618880 + }, + { + "query": "what should I check when kimetsu behaves differently on Windows than on Linux?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1256.5573, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 937734144, + "peak_working_set_bytes": 938647552 + }, + { + "query": "what are the moving parts of the kimetsu remote deployment story?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1285.575, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 937979904, + "peak_working_set_bytes": 938897408 + }, + { + "query": "which lessons cover guarding behavior behind environment variables?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1325.9044, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 938106880, + "peak_working_set_bytes": 939012096 + }, + { + "query": "SQLite BUSY error under concurrent writes", + "ranked": [ + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05XEEYRHK31DS2Q07F462", + "id": "01M1X0AJENQ8YJVNX3CP37EEZQ", + "kind": "memory", + "score": 0.8938739895820618, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1126.9301, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 898, + "mcp_result_bytes": 979, + "wire_bytes": 1016, + "reported_used_tokens": 979, + "working_set_bytes": 938106880, + "peak_working_set_bytes": 939012096 + }, + { + "query": "SQLite WAL mode breaks when the database is on a network share", + "ranked": [ + "sqlite-wal-network-drive", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05XGXEGK24HX3F30XBV45", + "id": "01M1X0AKJ4V2C0N9WM8Z6W9JW3", + "kind": "memory", + "score": 0.999137282371521, + "summary": "project:fact - [2026-09-07] [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + }, + { + "expansion_handle": "memory:01M1X05XEEYRHK31DS2Q07F462", + "id": "01M1X0AKJ4S67EF2Q2SFJAD6WS", + "kind": "memory", + "score": 0.9803613424301147, + "summary": "project:fact - [2026-09-07] [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1305.8609, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1448, + "mcp_result_bytes": 1547, + "wire_bytes": 1584, + "reported_used_tokens": 1547, + "working_set_bytes": 938115072, + "peak_working_set_bytes": 939028480 + }, + { + "query": "my SQLite WAL database causes SQLITE_IOERR_LOCK on a mapped drive", + "ranked": [ + "sqlite-wal-network-drive", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05XGXEGK24HX3F30XBV45", + "id": "01M1X0AMTZA76AW1QJE812XN1Y", + "kind": "memory", + "score": 0.999721109867096, + "summary": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + }, + { + "expansion_handle": "memory:01M1X05XEEYRHK31DS2Q07F462", + "id": "01M1X0AMTZ3F21362DSA2JXNHP", + "kind": "memory", + "score": 0.6342206001281738, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1317.2092, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1421, + "mcp_result_bytes": 1520, + "wire_bytes": 1557, + "reported_used_tokens": 1520, + "working_set_bytes": 938119168, + "peak_working_set_bytes": 939028480 + }, + { + "query": "FTS5 tokenizer configuration for Rust identifiers with underscores", + "ranked": [ + "sqlite-fts5-tokenizer" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05XJ63VKM0Q9FZZK5DSZ5", + "id": "01M1X0AP4AV7Q464CY1MF78Q86", + "kind": "memory", + "score": 0.9998878240585328, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1311.348, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 671, + "mcp_result_bytes": 756, + "wire_bytes": 793, + "reported_used_tokens": 756, + "working_set_bytes": 938119168, + "peak_working_set_bytes": 939028480 + }, + { + "query": "I switched the FTS5 tokenizer but search stopped returning results", + "ranked": [ + "sqlite-fts5-tokenizer" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05XJ63VKM0Q9FZZK5DSZ5", + "id": "01M1X0AQDD4JV952NCV9HMYF0E", + "kind": "memory", + "score": 0.943705141544342, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1325.0307, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 669, + "mcp_result_bytes": 754, + "wire_bytes": 791, + "reported_used_tokens": 754, + "working_set_bytes": 938119168, + "peak_working_set_bytes": 939028480 + }, + { + "query": "optimal SQLite page size for storing embedding vectors", + "ranked": [ + "sqlite-page-size" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05XKGW8AMX1K84V8SH536", + "id": "01M1X0ARRKYCS341QM0XJ3MNP6", + "kind": "memory", + "score": 0.9990623593330384, + "summary": "project:fact - [tags: sqlite page_size performance rusqlite] SQLite's default page_size is 4096 bytes. For a write-heavy brain database with large BLOB payloads (embedding vectors), raising page_size to 16384 reduces fragmentation and improves sequential scan throughput. `PRAGMA page_size = 16384;` must be set BEFORE the first table is created \u2014 changing it on an existing database requires a VACUUM afterward to rebuild all pages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1285.7172, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 809, + "mcp_result_bytes": 890, + "wire_bytes": 927, + "reported_used_tokens": 890, + "working_set_bytes": 938119168, + "peak_working_set_bytes": 939028480 + }, + { + "query": "ON DELETE CASCADE in SQLite does nothing \u2014 foreign keys not enforced", + "ranked": [ + "sqlite-foreign-keys-default-off" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05XMP7RX7AMZ2A33Q6W20", + "id": "01M1X0ASYTMA92MY37KWW17P7P", + "kind": "memory", + "score": 0.9999727010726928, + "summary": "project:fact - [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting \u2014 every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1295.8788000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 736, + "mcp_result_bytes": 817, + "wire_bytes": 854, + "reported_used_tokens": 817, + "working_set_bytes": 938123264, + "peak_working_set_bytes": 939032576 + }, + { + "query": "indexing a JSON metadata column in SQLite without a schema migration", + "ranked": [ + "sqlite-json1-extract" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05XNQQSPZS882QVYZSZ3Z", + "id": "01M1X0AV70QDVCEAB3TG4GSW17", + "kind": "memory", + "score": 0.9998306035995485, + "summary": "project:fact - [tags: sqlite json1 json_extract rusqlite] SQLite's json1 extension (built in since 3.38.0) lets you index and query JSONB columns with `json_extract(col, '$.field')`. To create a partial index over a JSON field: `CREATE INDEX idx ON memories (json_extract(metadata, '$.scope')) WHERE json_extract(metadata, '$.scope') IS NOT NULL;`. Use `json_each` for array fields." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1255.6927, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 757, + "mcp_result_bytes": 838, + "wire_bytes": 875, + "reported_used_tokens": 838, + "working_set_bytes": 938123264, + "peak_working_set_bytes": 939032576 + }, + { + "query": "prepare() vs prepare_cached() in rusqlite hot insert loop", + "ranked": [ + "sqlite-prepared-stmt-cache" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05XPZBE41Z9MH4SDG3ERZ", + "id": "01M1X0AWEDC3P3MB59YQ385FZ9", + "kind": "memory", + "score": 0.9999525547027588, + "summary": "project:fact - [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1244.5283, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 689, + "mcp_result_bytes": 770, + "wire_bytes": 807, + "reported_used_tokens": 770, + "working_set_bytes": 938131456, + "peak_working_set_bytes": 939036672 + }, + { + "query": "speed up bulk memory ingest by caching SQL statements", + "ranked": [ + "sqlite-prepared-stmt-cache" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05XPZBE41Z9MH4SDG3ERZ", + "id": "01M1X0AXNEAXCM1C5TRJK2F85C", + "kind": "memory", + "score": 0.6780275106430054, + "summary": "project:fact - [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1580.5082000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 688, + "mcp_result_bytes": 769, + "wire_bytes": 806, + "reported_used_tokens": 769, + "working_set_bytes": 938135552, + "peak_working_set_bytes": 939040768 + }, + { + "query": "partial index on deleted_at IS NULL for faster active memory queries", + "ranked": [ + "sqlite-partial-index", + "sqlite-json1-extract" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05XR2Z5HGPZE32JWPSHV1", + "id": "01M1X0AZ6TFE1M26VPEE8XX2AB", + "kind": "memory", + "score": 0.999954104423523, + "summary": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query \u2014 the planner uses the partial index only when the WHERE clause matches." + }, + { + "expansion_handle": "memory:01M1X05XNQQSPZS882QVYZSZ3Z", + "id": "01M1X0AZ6VNS58Z8B60F9PYHSK", + "kind": "memory", + "score": 0.5821903347969055, + "summary": "project:fact - [tags: sqlite json1 json_extract rusqlite] SQLite's json1 extension (built in since 3.38.0) lets you index and query JSONB columns with `json_extract(col, '$.field')`. To create a partial index over a JSON field: `CREATE INDEX idx ON memories (json_extract(metadata, '$.scope')) WHERE json_extract(metadata, '$.scope') IS NOT NULL;`. Use `json_each` for array fields." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1270.895, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1323, + "mcp_result_bytes": 1422, + "wire_bytes": 1459, + "reported_used_tokens": 1422, + "working_set_bytes": 938258432, + "peak_working_set_bytes": 939159552 + }, + { + "query": "the brain query is slow because it scans all rows including soft-deleted ones", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1298.1315, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 938389504, + "peak_working_set_bytes": 939307008 + }, + { + "query": "Cargo.lock changed unexpectedly after adding a new workspace crate", + "ranked": [ + "cargo-lockfile-drift", + "cargo-feature-unification-embeddings" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05XS87CVAXTDXJWSMWWVQ", + "id": "01M1X0B1Q9FNVSSMKJDNJ80PY6", + "kind": "memory", + "score": 0.9998825788497924, + "summary": "project:fact - [2026-09-07] [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this \u2014 it errors on any lockfile diff." + }, + { + "expansion_handle": "memory:01M1X05WQEADDZVS78GM78YF9F", + "id": "01M1X0B1Q993P5CTHT8QJR3JC2", + "kind": "memory", + "score": 0.9923800230026244, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1320.4715, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1698, + "mcp_result_bytes": 1801, + "wire_bytes": 1838, + "reported_used_tokens": 1801, + "working_set_bytes": 938401792, + "peak_working_set_bytes": 939319296 + }, + { + "query": "how do I prevent CI from accepting a modified lockfile silently?", + "ranked": [ + "cargo-lockfile-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05XS87CVAXTDXJWSMWWVQ", + "id": "01M1X0B30K1MT55SBK4GV33J8M", + "kind": "memory", + "score": 0.770147979259491, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this \u2014 it errors on any lockfile diff." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1291.4171999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 764, + "mcp_result_bytes": 845, + "wire_bytes": 882, + "reported_used_tokens": 845, + "working_set_bytes": 938401792, + "peak_working_set_bytes": 939319296 + }, + { + "query": "build.rs reruns on every incremental build even when nothing changed", + "ranked": [ + "cargo-build-script-rerun" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05XTCRMR5F7Y578F36EYT", + "id": "01M1X0B49G5PQR5EVGMQ591V5Q", + "kind": "memory", + "score": 0.9999223947525024, + "summary": "project:fact - [2026-09-07] [tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1598.3162, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 698, + "mcp_result_bytes": 779, + "wire_bytes": 816, + "reported_used_tokens": 779, + "working_set_bytes": 938405888, + "peak_working_set_bytes": 939323392 + }, + { + "query": "incremental cargo build is slow because build script runs every time", + "ranked": [ + "cargo-build-script-rerun" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05XTCRMR5F7Y578F36EYT", + "id": "01M1X0B5TKYTQR5ZYAJ2VEYMY5", + "kind": "memory", + "score": 0.9998290538787842, + "summary": "project:fact - [tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1402.0374, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 685, + "mcp_result_bytes": 766, + "wire_bytes": 803, + "reported_used_tokens": 766, + "working_set_bytes": 938405888, + "peak_working_set_bytes": 939323392 + }, + { + "query": "a dev-dependency is activating an embeddings feature in my production build", + "ranked": [ + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05XVN17RZ1ZWVR1CH6N4A", + "id": "01M1X0B76KRWVGGXCNN50HPKDJ", + "kind": "memory", + "score": 0.999002993106842, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1443.1612999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 930, + "mcp_result_bytes": 1011, + "wire_bytes": 1048, + "reported_used_tokens": 1011, + "working_set_bytes": 938405888, + "peak_working_set_bytes": 939323392 + }, + { + "query": "how do I prevent a test-only feature from bleeding into the non-test compilation?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1463.3293, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 938409984, + "peak_working_set_bytes": 939323392 + }, + { + "query": "linker errors in target/ caused by antivirus holding the exe file", + "ranked": [ + "windows-file-locking-av" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05YPYA3XHS75N4WF9MHX9", + "id": "01M1X0BA1JYQQXQTHBAQCVAZTA", + "kind": "memory", + "score": 0.9991186261177064, + "summary": "project:fact - [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1264.0836, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 756, + "mcp_result_bytes": 837, + "wire_bytes": 874, + "reported_used_tokens": 837, + "working_set_bytes": 938409984, + "peak_working_set_bytes": 939323392 + }, + { + "query": "Access is denied (os error 5) when linking on Windows \u2014 how do I fix this?", + "ranked": [ + "windows-file-locking-av" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05YPYA3XHS75N4WF9MHX9", + "id": "01M1X0BB98XV3SSAVTBQA5SJMX", + "kind": "memory", + "score": 0.9984531402587892, + "summary": "project:fact - [2026-09-07] [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1302.9280999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 769, + "mcp_result_bytes": 850, + "wire_bytes": 887, + "reported_used_tokens": 850, + "working_set_bytes": 938414080, + "peak_working_set_bytes": 939323392 + }, + { + "query": "incremental build broke with a type mismatch after switching branches", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05XY122KP5Z06P65A8Z5N", + "id": "01M1X0BCHPSZ6R1WY1P6DTWG24", + "kind": "memory", + "score": 0.7982672452926636, + "summary": "project:fact - [2026-09-07] [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1304.6504, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 890, + "mcp_result_bytes": 971, + "wire_bytes": 1008, + "reported_used_tokens": 971, + "working_set_bytes": 938414080, + "peak_working_set_bytes": 939327488 + }, + { + "query": "cargo reports a type error that references a type not in the codebase", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05XY122KP5Z06P65A8Z5N", + "id": "01M1X0BDTJ7F9BATWAQ1BHRGEN", + "kind": "memory", + "score": 0.7982914447784424, + "summary": "project:fact - [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1494.9393, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 877, + "mcp_result_bytes": 958, + "wire_bytes": 995, + "reported_used_tokens": 958, + "working_set_bytes": 938422272, + "peak_working_set_bytes": 939343872 + }, + { + "query": "compile fastembed at O2 in debug builds to avoid slow embedding inference", + "ranked": [ + "cargo-profile-override", + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05XZ1Y4BCQ76CJV97H2GQ", + "id": "01M1X0BF9YMP00VAY3WRJ89EK2", + "kind": "memory", + "score": 0.9999233484268188, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1X060DYCSGDHSEP6ZVV7E31", + "id": "01M1X0BF9YHDM2NC28Q4MCGT1Q", + "kind": "memory", + "score": 0.7583951950073242, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1480.4471, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1323, + "mcp_result_bytes": 1422, + "wire_bytes": 1459, + "reported_used_tokens": 1422, + "working_set_bytes": 938422272, + "peak_working_set_bytes": 939343872 + }, + { + "query": "override compilation profile for a single crate in a Cargo workspace", + "ranked": [ + "cargo-profile-override", + "cargo-patch-section", + "cargo-target-dir-sharing", + "cargo-lockfile-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05XZ1Y4BCQ76CJV97H2GQ", + "id": "01M1X0BGR03A96Y3K4Y8WH83X1", + "kind": "memory", + "score": 0.9999361038208008, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1X05Y067GYTZF5KQA0TBS6K", + "id": "01M1X0BGR0V68PD9NNVE4WH04M", + "kind": "memory", + "score": 0.9970531463623048, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace \u2014 including transitive deps \u2014 that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1X05XWX3W7XACE3KHEF1JJ6", + "id": "01M1X0BGR096TAR3FGJDE5H0BK", + "kind": "memory", + "score": 0.9418804049491882, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps \u2014 use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + }, + { + "expansion_handle": "memory:01M1X05XS87CVAXTDXJWSMWWVQ", + "id": "01M1X0BGR1WW6DC5119339R4ZG", + "kind": "memory", + "score": 0.6213672161102295, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this \u2014 it errors on any lockfile diff." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1487.7278999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2516, + "mcp_result_bytes": 2655, + "wire_bytes": 2692, + "reported_used_tokens": 2655, + "working_set_bytes": 938418176, + "peak_working_set_bytes": 939343872 + }, + { + "query": "[patch.crates-io] workspace dependency override", + "ranked": [ + "cargo-patch-section", + "cargo-dev-dep-leak", + "cargo-profile-override" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05Y067GYTZF5KQA0TBS6K", + "id": "01M1X0BJ6M1PB566JC4693AXWA", + "kind": "memory", + "score": 0.9999796152114868, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace \u2014 including transitive deps \u2014 that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1X05XVN17RZ1ZWVR1CH6N4A", + "id": "01M1X0BJ6MF5D30T4BCDW166GD", + "kind": "memory", + "score": 0.7515549063682556, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + }, + { + "expansion_handle": "memory:01M1X05XZ1Y4BCQ76CJV97H2GQ", + "id": "01M1X0BJ6N4KAXV6G6MKSKTS8W", + "kind": "memory", + "score": 0.6568455696105957, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1239.7351999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1917, + "mcp_result_bytes": 2038, + "wire_bytes": 2075, + "reported_used_tokens": 2038, + "working_set_bytes": 938418176, + "peak_working_set_bytes": 939343872 + }, + { + "query": "pin minimum supported Rust version in Cargo.toml", + "ranked": [ + "cargo-msrv" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05Y1AT99W716C7537ZD3Y", + "id": "01M1X0BKCYC82MC8BA02FX6FSN", + "kind": "memory", + "score": 0.9998986721038818, + "summary": "project:fact - [tags: cargo rust msrv edition compatibility] Set `rust-version` in each `Cargo.toml` to declare the minimum supported Rust version (MSRV). Cargo enforces this with `--check`: `cargo check` fails if the toolchain is older than `rust-version`. Keep MSRV as old as your oldest supported deployment target." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1219.49, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 693, + "mcp_result_bytes": 774, + "wire_bytes": 811, + "reported_used_tokens": 774, + "working_set_bytes": 938418176, + "peak_working_set_bytes": 939343872 + }, + { + "query": "Windows path over 260 characters causes OS error 3 during Cargo build", + "ranked": [ + "windows-long-paths", + "windows-file-locking-av" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05YNSFW3GZ52SA3YABAQT", + "id": "01M1X0BMK47B66VPT4MM0FE8YW", + "kind": "memory", + "score": 0.9998657703399658, + "summary": "project:fact - [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe." + }, + { + "expansion_handle": "memory:01M1X05YPYA3XHS75N4WF9MHX9", + "id": "01M1X0BMK43C82T3TDXQP88B4S", + "kind": "memory", + "score": 0.6231384873390198, + "summary": "project:fact - [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1158.1257, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1297, + "mcp_result_bytes": 1406, + "wire_bytes": 1443, + "reported_used_tokens": 1406, + "working_set_bytes": 938422272, + "peak_working_set_bytes": 939343872 + }, + { + "query": "how do I enable long file paths for Cargo on Windows?", + "ranked": [ + "windows-long-paths", + "windows-registry-rust" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05YNSFW3GZ52SA3YABAQT", + "id": "01M1X0BNQ4BC51ZNP6K9YCV48C", + "kind": "memory", + "score": 0.9999781847000122, + "summary": "project:fact - [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe." + }, + { + "expansion_handle": "memory:01M1X05YX39BT7SXR3ZKK61CXC", + "id": "01M1X0BNQ4Z8R2V2MV6A3TMMTP", + "kind": "memory", + "score": 0.7387577891349792, + "summary": "project:fact - [tags: windows registry rust winreg read write] Reading and writing the Windows registry from Rust requires the `winreg` crate. Open a key with `RegKey::predef(HKEY_LOCAL_MACHINE).open_subkey_with_flags(path, KEY_READ)` \u2014 use `KEY_READ` for reads and `KEY_READ | KEY_WRITE` for writes (NOT `KEY_ALL_ACCESS`, which requires admin). To set a DWORD value: `key.set_value(\"LongPathsEnabled\", &1u32)`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1652.5949, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1332, + "mcp_result_bytes": 1445, + "wire_bytes": 1482, + "reported_used_tokens": 1445, + "working_set_bytes": 938422272, + "peak_working_set_bytes": 939343872 + }, + { + "query": "intermittent sharing violation errors when Rust linker writes the exe on Windows", + "ranked": [ + "windows-file-locking-av" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05YPYA3XHS75N4WF9MHX9", + "id": "01M1X0BQBCJ1AJ18YVEDA26G37", + "kind": "memory", + "score": 0.9999388456344604, + "summary": "project:fact - [2026-09-07] [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1253.9413, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 769, + "mcp_result_bytes": 850, + "wire_bytes": 887, + "reported_used_tokens": 850, + "working_set_bytes": 938426368, + "peak_working_set_bytes": 939343872 + }, + { + "query": "Rust walkdir follows junctions differently from symlinks on Windows", + "ranked": [ + "windows-junctions-vs-symlinks" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05YTKKRA619J79YJ19R8Y", + "id": "01M1X0BRJC631YT4WMWWT7CP89", + "kind": "memory", + "score": 0.9999210834503174, + "summary": "project:fact - [tags: windows junctions symlinks rust std::fs] On Windows, directory junctions (NTFS reparse points) behave like symlinks for directory traversal but `std::fs::symlink_metadata` returns `FileType::is_symlink() = false` for junctions (only true for regular symlinks). Use `std::fs::read_link` \u2014 it succeeds for both junction and symlink. `walkdir` crate's `follow_links` follows both, but its `is_symlink()` method correctly reports only actual symlinks." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1272.1425, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 845, + "mcp_result_bytes": 926, + "wire_bytes": 963, + "reported_used_tokens": 926, + "working_set_bytes": 938426368, + "peak_working_set_bytes": 939343872 + }, + { + "query": "UNC path canonicalize returns verbatim prefix \u2014 how do I strip it?", + "ranked": [ + "windows-unc-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05YQZC03JE18CFB7QXZ3C", + "id": "01M1X0BST1PZS916Z4PJV2MAAS", + "kind": "memory", + "score": 0.999847412109375, + "summary": "project:fact - [tags: windows unc-paths rust std::fs] Windows UNC paths (`\\\\server\\share\\...`) are not supported by most Rust `std::fs` operations unless passed through the extended-length prefix `\\\\?\\UNC\\server\\share\\...`. `std::path::Path::new(\"\\\\\\\\server\\\\share\")` works for basic operations but breaks with `canonicalize()` which returns the verbatim prefix form. When walking directory trees that may start on UNC paths, use the `dunce` crate to strip the verbatim prefix before comparing or displaying paths." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1665.1674, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 907, + "mcp_result_bytes": 1024, + "wire_bytes": 1061, + "reported_used_tokens": 1024, + "working_set_bytes": 938426368, + "peak_working_set_bytes": 939352064 + }, + { + "query": "UTF-8 memory text prints as mojibake in the Windows console", + "ranked": [ + "windows-console-encoding" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05YS9AVW6MDFWV2W019V2", + "id": "01M1X0BVE9C72MT6SB74T617HK", + "kind": "memory", + "score": 0.9999754428863524, + "summary": "project:fact - [tags: windows console encoding utf8 rust] Windows console code page defaults to the system ANSI code page (usually CP1252 or CP932), not UTF-8. Rust's `println!` writes UTF-8 bytes which display as mojibake in a non-UTF-8 console. Fix at process startup: call `SetConsoleOutputCP(65001)` via `winapi` or `windows-sys`, or set `PYTHONUTF8=1`/`RUST_LOG` before launch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1267.0568, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 757, + "mcp_result_bytes": 838, + "wire_bytes": 875, + "reported_used_tokens": 838, + "working_set_bytes": 938430464, + "peak_working_set_bytes": 939352064 + }, + { + "query": "process exit code is 4294967295 instead of -1 on Windows", + "ranked": [ + "windows-exit-codes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05YVW3RBBRW3KBAEDSWMA", + "id": "01M1X0BWPQQG0AR4GGAGEY9DKZ", + "kind": "memory", + "score": 0.9999797344207764, + "summary": "project:fact - [tags: windows exit-codes rust process child] On Windows, process exit codes are 32-bit unsigned integers (DWORD). Rust's `ExitStatus::code()` returns `Option` \u2014 it's `None` if the process was killed by a signal (which Windows doesn't use; instead, TerminateProcess with a code). Conventional codes: 0=success, 1=generic error, 0xC0000005=access violation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1456.4154, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 753, + "mcp_result_bytes": 834, + "wire_bytes": 871, + "reported_used_tokens": 834, + "working_set_bytes": 938430464, + "peak_working_set_bytes": 939352064 + }, + { + "query": "tokenizer.json must match the ONNX model \u2014 what breaks if it doesn't?", + "ranked": [ + "onnx-tokenizer-mismatch" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05YYF8VZC3V8BGC0DGCBT", + "id": "01M1X0BY37TW6W23704YXPTDMQ", + "kind": "memory", + "score": 0.9999275207519532, + "summary": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly \u2014 specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings \u2014 cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1321.2809000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 959, + "mcp_result_bytes": 1040, + "wire_bytes": 1077, + "reported_used_tokens": 1040, + "working_set_bytes": 938430464, + "peak_working_set_bytes": 939352064 + }, + { + "query": "embedding quality degraded after I swapped in the INT8 quantized model", + "ranked": [ + "onnx-quantization-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05YZKN4Y3GJBMHHAJ3NK7", + "id": "01M1X0BZCG1TY1NJS2TPRJJB9G", + "kind": "memory", + "score": 0.9944571256637572, + "summary": "project:fact - [2026-09-07] [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals \u2014 cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1294.6952999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 991, + "mcp_result_bytes": 1072, + "wire_bytes": 1109, + "reported_used_tokens": 1072, + "working_set_bytes": 938430464, + "peak_working_set_bytes": 939352064 + }, + { + "query": "missing attention mask causes low-norm embeddings in batch inference", + "ranked": [ + "onnx-batch-padding" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05Z0QDTFTJJW5R532RPWH", + "id": "01M1X0C0N1JJ7HMKRECQ7TRMPA", + "kind": "memory", + "score": 0.9998724460601808, + "summary": "project:fact - [tags: onnx batch padding attention-mask embeddings] When running batch inference with an ONNX model, all inputs in the batch must be padded to the same sequence length. The `attention_mask` tensor marks which tokens are real (1) and which are padding (0). Failing to pass `attention_mask` causes the model to average-pool over padding tokens, producing systematically lower-norm embeddings." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1270.7474, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 781, + "mcp_result_bytes": 862, + "wire_bytes": 899, + "reported_used_tokens": 862, + "working_set_bytes": 938430464, + "peak_working_set_bytes": 939352064 + }, + { + "query": "ONNX model download fails in a Docker container with no home directory", + "ranked": [ + "onnx-model-cache-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05Z21MR5GB9R6B7AZ4M5R", + "id": "01M1X0C1WRC33Y34SQY9M8GHMM", + "kind": "memory", + "score": 0.9924855828285216, + "summary": "project:fact - [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1318.5726, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 755, + "mcp_result_bytes": 838, + "wire_bytes": 875, + "reported_used_tokens": 838, + "working_set_bytes": 938430464, + "peak_working_set_bytes": 939352064 + }, + { + "query": "fastembed cache path environment variable for CI", + "ranked": [ + "onnx-model-cache-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05Z21MR5GB9R6B7AZ4M5R", + "id": "01M1X0C35X2A5W29HDHC1J0YZZ", + "kind": "memory", + "score": 0.9999436140060424, + "summary": "project:fact - [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1263.1211, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 756, + "mcp_result_bytes": 839, + "wire_bytes": 876, + "reported_used_tokens": 839, + "working_set_bytes": 938430464, + "peak_working_set_bytes": 939352064 + }, + { + "query": "cosine similarity vs dot product for L2-normalized embedding vectors", + "ranked": [ + "onnx-cosine-vs-dot" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05Z35M93KRKTA23QWYT13", + "id": "01M1X0C4D9D0QKFA94SVENZ5R7", + "kind": "memory", + "score": 0.9999769926071168, + "summary": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing \u2014 double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1223.4191, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 765, + "mcp_result_bytes": 846, + "wire_bytes": 883, + "reported_used_tokens": 846, + "working_set_bytes": 938430464, + "peak_working_set_bytes": 939352064 + }, + { + "query": "stored vectors have wrong dimension after switching embedding models", + "ranked": [ + "onnx-dim-mismatch", + "onnx-cosine-vs-dot" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05Z4DT1C1RCDZHTSN5X5N", + "id": "01M1X0C5KHJY24VEKNENE26NXV", + "kind": "memory", + "score": 0.9999632835388184, + "summary": "project:fact - [2026-09-07] [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results \u2014 the ANN index shape mismatch isn't always caught at runtime." + }, + { + "expansion_handle": "memory:01M1X05Z35M93KRKTA23QWYT13", + "id": "01M1X0C5KHJW4J8CY61PT2SX40", + "kind": "memory", + "score": 0.8715931177139282, + "summary": "project:fact - [2026-09-07] [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing \u2014 double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1143.1637, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1305, + "mcp_result_bytes": 1404, + "wire_bytes": 1441, + "reported_used_tokens": 1404, + "working_set_bytes": 938430464, + "peak_working_set_bytes": 939352064 + }, + { + "query": "E5 and Instructor models need a query prefix \u2014 what happens without it?", + "ranked": [ + "onnx-prefix-instructions", + "onnx-cosine-vs-dot" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05Z7VTT311QYS4JK8XZTA", + "id": "01M1X0C6QN34MT25GB0AJZYB5A", + "kind": "memory", + "score": 0.9999685287475586, + "summary": "project:fact - [tags: onnx embeddings prefix instruction e5 query passage] E5 and Instructor family models require a text prefix on BOTH query and passage sides to produce meaningful similarities: query prefix `\"query: \"`, passage prefix `\"passage: \"`. Omitting the prefix can drop MRR by 10-15 percentage points on out-of-domain datasets. Check the model's README for the exact prefix string \u2014 it varies by model family." + }, + { + "expansion_handle": "memory:01M1X05Z35M93KRKTA23QWYT13", + "id": "01M1X0C6QNR7ETQPKEWCHY348B", + "kind": "memory", + "score": 0.5567834973335266, + "summary": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing \u2014 double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1591.4233, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1339, + "mcp_result_bytes": 1446, + "wire_bytes": 1483, + "reported_used_tokens": 1446, + "working_set_bytes": 938430464, + "peak_working_set_bytes": 939352064 + }, + { + "query": "ORT thread pool contention when running multiple bench processes in parallel", + "ranked": [ + "onnx-ort-threading" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05Z918YHAY118R8F113T6", + "id": "01M1X0C89GPPH0JPE8MJV3KBWX", + "kind": "memory", + "score": 0.9999712705612184, + "summary": "project:fact - [2026-09-07] [tags: onnx ort thread-pool parallelism cpu] ORT (ONNX Runtime) creates its own inter-op and intra-op thread pools. In a multi-process bench setup, each child inherits these pools and they compete for CPU cores. Set `SessionOptionsBuilder::with_intra_threads(1).with_inter_threads(1)` if you're running many parallel bench processes \u2014 this sacrifices per-inference throughput for lower contention." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1311.4336, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 802, + "mcp_result_bytes": 883, + "wire_bytes": 920, + "reported_used_tokens": 883, + "working_set_bytes": 938430464, + "peak_working_set_bytes": 939352064 + }, + { + "query": "git worktrees share the .kimetsu brain \u2014 how do I isolate test runs?", + "ranked": [ + "git-worktree-brain-isolation", + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05ZA9QQ47DPP3Q0PVXQSH", + "id": "01M1X0C9KY2QNH20YJR3G4BPTJ", + "kind": "memory", + "score": 0.9999784231185912, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root \u2014 if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + }, + { + "expansion_handle": "memory:01M1X05X2XE8B8NBDATM3RYT6N", + "id": "01M1X0C9KYTMPQNMNQ8HNBQ08E", + "kind": "memory", + "score": 0.9857950210571288, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1345.9252999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1435, + "mcp_result_bytes": 1534, + "wire_bytes": 1571, + "reported_used_tokens": 1534, + "working_set_bytes": 938430464, + "peak_working_set_bytes": 939352064 + }, + { + "query": "when is it safe to use --no-verify on git commit?", + "ranked": [ + "git-hooks-bypass" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05ZBEJR3DEG99VGQJ85JM", + "id": "01M1X0CAW3WG6PGG29T1NAKGEP", + "kind": "memory", + "score": 0.99863463640213, + "summary": "project:fact - [2026-09-07] [tags: git hooks bypass pre-commit skip] `git commit --no-verify` skips ALL hooks (pre-commit and commit-msg). Never use this in shared team repos where hooks enforce quality gates (lint, tests, memory harvest). Instead, fix the failing hook." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1300.6039999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 645, + "mcp_result_bytes": 726, + "wire_bytes": 763, + "reported_used_tokens": 726, + "working_set_bytes": 938434560, + "peak_working_set_bytes": 939352064 + }, + { + "query": "reduce clone size and bandwidth for server-side repo ingest", + "ranked": [ + "git-sparse-checkout", + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05ZCMYMCY0306J19N23J2", + "id": "01M1X0CC51RD6ZB4YW82R15R7E", + "kind": "memory", + "score": 0.9952669143676758, + "summary": "project:fact - [tags: git sparse-checkout partial-clone bandwidth] `git sparse-checkout init --cone` combined with `git clone --filter=blob:none` (partial clone) fetches only the commit graph and tree objects, not blobs. Individual blobs are fetched on demand when accessed. This cuts clone time for large repos from minutes to seconds." + }, + { + "expansion_handle": "memory:01M1X05WJQ71HE168C1JX89W7A", + "id": "01M1X0CC5171PPE4JZG4DF0VFN", + "kind": "memory", + "score": 0.5591859817504883, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1700.4128, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1744, + "mcp_result_bytes": 1843, + "wire_bytes": 1880, + "reported_used_tokens": 1843, + "working_set_bytes": 938553344, + "peak_working_set_bytes": 939462656 + }, + { + "query": "spurious diffs from Windows CRLF line ending conversion in git", + "ranked": [ + "git-line-endings-windows" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05ZDZWSFR7QEB992XYSWN", + "id": "01M1X0CDT8R8MDRVJT9SFGNF0T", + "kind": "memory", + "score": 0.9999781847000122, + "summary": "project:fact - [tags: git line-endings windows crlf autocrlf] On Windows, `core.autocrlf=true` (git's default for Windows installs) converts LF to CRLF on checkout and CRLF to LF on commit. This causes spurious diffs when files are edited on Windows then committed \u2014 the content is identical but the line endings differ in the index vs the working tree. Fix: set `core.autocrlf=false` and `.gitattributes` with `* text=auto eol=lf` for the repo." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1615.5945000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 940, + "reported_used_tokens": 903, + "working_set_bytes": 938553344, + "peak_working_set_bytes": 939462656 + }, + { + "query": "git submodule always gets the wrong commit in CI", + "ranked": [ + "git-submodule-pinning", + "git-hooks-bypass" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05ZF8KVJJ7AM0A778QQJK", + "id": "01M1X0CFCJS72K1ZJE3FT4W1BY", + "kind": "memory", + "score": 0.9990686774253844, + "summary": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip \u2014 this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version." + }, + { + "expansion_handle": "memory:01M1X05ZBEJR3DEG99VGQJ85JM", + "id": "01M1X0CFCJ8VRSF22QE8JWZY0V", + "kind": "memory", + "score": 0.7485930919647217, + "summary": "project:fact - [tags: git hooks bypass pre-commit skip] `git commit --no-verify` skips ALL hooks (pre-commit and commit-msg). Never use this in shared team repos where hooks enforce quality gates (lint, tests, memory harvest). Instead, fix the failing hook." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1275.5311, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1157, + "mcp_result_bytes": 1256, + "wire_bytes": 1293, + "reported_used_tokens": 1256, + "working_set_bytes": 938553344, + "peak_working_set_bytes": 939466752 + }, + { + "query": "accidentally ran git reset --hard and lost commits \u2014 can I recover?", + "ranked": [ + "git-reflog-rescue" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05ZGE299FAXV9GB34BGK7", + "id": "01M1X0CGMAFBHQFTBVEH5J923E", + "kind": "memory", + "score": 0.9999775886535645, + "summary": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone \u2014 they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only \u2014 remote reflog is not accessible via normal git commands." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1665.5560999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 762, + "mcp_result_bytes": 843, + "wire_bytes": 880, + "reported_used_tokens": 843, + "working_set_bytes": 938557440, + "peak_working_set_bytes": 939466752 + }, + { + "query": "blocking SQLite call from an async tokio handler causes latency spikes", + "ranked": [ + "tokio-blocking-in-async", + "tokio-runtime-in-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05ZHKS6ZS872XPKZ5N763", + "id": "01M1X0CJ8EQ821BYXNY302CTHY", + "kind": "memory", + "score": 0.9999537467956544, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + }, + { + "expansion_handle": "memory:01M1X05ZJXG8K42RHMEJTR53F4", + "id": "01M1X0CJ8EHHTERDVWWEMAG106", + "kind": "memory", + "score": 0.7837615609169006, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1293.1843000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1370, + "mcp_result_bytes": 1477, + "wire_bytes": 1514, + "reported_used_tokens": 1477, + "working_set_bytes": 938680320, + "peak_working_set_bytes": 939589632 + }, + { + "query": "Cannot start a runtime from within a runtime in a tokio test", + "ranked": [ + "tokio-runtime-in-tests", + "tokio-blocking-in-async" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05ZJXG8K42RHMEJTR53F4", + "id": "01M1X0CKHY42GN7CNA36HRTZ54", + "kind": "memory", + "score": 0.9999808073043824, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + }, + { + "expansion_handle": "memory:01M1X05ZHKS6ZS872XPKZ5N763", + "id": "01M1X0CKHYQ54PWR7Y0FFW5B4N", + "kind": "memory", + "score": 0.7143720388412476, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1561.8876, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1370, + "mcp_result_bytes": 1477, + "wire_bytes": 1514, + "reported_used_tokens": 1477, + "working_set_bytes": 938684416, + "peak_working_set_bytes": 939597824 + }, + { + "query": "tokio select cancels the other branch and loses the value in the channel", + "ranked": [ + "tokio-select-cancellation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05ZME5V7WFJ9P33K58JEV", + "id": "01M1X0CN1TPN87PAYJCNFS7G8E", + "kind": "memory", + "score": 0.9996563196182252, + "summary": "project:fact - [tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1288.2271, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 751, + "mcp_result_bytes": 832, + "wire_bytes": 869, + "reported_used_tokens": 832, + "working_set_bytes": 938688512, + "peak_working_set_bytes": 939606016 + }, + { + "query": "mpsc channel backpressure causing senders to stall", + "ranked": [ + "tokio-channel-backpressure" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05ZNQPZSKQ5XMSXT2C5TW", + "id": "01M1X0CPB7CMJEEF2E0QZB2M3R", + "kind": "memory", + "score": 0.999940037727356, + "summary": "project:fact - [tags: tokio mpsc channel backpressure async rust] `tokio::sync::mpsc::channel(N)` with a bounded buffer provides backpressure: senders block when the buffer is full. This prevents unbounded memory growth but can cause sender tasks to stall. Choosing N: too small causes frequent backpressure (throughput drops); too large defeats the purpose." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1375.5823999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 732, + "mcp_result_bytes": 813, + "wire_bytes": 850, + "reported_used_tokens": 813, + "working_set_bytes": 938942464, + "peak_working_set_bytes": 939847680 + }, + { + "query": "overhead from calling spawn_blocking on every single query request", + "ranked": [ + "tokio-spawn-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05ZQ2WBH9P65YQCV6XM2J", + "id": "01M1X0CQN4436HHAKQ1AHM9EZ2", + "kind": "memory", + "score": 0.9911772012710572, + "summary": "project:fact - [tags: tokio spawn_blocking thread-pool rust blocking] `tokio::task::spawn_blocking` places work on a dedicated blocking thread pool (default up to 512 threads, configurable via `Builder::max_blocking_threads`). Each call creates or reuses a thread \u2014 there's no true pooling, threads may be created on demand. For many short-duration blocking calls (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1270.5303, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 746, + "mcp_result_bytes": 827, + "wire_bytes": 864, + "reported_used_tokens": 827, + "working_set_bytes": 939069440, + "peak_working_set_bytes": 939978752 + }, + { + "query": "axum server panics during shutdown because the DB pool is already closed", + "ranked": [ + "tokio-shutdown-ordering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05ZSQVC0YB5CZZ6E6NXZY", + "id": "01M1X0CRWWQ0KVJAH2MSBB2HCK", + "kind": "memory", + "score": 0.9920267462730408, + "summary": "project:fact - [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries \u2014 the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1775.6866, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 933, + "mcp_result_bytes": 1014, + "wire_bytes": 1051, + "reported_used_tokens": 1014, + "working_set_bytes": 939077632, + "peak_working_set_bytes": 939995136 + }, + { + "query": "reqwest Client created per-request defeats connection pooling", + "ranked": [ + "http-connection-pooling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05ZV1JYS6V5451CXYDEGY", + "id": "01M1X0CTMAPK1ZN377YPHYBNZ8", + "kind": "memory", + "score": 0.9999794960021972, + "summary": "project:fact - [tags: http reqwest connection-pool keep-alive rust] reqwest's `Client` holds a connection pool; always create ONE `Client` instance and clone it for each handler \u2014 cloning is cheap (Arc under the hood). Creating a `Client::new()` per request defeats connection pooling and causes TCP connection exhaustion under load. The default pool settings: max_idle_per_host=usize::MAX (unbounded), idle_timeout=90s." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1221.1997000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 797, + "mcp_result_bytes": 878, + "wire_bytes": 915, + "reported_used_tokens": 878, + "working_set_bytes": 939212800, + "peak_working_set_bytes": 940105728 + }, + { + "query": "LLM request times out during streaming \u2014 which timeout setting applies?", + "ranked": [ + "http-timeout-layering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05ZWCK04NK2NMYQ6DAP5E", + "id": "01M1X0CVTHAZFWR10GQM6ZD0NV", + "kind": "memory", + "score": 0.999026656150818, + "summary": "project:fact - [tags: http reqwest timeout connect read total rust] reqwest has three distinct timeout knobs: `connect_timeout`, `read_timeout`, and `timeout` (total). They compose: if all three are set, the request fails at whichever fires first. For LLM API calls with streaming responses, `read_timeout` must be larger than the slowest expected token (often 30-60s) while `connect_timeout` can be tight (3-5s)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1420.6311, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 788, + "mcp_result_bytes": 869, + "wire_bytes": 906, + "reported_used_tokens": 869, + "working_set_bytes": 939405312, + "peak_working_set_bytes": 940318720 + }, + { + "query": "how do I safely retry a POST to the LLM API without creating duplicates?", + "ranked": [ + "http-retry-idempotency" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05ZXP17S0RBF6501KBY5E", + "id": "01M1X0CX79YHEJ45K65ZX9DGTN", + "kind": "memory", + "score": 0.9997218251228333, + "summary": "project:fact - [tags: http retry idempotency post put reqwest] Only retry idempotent requests automatically. GET, HEAD, PUT, DELETE are idempotent. POST is NOT \u2014 retrying a POST may create duplicate resources." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1596.852, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 585, + "mcp_result_bytes": 666, + "wire_bytes": 703, + "reported_used_tokens": 666, + "working_set_bytes": 939405312, + "peak_working_set_bytes": 940322816 + }, + { + "query": "custom enterprise root CA not trusted by rustls on Windows", + "ranked": [ + "http-tls-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05ZZ0XY647CTYY7W8C1EM", + "id": "01M1X0CYRVVMVJX33SV1CXXNGA", + "kind": "memory", + "score": 0.9999604225158693, + "summary": "project:fact - [tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle \u2014 the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1630.678, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 780, + "mcp_result_bytes": 861, + "wire_bytes": 898, + "reported_used_tokens": 861, + "working_set_bytes": 939421696, + "peak_working_set_bytes": 940335104 + }, + { + "query": "parsing server-sent events when a single TCP chunk contains a partial SSE frame", + "ranked": [ + "http-streaming-bodies" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X06008K641GZ48NFRRPA08", + "id": "01M1X0D0CH97N3EF4X5TT54K7J", + "kind": "memory", + "score": 0.9942779541015624, + "summary": "project:fact - [2026-09-07] [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding \u2014 a chunk may split across frame boundaries." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1285.9587000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 859, + "mcp_result_bytes": 940, + "wire_bytes": 977, + "reported_used_tokens": 940, + "working_set_bytes": 939425792, + "peak_working_set_bytes": 940339200 + }, + { + "query": "reqwest does not use the system proxy settings on Windows", + "ranked": [ + "http-proxy-env", + "http-tls-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0601JFQYHM4NXFXHAJ925", + "id": "01M1X0D1M4FEFF1BS1CB2GJG2Y", + "kind": "memory", + "score": 0.9999799728393556, + "summary": "project:fact - [tags: http proxy environment reqwest rust corporate] reqwest respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` environment variables by default (with `default-tls` or `rustls-tls`). In a corporate network, these may redirect traffic through an intercepting proxy that breaks mTLS or adds latency. To disable proxy usage entirely: `reqwest::ClientBuilder::no_proxy()`." + }, + { + "expansion_handle": "memory:01M1X05ZZ0XY647CTYY7W8C1EM", + "id": "01M1X0D1M4SG6TADZVFSZ3V6VR", + "kind": "memory", + "score": 0.9782498478889464, + "summary": "project:fact - [tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle \u2014 the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1301.1726, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1310, + "mcp_result_bytes": 1409, + "wire_bytes": 1446, + "reported_used_tokens": 1409, + "working_set_bytes": 939429888, + "peak_working_set_bytes": 940339200 + }, + { + "query": "insta snapshot tests fail in CI because output includes a timestamp", + "ranked": [ + "testing-snapshot-churn" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0602T5NKKD7VE83AHYPWN", + "id": "01M1X0D2XAF5N9RD50GETFKPVH", + "kind": "memory", + "score": 0.9999759197235109, + "summary": "project:fact - [tags: testing snapshot insta assert churn rust] Snapshot tests (e.g. with the `insta` crate) fail whenever the output changes, even for intended changes. In CI, they fail loudly; locally, `cargo insta review` walks you through accepting or rejecting changes." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1370.8894, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 649, + "mcp_result_bytes": 730, + "wire_bytes": 767, + "reported_used_tokens": 730, + "working_set_bytes": 939433984, + "peak_working_set_bytes": 940347392 + }, + { + "query": "two test workers writing to the same temp directory path race each other", + "ranked": [ + "testing-temp-dirs-ci" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X06045GR186PNBCJEQVZFJ", + "id": "01M1X0D47R79DRH78THE44FVAC", + "kind": "memory", + "score": 0.9582907557487488, + "summary": "project:fact - [tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1383.6694, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 755, + "mcp_result_bytes": 836, + "wire_bytes": 873, + "reported_used_tokens": 836, + "working_set_bytes": 939433984, + "peak_working_set_bytes": 940347392 + }, + { + "query": "test passes locally but fails on a slow CI runner due to a 100ms sleep", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1488.4156, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 939433984, + "peak_working_set_bytes": 940355584 + }, + { + "query": "proptest found a hash collision in text normalization that example tests missed", + "ranked": [ + "testing-property-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0606SVWA37H209AQCZ89N", + "id": "01M1X0D71H4YMH7Z4XCTX3GAZE", + "kind": "memory", + "score": 0.9999779462814332, + "summary": "project:fact - [tags: testing property-based proptest quickcheck rust] Property-based tests (proptest, quickcheck) find edge cases that example-based tests miss. For kimetsu's memory text normalization, proptest found that zero-width joiner characters and right-to-left marks caused hash collisions. Run proptest with `PROPTEST_CASES=10000` in CI for thorough coverage." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1464.7314000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 744, + "mcp_result_bytes": 825, + "wire_bytes": 862, + "reported_used_tokens": 825, + "working_set_bytes": 939433984, + "peak_working_set_bytes": 940355584 + }, + { + "query": "set_var in tests races when cargo test runs them in parallel", + "ranked": [ + "testing-serial-vs-parallel" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0607YVVTMGSXZKPGQ8P2A", + "id": "01M1X0D8FDTAGRY1V9SV8NV17W", + "kind": "memory", + "score": 0.9995468258857728, + "summary": "project:fact - [2026-09-07] [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1284.2358, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 832, + "mcp_result_bytes": 913, + "wire_bytes": 950, + "reported_used_tokens": 913, + "working_set_bytes": 939433984, + "peak_working_set_bytes": 940355584 + }, + { + "query": "hardcoded JSON fixtures broke after a schema migration", + "ranked": [ + "testing-fixture-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X060BK72MAZMP4S1095BR8", + "id": "01M1X0D9QS6W4GQ9S34G6YR2TR", + "kind": "memory", + "score": 0.9999451637268066, + "summary": "project:fact - [2026-09-07] [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1263.4483, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 783, + "mcp_result_bytes": 864, + "wire_bytes": 901, + "reported_used_tokens": 864, + "working_set_bytes": 939433984, + "peak_working_set_bytes": 940355584 + }, + { + "query": "debug print in the MCP handler corrupts the JSON-Lines protocol stream", + "ranked": [ + "mcp-stdout-protocol" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X060CNDKDV0G74BPVB9WQW", + "id": "01M1X0DAYT5TPGFVJMC6C006JN", + "kind": "memory", + "score": 0.9999716281890868, + "summary": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1574.5985, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 705, + "mcp_result_bytes": 786, + "wire_bytes": 823, + "reported_used_tokens": 786, + "working_set_bytes": 939433984, + "peak_working_set_bytes": 940355584 + }, + { + "query": "kimetsu MCP tool call times out because embedding model is re-initialized every call", + "ranked": [ + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X060DYCSGDHSEP6ZVV7E31", + "id": "01M1X0DCG953Z8FRQ0WVP9XCKZ", + "kind": "memory", + "score": 0.9981862902641296, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1196.1408, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 858, + "mcp_result_bytes": 939, + "wire_bytes": 976, + "reported_used_tokens": 939, + "working_set_bytes": 939433984, + "peak_working_set_bytes": 940355584 + }, + { + "query": "env var set after host launch is not visible to the MCP server process", + "ranked": [ + "mcp-env-propagation", + "kimetsu-daemon-lifecycle" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X060F787CV8C15YJZ8CNQE", + "id": "01M1X0DDNHWQAGYZ5HQPYNNGW0", + "kind": "memory", + "score": 0.9996464252471924, + "summary": "project:fact - [2026-09-07] [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment \u2014 changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate." + }, + { + "expansion_handle": "memory:01M1X0613T09134FY71BP84VZG", + "id": "01M1X0DDNHM4Z7TWV5MK8D6XP3", + "kind": "memory", + "score": 0.8873274922370911, + "summary": "project:fact - [2026-09-07] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1651.0954, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1267, + "mcp_result_bytes": 1366, + "wire_bytes": 1403, + "reported_used_tokens": 1366, + "working_set_bytes": 939511808, + "peak_working_set_bytes": 940433408 + }, + { + "query": "MCP tool call fails because a required field is missing from the JSON input", + "ranked": [ + "mcp-schema-validation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X060GEGPNA197DE82MV706", + "id": "01M1X0DF9J7GHS17KMAB4XP44X", + "kind": "memory", + "score": 0.9998551607131958, + "summary": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array \u2014 omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1522.1662000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 799, + "mcp_result_bytes": 880, + "wire_bytes": 917, + "reported_used_tokens": 880, + "working_set_bytes": 939511808, + "peak_working_set_bytes": 940433408 + }, + { + "query": "Claude Code rejects the tool name with a hyphen in it", + "ranked": [ + "mcp-tool-naming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X060HMFXE9A894YPA74PVR", + "id": "01M1X0DGSDBGGY6BATVD1TBD6E", + "kind": "memory", + "score": 0.999729573726654, + "summary": "project:fact - [tags: mcp tool naming convention kimetsu] MCP tool names must be valid identifiers for all host agents. Claude Code restricts tool names to `[a-zA-Z0-9_-]` and max 64 chars. Use `snake_case` (kimetsu_brain_context, kimetsu_brain_record) \u2014 hyphen is technically allowed but some hosts reject it." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1586.333, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 686, + "mcp_result_bytes": 767, + "wire_bytes": 804, + "reported_used_tokens": 767, + "working_set_bytes": 939511808, + "peak_working_set_bytes": 940433408 + }, + { + "query": "MCP response path uses backslashes and the host rejects it", + "ranked": [ + "mcp-transcript-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X060JTWDDA7S5XDZARTCHT", + "id": "01M1X0DJAEQEE1YJXQ0QNT76ZH", + "kind": "memory", + "score": 0.9998076558113098, + "summary": "project:fact - [tags: mcp transcript paths kimetsu hooks runs] kimetsu writes run transcripts to `/.kimetsu/runs//`. The post-session hook reads the latest run's transcript to trigger memory harvest. On Windows, the path uses backslashes internally but the MCP JSON must use forward slashes or the host may reject path-type arguments." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1771.4704000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 724, + "mcp_result_bytes": 805, + "wire_bytes": 842, + "reported_used_tokens": 805, + "working_set_bytes": 939511808, + "peak_working_set_bytes": 940433408 + }, + { + "query": "AWS credentials not found \u2014 which env var does kimetsu read for Bedrock?", + "ranked": [ + "aws-credentials-chain", + "aws-region-resolution", + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X060M5GC897T5Z6GA6S8YY", + "id": "01M1X0DM1Y6DRJT1FN6WWXR198", + "kind": "memory", + "score": 0.9999332427978516, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + }, + { + "expansion_handle": "memory:01M1X060NPXN24BW4FY2DMGRH1", + "id": "01M1X0DM1YBZ2DSXYR4QZKFXM7", + "kind": "memory", + "score": 0.999756395816803, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X05WS4SP2GV31Y7Z83MPR1", + "id": "01M1X0DM1YCG2RTDQXQDHYV1EB", + "kind": "memory", + "score": 0.9983052015304564, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X05WZN8M0WJBHX8WAN1FW1", + "id": "01M1X0DM1YP5WEBP7ZZFR962V2", + "kind": "memory", + "score": 0.7727437615394592, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1690.6656, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3454, + "mcp_result_bytes": 3617, + "wire_bytes": 3654, + "reported_used_tokens": 3617, + "working_set_bytes": 939511808, + "peak_working_set_bytes": 940433408 + }, + { + "query": "Bedrock InvokeModel fails because the region is not configured", + "ranked": [ + "aws-region-resolution", + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X060NPXN24BW4FY2DMGRH1", + "id": "01M1X0DNPTVKK9ZW32HKVNKFEZ", + "kind": "memory", + "score": 0.9998329877853394, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X05WS4SP2GV31Y7Z83MPR1", + "id": "01M1X0DNPVMESRCF6607JVBZY4", + "kind": "memory", + "score": 0.871902346611023, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X05WZN8M0WJBHX8WAN1FW1", + "id": "01M1X0DNPTR3V8J1BC9Y3DYPJ3", + "kind": "memory", + "score": 0.7683040499687195, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1632.3465, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2786, + "mcp_result_bytes": 2931, + "wire_bytes": 2968, + "reported_used_tokens": 2931, + "working_set_bytes": 939511808, + "peak_working_set_bytes": 940433408 + }, + { + "query": "how do I handle ThrottlingException from Bedrock with exponential backoff?", + "ranked": [ + "aws-retry-throttling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X060PXDF79R4DP7JK4GGQ6", + "id": "01M1X0DQ9TPHA6W2KJCC4ZPS31", + "kind": "memory", + "score": 0.9984448552131652, + "summary": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with \u00b125% jitter." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1655.4223000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 771, + "mcp_result_bytes": 868, + "wire_bytes": 905, + "reported_used_tokens": 868, + "working_set_bytes": 939511808, + "peak_working_set_bytes": 940433408 + }, + { + "query": "generating a presigned S3 URL for brain export without exposing credentials", + "ranked": [ + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X060R52T1PEXSFG92WM307", + "id": "01M1X0DRY0ZFWPZEVT03DY7JXC", + "kind": "memory", + "score": 0.9999775886535645, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1781.8283, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 875, + "mcp_result_bytes": 956, + "wire_bytes": 993, + "reported_used_tokens": 956, + "working_set_bytes": 939511808, + "peak_working_set_bytes": 940433408 + }, + { + "query": "IMDSv2 token required for instance metadata \u2014 PUT before GET", + "ranked": [ + "aws-instance-metadata" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X060SE2TSNSHZWW314WP3Y", + "id": "01M1X0DTNW2646CSWF480T66MD", + "kind": "memory", + "score": 0.999979853630066, + "summary": "project:fact - [2026-09-07] [tags: aws imds instance-metadata ec2 token] The AWS Instance Metadata Service v2 (IMDSv2) requires a session token: PUT `http://169.254.169.254/latest/api/token` with `X-aws-ec2-metadata-token-ttl-seconds: 21600` to get a token, then GET metadata with `X-aws-ec2-metadata-token: `. IMDSv1 (no token) is disabled on hardened instances. The metadata endpoint is only reachable from within EC2 \u2014 a connection timeout means you're not on EC2." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1470.5481, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 851, + "mcp_result_bytes": 932, + "wire_bytes": 969, + "reported_used_tokens": 932, + "working_set_bytes": 939511808, + "peak_working_set_bytes": 940433408 + }, + { + "query": "Cargo cache key strategy for GitHub Actions to avoid toolchain version collisions", + "ranked": [ + "ci-cache-keys" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X060XXRM2GGMA6C6V79EZ9", + "id": "01M1X0DW38RJZ4D14QVN9B9PG0", + "kind": "memory", + "score": 0.9999439716339112, + "summary": "project:fact - [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key \u2014 macOS and Windows have incompatible artifact formats." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1293.7559, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 788, + "mcp_result_bytes": 869, + "wire_bytes": 906, + "reported_used_tokens": 869, + "working_set_bytes": 939511808, + "peak_working_set_bytes": 940433408 + }, + { + "query": "CI matrix has 18 jobs and costs too much \u2014 how do I reduce it?", + "ranked": [ + "ci-matrix-explosion" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X060Z238VWX58G4788SB6B", + "id": "01M1X0DXC5PKJ6FV2NW5878DE4", + "kind": "memory", + "score": 0.99962317943573, + "summary": "project:fact - [tags: ci github-actions matrix jobs resources] A CI matrix combining OS (3) x Rust toolchain (3) x features (2) = 18 jobs. Each spawns a runner; at $0.008/min for Ubuntu and $0.016/min for Windows, a 10-minute build costs $2.40 per push. Reduce: test the full matrix only on PRs to main; on feature branches, test only Linux+stable." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1420.1372000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 721, + "mcp_result_bytes": 802, + "wire_bytes": 839, + "reported_used_tokens": 802, + "working_set_bytes": 939511808, + "peak_working_set_bytes": 940433408 + }, + { + "query": "GitHub Actions secret accidentally printed in build logs", + "ranked": [ + "ci-secrets-masking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X061078MQ22FKF61HZT8TX", + "id": "01M1X0DYQZ4HNNDV0934YSJ64P", + "kind": "memory", + "score": 0.9951270818710328, + "summary": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output \u2014 but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1562.9173, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 711, + "mcp_result_bytes": 792, + "wire_bytes": 829, + "reported_used_tokens": 792, + "working_set_bytes": 939511808, + "peak_working_set_bytes": 940433408 + }, + { + "query": "how long do GitHub Actions artifacts persist and what's the storage limit?", + "ranked": [ + "ci-artifact-retention" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0611F13S2BWZ18S1QKE8K", + "id": "01M1X0E08YRR6D3519634GX4R8", + "kind": "memory", + "score": 0.9989684820175172, + "summary": "project:fact - [tags: ci github-actions artifacts retention benchmark] GitHub Actions artifacts are retained for 90 days (default). For benchmark results, use `actions/upload-artifact` with `retention-days: 365` for long-term tracking. The free tier has 500MB storage \u2014 per-combo JSON files from kimetsu bench (each ~60KB) add up fast if you upload them on every push." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1398.7204, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 744, + "mcp_result_bytes": 825, + "wire_bytes": 862, + "reported_used_tokens": 825, + "working_set_bytes": 939511808, + "peak_working_set_bytes": 940433408 + }, + { + "query": "timing-based test flake in CI \u2014 quarantine or fix?", + "ranked": [ + "ci-flaky-quarantine", + "testing-time-dependent-flakes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0612G4E6G9F7MGT838FVB", + "id": "01M1X0E1MSDS2MHA2KSGATVG5J", + "kind": "memory", + "score": 0.9940990209579468, + "summary": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal \u2014 a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output." + }, + { + "expansion_handle": "memory:01M1X0605JEVVKHAY9RXBGWJSW", + "id": "01M1X0E1MSZVAC01TAGACNE4A6", + "kind": "memory", + "score": 0.7894570231437683, + "summary": "project:fact - [tags: testing time flaky clock mock rust] Tests that depend on wall-clock time are inherently flaky under load (slow CI runners, GC pauses). Abstract time behind a trait (`Clock: Fn() -> SystemTime`) injected at construction, and supply a fake in tests. For tests checking that something happened \"within N seconds\", use a generous multiple of the expected duration (10x is not unreasonable for CI)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1307.0957, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1340, + "mcp_result_bytes": 1443, + "wire_bytes": 1480, + "reported_used_tokens": 1443, + "working_set_bytes": 939515904, + "peak_working_set_bytes": 940437504 + }, + { + "query": "kimetsu doctor says the MCP server is running \u2014 how do I stop it before an update?", + "ranked": [ + "kimetsu-daemon-lifecycle", + "mcp-env-propagation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0613T09134FY71BP84VZG", + "id": "01M1X0E2X9CDSMWTA1HT2XFZM3", + "kind": "memory", + "score": 0.9999032020568848, + "summary": "project:fact - [2026-09-07] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1X060F787CV8C15YJZ8CNQE", + "id": "01M1X0E2X9CFWA9BHXXE2Q5VAF", + "kind": "memory", + "score": 0.9228461980819702, + "summary": "project:fact - [2026-09-07] [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment \u2014 changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1625.6802000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1266, + "mcp_result_bytes": 1365, + "wire_bytes": 1402, + "reported_used_tokens": 1365, + "working_set_bytes": 939515904, + "peak_working_set_bytes": 940437504 + }, + { + "query": "noise capsules consuming token budget without contributing retrieval signal", + "ranked": [ + "kimetsu-capsule-budgets" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X06152HGTJS07214G4MFEY", + "id": "01M1X0E4HGEQZKWPMA2BEE9CBM", + "kind": "memory", + "score": 0.9999643564224244, + "summary": "project:fact - [tags: kimetsu capsule tokens budget retrieval] kimetsu retrieval enforces a token budget per capsule type: memory capsules are capped at 6000 tokens total (across all retrieved memories), file capsules at 3000 tokens. When a memory is large and would exceed the budget, it is truncated at a sentence boundary. The budget is enforced AFTER reranking \u2014 reranking may reorder results so that a truncated high-ranked memory displaces a full lower-ranked one." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1574.1007, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 847, + "mcp_result_bytes": 928, + "wire_bytes": 965, + "reported_used_tokens": 928, + "working_set_bytes": 939511808, + "peak_working_set_bytes": 940437504 + }, + { + "query": "kimetsu_brain_record writes to the wrong brain location \u2014 user vs project scope", + "ranked": [ + "kimetsu-memory-scopes", + "kimetsu-write-tools-gate", + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X06165XWNTWPM01MH2BBDB", + "id": "01M1X0E62YJYPVHCFT1MNFF70E", + "kind": "memory", + "score": 0.9998672008514404, + "summary": "project:fact - [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available \u2014 if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope." + }, + { + "expansion_handle": "memory:01M1X0619ZNMV3ST0HFAD5K0PN", + "id": "01M1X0E62YYEYY9GQ42VGFHMVT", + "kind": "memory", + "score": 0.9389453530311584, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level \u2014 disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1X05X2XE8B8NBDATM3RYT6N", + "id": "01M1X0E62YG414Q4VKK0M8PCC4", + "kind": "memory", + "score": 0.9388486742973328, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1370.3418000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2097, + "mcp_result_bytes": 2214, + "wire_bytes": 2251, + "reported_used_tokens": 2214, + "working_set_bytes": 939515904, + "peak_working_set_bytes": 940437504 + }, + { + "query": "how do I configure kimetsu to use Claude Haiku for harvesting but Opus for the agent?", + "ranked": [ + "kimetsu-distiller-config", + "aws-region-resolution", + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0617GZR6NV1ZFE7G559X4", + "id": "01M1X0E7CS8HY5M8S2DM112A10", + "kind": "memory", + "score": 0.9999725818634032, + "summary": "project:fact - [tags: kimetsu distiller harvest config provider] The kimetsu distiller (auto-harvester) uses a SEPARATE provider configuration from the main agent: `distiller.provider`, `distiller.model`, `distiller.api_key`. This allows running the agent on an expensive model (Claude Opus) while harvesting with a cheap model (Claude Haiku). If `distiller.provider` is not set, it inherits `provider`." + }, + { + "expansion_handle": "memory:01M1X060NPXN24BW4FY2DMGRH1", + "id": "01M1X0E7CTJFH3837CQRFT9YZD", + "kind": "memory", + "score": 0.8705393075942993, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X05WS4SP2GV31Y7Z83MPR1", + "id": "01M1X0E7CT3BW3ZM8T4TP37QZB", + "kind": "memory", + "score": 0.8454174399375916, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1624.291, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2348, + "mcp_result_bytes": 2473, + "wire_bytes": 2510, + "reported_used_tokens": 2473, + "working_set_bytes": 939515904, + "peak_working_set_bytes": 940437504 + }, + { + "query": "first agent turn is slow because kimetsu proactive hook runs embedding inference", + "ranked": [ + "kimetsu-proactive-hooks", + "kimetsu-distiller-config", + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0618RXBRKCMZNK66BZXRH", + "id": "01M1X0E8Z82X89DKZ7JMAPT916", + "kind": "memory", + "score": 0.999129831790924, + "summary": "project:fact - [2026-09-07] [tags: kimetsu proactive hooks context injection] kimetsu's proactive context injection runs before each agent turn (pre-turn hook) and injects relevant memories into the system prompt prefix. The hook invocation adds latency to the first token: embedding inference + vector search + reranking + context formatting. On a cold start, this can be 1-3 seconds." + }, + { + "expansion_handle": "memory:01M1X0617GZR6NV1ZFE7G559X4", + "id": "01M1X0E8Z85N2G56YSVBKH0C78", + "kind": "memory", + "score": 0.8709061145782471, + "summary": "project:fact - [2026-09-07] [tags: kimetsu distiller harvest config provider] The kimetsu distiller (auto-harvester) uses a SEPARATE provider configuration from the main agent: `distiller.provider`, `distiller.model`, `distiller.api_key`. This allows running the agent on an expensive model (Claude Opus) while harvesting with a cheap model (Claude Haiku). If `distiller.provider` is not set, it inherits `provider`." + }, + { + "expansion_handle": "memory:01M1X060DYCSGDHSEP6ZVV7E31", + "id": "01M1X0E8Z88MCPZXA2QAS93QBZ", + "kind": "memory", + "score": 0.6799831390380859, + "summary": "project:fact - [2026-09-07] [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1834.1083, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1967, + "mcp_result_bytes": 2084, + "wire_bytes": 2121, + "reported_used_tokens": 2084, + "working_set_bytes": 939659264, + "peak_working_set_bytes": 940572672 + }, + { + "query": "make the kimetsu brain read-only for certain repos on a shared remote server", + "ranked": [ + "kimetsu-write-tools-gate", + "remote-ingest-split-roots", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0619ZNMV3ST0HFAD5K0PN", + "id": "01M1X0EAS4V7W65XKPM9WBQ0FV", + "kind": "memory", + "score": 0.9999514818191528, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level \u2014 disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1X05WJQ71HE168C1JX89W7A", + "id": "01M1X0EAS45P05R0XP5TCDZ76Z", + "kind": "memory", + "score": 0.9976721405982972, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1X05WN6EB9RPFK8EF9QTXFD", + "id": "01M1X0EAS40S83Z928XKMH8GYP", + "kind": "memory", + "score": 0.915355622768402, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1681.6436999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2725, + "mcp_result_bytes": 2890, + "wire_bytes": 2927, + "reported_used_tokens": 2890, + "working_set_bytes": 939659264, + "peak_working_set_bytes": 940572672 + }, + { + "query": "kimetsu FTS search misses 'deadlocking' when memory says 'deadlock'", + "ranked": [ + "kimetsu-query-stemming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X061D3XT2MBJCMZ743PK9Y", + "id": "01M1X0ECD4Q9HWP7XX5BAPBDEB", + "kind": "memory", + "score": 0.989694595336914, + "summary": "project:fact - [2026-09-07] [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1257.2069, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 781, + "mcp_result_bytes": 878, + "wire_bytes": 915, + "reported_used_tokens": 878, + "working_set_bytes": 939659264, + "peak_working_set_bytes": 940572672 + }, + { + "query": "how does pool size affect retrieval recall and latency in the bench?", + "ranked": [ + "kimetsu-rerank-pool" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X061ECR7T6YRFGEBN94TM9", + "id": "01M1X0EDMDW3N1242G9RHXBE7A", + "kind": "memory", + "score": 0.999588668346405, + "summary": "project:fact - [tags: kimetsu reranker pool size ann retrieval] kimetsu's retrieval pipeline: ANN (approximate nearest neighbor) retrieves a pool of candidates, then the reranker reorders them, then the top-K are returned. The pool size (default 6 for production, 12 in bench) controls the recall-latency tradeoff: larger pool = higher recall = more reranker calls = more latency. For the jina-tiny reranker, pool 12 adds ~80ms vs pool 6." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1318.489, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 812, + "mcp_result_bytes": 893, + "wire_bytes": 930, + "reported_used_tokens": 893, + "working_set_bytes": 939659264, + "peak_working_set_bytes": 940576768 + }, + { + "query": "second embedder in a remote bench run gets worse results than the first", + "ranked": [ + "kimetsu-bench-remote-embedder-singleton" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X061FH4AQNYP3Q7X2RV0MJ", + "id": "01M1X0EEY26B54Y0KZZXRHH4ZF", + "kind": "memory", + "score": 0.8715754747390747, + "summary": "project:fact - [2026-09-07] [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1436.9513, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 895, + "mcp_result_bytes": 976, + "wire_bytes": 1013, + "reported_used_tokens": 976, + "working_set_bytes": 939753472, + "peak_working_set_bytes": 940670976 + }, + { + "query": "what is the expected JSON schema for kimetsu brain bench dataset files?", + "ranked": [ + "kimetsu-eval-fixture-shape", + "mcp-schema-validation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X061GTEJAPN83RMS8402NW", + "id": "01M1X0EGAKRS4GFD81DR4YS1A9", + "kind": "memory", + "score": 0.9999712705612184, + "summary": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` \u2014 a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases)." + }, + { + "expansion_handle": "memory:01M1X060GEGPNA197DE82MV706", + "id": "01M1X0EGAKAKKQWVBAVPFF55GH", + "kind": "memory", + "score": 0.7343910336494446, + "summary": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array \u2014 omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1309.6608999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1390, + "mcp_result_bytes": 1533, + "wire_bytes": 1570, + "reported_used_tokens": 1533, + "working_set_bytes": 939868160, + "peak_working_set_bytes": 940781568 + }, + { + "query": "what does MRR mean and how do I interpret a 0.01 difference between combos?", + "ranked": [ + "kimetsu-mrr-metric" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X061J54DQ601JTBSDYXHMV", + "id": "01M1X0EHKXYPAG9PTJA7YWC9WH", + "kind": "memory", + "score": 0.930732488632202, + "summary": "project:fact - [tags: kimetsu bench mrr recall metrics evaluation] kimetsu bench reports MRR (Mean Reciprocal Rank) and Recall@K. MRR is 1/rank_of_first_relevant_result, averaged across cases; it penalizes models that rank the correct answer 2nd or 3rd. Recall@K is the fraction of cases where at least one relevant answer appears in the top K." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1339.1010999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 719, + "mcp_result_bytes": 800, + "wire_bytes": 837, + "reported_used_tokens": 800, + "working_set_bytes": 939872256, + "peak_working_set_bytes": 940785664 + }, + { + "query": "SQLITE_BUSY keeps appearing even with WAL mode enabled", + "ranked": [ + "sqlite-busy-timeout-wal", + "sqlite-wal-network-drive" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05XEEYRHK31DS2Q07F462", + "id": "01M1X0EJXACBE1WKJ27XS1HSHE", + "kind": "memory", + "score": 0.9940937161445618, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + }, + { + "expansion_handle": "memory:01M1X05XGXEGK24HX3F30XBV45", + "id": "01M1X0EJXA6M67BJ8C7SZ0Z709", + "kind": "memory", + "score": 0.7747130393981934, + "summary": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1678.9282, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1423, + "mcp_result_bytes": 1522, + "wire_bytes": 1559, + "reported_used_tokens": 1522, + "working_set_bytes": 939909120, + "peak_working_set_bytes": 940822528 + }, + { + "query": "my brain file got huge again right after I compacted it", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1482.1295, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 939999232, + "peak_working_set_bytes": 940900352 + }, + { + "query": "all my FTS queries stopped returning results after I changed the tokenizer config", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1464.1575, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 939999232, + "peak_working_set_bytes": 940912640 + }, + { + "query": "something is preventing the kimetsu binary from being replaced during update", + "ranked": [ + "kimetsu-daemon-lifecycle" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0613T09134FY71BP84VZG", + "id": "01M1X0EQEBRYCA89ATDJGW895R", + "kind": "memory", + "score": 0.9216884970664978, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + } + ], + "positive_recall_at_4": 0.3333333333333333, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1298.4844, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 752, + "mcp_result_bytes": 833, + "wire_bytes": 870, + "reported_used_tokens": 833, + "working_set_bytes": 940032000, + "peak_working_set_bytes": 940945408 + }, + { + "query": "tool call results not appearing in the context \u2014 is the semantic floor too high?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1336.2471, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 228, + "mcp_result_bytes": 291, + "wire_bytes": 328, + "reported_used_tokens": 291, + "working_set_bytes": 940158976, + "peak_working_set_bytes": 941080576 + }, + { + "query": "CARGO_INCREMENTAL=0 in CI prevents a class of spurious compilation errors", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05XY122KP5Z06P65A8Z5N", + "id": "01M1X0ET0K2KVPWZFYM5E2BVK5", + "kind": "memory", + "score": 0.7999841570854187, + "summary": "project:fact - [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1296.9076, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 877, + "mcp_result_bytes": 958, + "wire_bytes": 995, + "reported_used_tokens": 958, + "working_set_bytes": 940158976, + "peak_working_set_bytes": 941080576 + }, + { + "query": "how do I check whether my Cargo workspace respects the MSRV constraint?", + "ranked": [ + "cargo-msrv", + "cargo-patch-section", + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05Y1AT99W716C7537ZD3Y", + "id": "01M1X0EV8ZFD0AAF41PPSD5WB2", + "kind": "memory", + "score": 0.9985359907150269, + "summary": "project:fact - [tags: cargo rust msrv edition compatibility] Set `rust-version` in each `Cargo.toml` to declare the minimum supported Rust version (MSRV). Cargo enforces this with `--check`: `cargo check` fails if the toolchain is older than `rust-version`. Keep MSRV as old as your oldest supported deployment target." + }, + { + "expansion_handle": "memory:01M1X05Y067GYTZF5KQA0TBS6K", + "id": "01M1X0EV8ZFGAGS17SMQE7X6QQ", + "kind": "memory", + "score": 0.620707631111145, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace \u2014 including transitive deps \u2014 that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1X05XVN17RZ1ZWVR1CH6N4A", + "id": "01M1X0EV8Z9QCW051WFDA092KH", + "kind": "memory", + "score": 0.6190821528434753, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1316.4613000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1915, + "mcp_result_bytes": 2036, + "wire_bytes": 2073, + "reported_used_tokens": 2036, + "working_set_bytes": 940158976, + "peak_working_set_bytes": 941080576 + }, + { + "query": "rusqlite connection opened but ON DELETE CASCADE cascade never fires", + "ranked": [ + "sqlite-foreign-keys-default-off" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05XMP7RX7AMZ2A33Q6W20", + "id": "01M1X0EWJ1QKNHD1JYCRKEMAG0", + "kind": "memory", + "score": 0.9797621369361876, + "summary": "project:fact - [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting \u2014 every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1745.7449000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 735, + "mcp_result_bytes": 816, + "wire_bytes": 853, + "reported_used_tokens": 816, + "working_set_bytes": 940158976, + "peak_working_set_bytes": 941080576 + }, + { + "query": "I cannot connect to kimetsu-remote \u2014 something about TLS cert validation failed", + "ranked": [ + "http-tls-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05ZZ0XY647CTYY7W8C1EM", + "id": "01M1X0EY8XXFN8AG1R90SP3SHY", + "kind": "memory", + "score": 0.5578561425209045, + "summary": "project:fact - [tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle \u2014 the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1309.0355, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 779, + "mcp_result_bytes": 860, + "wire_bytes": 897, + "reported_used_tokens": 860, + "working_set_bytes": 994283520, + "peak_working_set_bytes": 995201024 + }, + { + "query": "graceful shutdown fails because in-flight SQLite queries are still running when pool closes", + "ranked": [ + "tokio-shutdown-ordering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05ZSQVC0YB5CZZ6E6NXZY", + "id": "01M1X0EZHP8C1NTBH8N3SNCRK1", + "kind": "memory", + "score": 0.9991455078125, + "summary": "project:fact - [2026-09-07] [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries \u2014 the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1330.354, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 944, + "mcp_result_bytes": 1025, + "wire_bytes": 1062, + "reported_used_tokens": 1025, + "working_set_bytes": 994283520, + "peak_working_set_bytes": 995201024 + }, + { + "query": "kimetsu-remote response takes 8 seconds \u2014 which stage is slow?", + "ranked": [ + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X060DYCSGDHSEP6ZVV7E31", + "id": "01M1X0F0VB2XA4H23NNXS9ECA2", + "kind": "memory", + "score": 0.980535328388214, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1507.4401, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 858, + "mcp_result_bytes": 939, + "wire_bytes": 976, + "reported_used_tokens": 939, + "working_set_bytes": 994287616, + "peak_working_set_bytes": 995201024 + }, + { + "query": "git reflog to rescue accidentally deleted branch", + "ranked": [ + "git-reflog-rescue" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05ZGE299FAXV9GB34BGK7", + "id": "01M1X0F2A919PRJ4GPGSZ3P240", + "kind": "memory", + "score": 0.9931837916374208, + "summary": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone \u2014 they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only \u2014 remote reflog is not accessible via normal git commands." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1386.4682, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 762, + "mcp_result_bytes": 843, + "wire_bytes": 880, + "reported_used_tokens": 843, + "working_set_bytes": 994287616, + "peak_working_set_bytes": 995201024 + }, + { + "query": "git submodule --remote advances the pinned SHA unexpectedly", + "ranked": [ + "git-submodule-pinning" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05ZF8KVJJ7AM0A778QQJK", + "id": "01M1X0F3P55BQ2WYZ5YXGJBG8N", + "kind": "memory", + "score": 0.9999607801437378, + "summary": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip \u2014 this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1648.1085, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 752, + "mcp_result_bytes": 833, + "wire_bytes": 870, + "reported_used_tokens": 833, + "working_set_bytes": 994299904, + "peak_working_set_bytes": 995205120 + }, + { + "query": "axum SSE streaming drops the last event when client disconnects", + "ranked": [ + "http-streaming-bodies" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X06008K641GZ48NFRRPA08", + "id": "01M1X0F59MY2TTCP8R3GXGJ9NT", + "kind": "memory", + "score": 0.7960996031761169, + "summary": "project:fact - [2026-09-07] [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding \u2014 a chunk may split across frame boundaries." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1837.2136, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 859, + "mcp_result_bytes": 940, + "wire_bytes": 977, + "reported_used_tokens": 940, + "working_set_bytes": 994553856, + "peak_working_set_bytes": 995467264 + }, + { + "query": "how do I detect that I am running inside a git worktree vs the main checkout?", + "ranked": [ + "git-worktree-brain-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05ZA9QQ47DPP3Q0PVXQSH", + "id": "01M1X0F72Q8PYQZGJVSC9B66P7", + "kind": "memory", + "score": 0.9114787578582764, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root \u2014 if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1310.6833, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 881, + "mcp_result_bytes": 962, + "wire_bytes": 999, + "reported_used_tokens": 962, + "working_set_bytes": 994549760, + "peak_working_set_bytes": 995467264 + }, + { + "query": "ONNX Runtime intra-op threads causing CPU contention during parallel bench", + "ranked": [ + "onnx-ort-threading" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X05Z918YHAY118R8F113T6", + "id": "01M1X0F8BJE38MSRAGF87YS36J", + "kind": "memory", + "score": 0.9999747276306152, + "summary": "project:fact - [tags: onnx ort thread-pool parallelism cpu] ORT (ONNX Runtime) creates its own inter-op and intra-op thread pools. In a multi-process bench setup, each child inherits these pools and they compete for CPU cores. Set `SessionOptionsBuilder::with_intra_threads(1).with_inter_threads(1)` if you're running many parallel bench processes \u2014 this sacrifices per-inference throughput for lower contention." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1228.0216, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 789, + "mcp_result_bytes": 870, + "wire_bytes": 907, + "reported_used_tokens": 870, + "working_set_bytes": 994553856, + "peak_working_set_bytes": 995467264 + }, + { + "query": "what is the right way to supply AWS session token alongside access key and secret?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1574.6454, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 994562048, + "peak_working_set_bytes": 995471360 + } + ], + "id": "existing-development-100", + "dimension": "retrieval", + "tier": "hard", + "score": 0.8293650793650794, + "skipped": false, + "detail": "positive-recall@4=0.84 mrr=0.86 stale-hit=n/a resolution=n/a false-injection=0.385 (n=13) positive-n=197 negative-n=13 (210 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 0.8293650793650794, + 1 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 0.8293650793650794, + "n": 1, + "ci95": null + } + }, + "overall_index": 0.8293650793650794, + "scenario_weighted_index": 0.8293650793650794 +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-retrieval/development/2-baseline.json b/docs/audits/2026-09-07-retrieval/development/2-baseline.json new file mode 100644 index 0000000..955878a --- /dev/null +++ b/docs/audits/2026-09-07-retrieval/development/2-baseline.json @@ -0,0 +1,6811 @@ +{ + "generated_at": "2026-09-07T04:10:45.2231949Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\tmp-tests\\brainbench-development-100.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "test_env_lock inside with_user_brain_disabled deadlock", + "ranked": [ + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RY96B4DJ5PBZXCAERB1C", + "id": "01M1X0S3Q65WHPHHJ9Y6RS59VP", + "kind": "memory", + "score": 0.9999488592147828, + "summary": "project:fact - [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure \u2014 `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1183.7074, + "first_query": true, + "server_startup_ms": 97.206, + "model_text_bytes": 796, + "mcp_result_bytes": 877, + "wire_bytes": 912, + "reported_used_tokens": 877, + "working_set_bytes": 227233792, + "peak_working_set_bytes": 248389632 + }, + { + "query": "why does my test hang after calling with_user_brain_disabled when I also lock test_env_lock?", + "ranked": [ + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RY96B4DJ5PBZXCAERB1C", + "id": "01M1X0S4GQFP0CX27G7X792PMQ", + "kind": "memory", + "score": 0.9990190267562866, + "summary": "project:fact - [2026-09-07] [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure \u2014 `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 933.9685, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 808, + "mcp_result_bytes": 889, + "wire_bytes": 924, + "reported_used_tokens": 889, + "working_set_bytes": 229330944, + "peak_working_set_bytes": 248389632 + }, + { + "query": "ingest_repo_at_root brain_root files_root kimetsu remote", + "ranked": [ + "remote-ingest-split-roots", + "kimetsu-write-tools-gate", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYAH80HT0J4XD4TW3J26", + "id": "01M1X0S5DR2HVTEENE6KHSBQDM", + "kind": "memory", + "score": 0.999886393547058, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1X0S2KNSRADJJPEW568DX62", + "id": "01M1X0S5DRB1ESEMNGFP4H3JET", + "kind": "memory", + "score": 0.8439717888832092, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level \u2014 disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1X0RYD13DKH4PFCSN9N3JVS", + "id": "01M1X0S5DRWR7425GVSXJAFAMK", + "kind": "memory", + "score": 0.8363722562789917, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1032.5409000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2726, + "mcp_result_bytes": 2891, + "wire_bytes": 2926, + "reported_used_tokens": 2891, + "working_set_bytes": 252088320, + "peak_working_set_bytes": 252997632 + }, + { + "query": "why does the remote server index the wrong directory when I run kimetsu brain ingest?", + "ranked": [ + "remote-ingest-split-roots", + "onnx-dim-mismatch" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYAH80HT0J4XD4TW3J26", + "id": "01M1X0S6EHQTVZJQN13A44DVBN", + "kind": "memory", + "score": 0.9836117625236512, + "summary": "project:fact - [2026-09-07] [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1X0S0F0X8EQPVKXEKWVPP7B", + "id": "01M1X0S6EHBHK4HAW22VP2VWNQ", + "kind": "memory", + "score": 0.3657674789428711, + "summary": "project:fact - [2026-09-07] [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results \u2014 the ANN index shape mismatch isn't always caught at runtime." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1073.2961, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1800, + "mcp_result_bytes": 1899, + "wire_bytes": 1934, + "reported_used_tokens": 1899, + "working_set_bytes": 257638400, + "peak_working_set_bytes": 258560000 + }, + { + "query": "kimetsu plugin install --remote mcp.json authorization bearer token", + "ranked": [ + "remote-mcp-host-wiring", + "mcp-stdout-protocol" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYD13DKH4PFCSN9N3JVS", + "id": "01M1X0S7FXE47MT3E8F9PTPD78", + "kind": "memory", + "score": 0.999605119228363, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + }, + { + "expansion_handle": "memory:01M1X0S1R25W8ZJDGRFWG990X7", + "id": "01M1X0S7FYR35Y2DAWGHXVJJJ0", + "kind": "memory", + "score": 0.3375842869281769, + "summary": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 970.8101, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1472, + "mcp_result_bytes": 1619, + "wire_bytes": 1654, + "reported_used_tokens": 1619, + "working_set_bytes": 258220032, + "peak_working_set_bytes": 259145728 + }, + { + "query": "how do I wire a remote kimetsu brain into Claude Code without storing the token in the config file?", + "ranked": [ + "remote-mcp-host-wiring", + "mcp-tool-naming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYD13DKH4PFCSN9N3JVS", + "id": "01M1X0S8E688A104JMG0GBQTDD", + "kind": "memory", + "score": 0.9963359832763672, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + }, + { + "expansion_handle": "memory:01M1X0S1WXQG18AAW2MVWESA3A", + "id": "01M1X0S8E6ZZQ6WHV877N1G01C", + "kind": "memory", + "score": 0.831425666809082, + "summary": "project:fact - [tags: mcp tool naming convention kimetsu] MCP tool names must be valid identifiers for all host agents. Claude Code restricts tool names to `[a-zA-Z0-9_-]` and max 64 chars. Use `snake_case` (kimetsu_brain_context, kimetsu_brain_record) \u2014 hyphen is technically allowed but some hosts reject it." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 984.4977, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1454, + "mcp_result_bytes": 1601, + "wire_bytes": 1636, + "reported_used_tokens": 1601, + "working_set_bytes": 258379776, + "peak_working_set_bytes": 259301376 + }, + { + "query": "cargo feature unification kimetsu-brain embeddings fastembed test failure", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-profile-override", + "clap-version-build-flavor" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYFCG3DKA4C6N43J3PQW", + "id": "01M1X0S9CYJ3FP5DHTXEMN8VVC", + "kind": "memory", + "score": 0.9996790885925292, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X0RZRY0ANM96SJZ9PXYYY2", + "id": "01M1X0S9CYWW1Q88PYJ2CW8NB5", + "kind": "memory", + "score": 0.9923595786094666, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1X0RYW44BMZQY7WYDDH5B2Z", + "id": "01M1X0S9CYNN6MYXVW9T5WC1C7", + "kind": "memory", + "score": 0.585203230381012, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 957.4585, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2387, + "mcp_result_bytes": 2524, + "wire_bytes": 2559, + "reported_used_tokens": 2524, + "working_set_bytes": 260268032, + "peak_working_set_bytes": 261189632 + }, + { + "query": "my integration tests pass in isolation but break when I run cargo test --workspace \u2014 embedder changed?", + "ranked": [ + "cargo-feature-unification-embeddings", + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYFCG3DKA4C6N43J3PQW", + "id": "01M1X0SABF1EKC1NHT3FT4KCJV", + "kind": "memory", + "score": 0.9943140745162964, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X0RYV389BQAB7HQN0MA99V", + "id": "01M1X0SABFSAF36Y6BSGA5AJG7", + "kind": "memory", + "score": 0.31398114562034607, + "summary": "project:fact - [2026-09-07] [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1030.2062999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1714, + "mcp_result_bytes": 1817, + "wire_bytes": 1852, + "reported_used_tokens": 1817, + "working_set_bytes": 261459968, + "peak_working_set_bytes": 262381568 + }, + { + "query": "build_anthropic_body bedrock-2023-05-31 InvokeModel blocking reqwest", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYH0B8FECY7X9RAA8ZWE", + "id": "01M1X0SBBFGNCXPS7WHV75JDNE", + "kind": "memory", + "score": 0.9973788261413574, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X0RYQNNBEV2TCA2CR5B3P8", + "id": "01M1X0SBBFH0VCDN1QBSKN2V7D", + "kind": "memory", + "score": 0.6916899085044861, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 815.7412, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2193, + "mcp_result_bytes": 2320, + "wire_bytes": 2356, + "reported_used_tokens": 2320, + "working_set_bytes": 261881856, + "peak_working_set_bytes": 262795264 + }, + { + "query": "how do I add AWS Bedrock as a model provider in Kimetsu without pulling in the aws-sdk?", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-region-resolution", + "aws-credentials-chain", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYH0B8FECY7X9RAA8ZWE", + "id": "01M1X0SC52W3S4GS6CMHATMAHM", + "kind": "memory", + "score": 0.9998898506164552, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X0S20Z7NDCY69PTPT27MYZ", + "id": "01M1X0SC52Z4WEVC5G5RGQKZ6Z", + "kind": "memory", + "score": 0.995676338672638, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X0S1ZFY1N7K6ABQSCYNE24", + "id": "01M1X0SC52V878VD46ND90EAKA", + "kind": "memory", + "score": 0.987064242362976, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + }, + { + "expansion_handle": "memory:01M1X0RYQNNBEV2TCA2CR5B3P8", + "id": "01M1X0SC53806H38RS948DPN9H", + "kind": "memory", + "score": 0.9493880867958068, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 980.3764, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3455, + "mcp_result_bytes": 3618, + "wire_bytes": 3654, + "reported_used_tokens": 3618, + "working_set_bytes": 270262272, + "peak_working_set_bytes": 271179776 + }, + { + "query": "BridgeTarget enum seams plugin_install_inner plugin_status_inner resolve_setup_hosts", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYK4YHQ3NZXK16HEAQYY", + "id": "01M1X0SD3B73JRWX1GR5STASQ4", + "kind": "memory", + "score": 0.9997583031654358, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 851.5373000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1060, + "mcp_result_bytes": 1141, + "wire_bytes": 1177, + "reported_used_tokens": 1141, + "working_set_bytes": 280018944, + "peak_working_set_bytes": 280932352 + }, + { + "query": "I added a new host to the bridge enum but cargo gives me compile errors in five different match arms \u2014 what did I miss?", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYK4YHQ3NZXK16HEAQYY", + "id": "01M1X0SDYN3VHQT9W4XS9JJ9AP", + "kind": "memory", + "score": 0.9977060556411744, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1092.99, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1059, + "mcp_result_bytes": 1140, + "wire_bytes": 1176, + "reported_used_tokens": 1140, + "working_set_bytes": 280649728, + "peak_working_set_bytes": 281563136 + }, + { + "query": "Pi extension factory defineExtension agent_end session_shutdown kimetsu.ts", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYMH38DNR143J812N83T", + "id": "01M1X0SF0H3WFC0NCQWKS29VHN", + "kind": "memory", + "score": 0.9990354776382446, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1048.9435, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 804, + "mcp_result_bytes": 893, + "wire_bytes": 929, + "reported_used_tokens": 893, + "working_set_bytes": 280858624, + "peak_working_set_bytes": 281772032 + }, + { + "query": "how does Pi (earendil-works/pi) load plugins and what lifecycle hooks does it expose?", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYMH38DNR143J812N83T", + "id": "01M1X0SG13PB7B63WPEDPC9Y3B", + "kind": "memory", + "score": 0.9934834837913512, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1078.0985, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 803, + "mcp_result_bytes": 892, + "wire_bytes": 928, + "reported_used_tokens": 892, + "working_set_bytes": 281088000, + "peak_working_set_bytes": 281997312 + }, + { + "query": "aws-sigv4 SigningParams apply_to_request_http1x reqwest sign-http", + "ranked": [ + "aws-sigv4-bedrock-blocking", + "aws-presigned-urls", + "bedrock-kimetsu-provider", + "aws-credentials-chain" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYQNNBEV2TCA2CR5B3P8", + "id": "01M1X0SH2RMV36XBQ29KS8S0DS", + "kind": "memory", + "score": 0.9995608925819396, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1X0S23B6NYNB9ZTRCDV5CN8", + "id": "01M1X0SH2RFKFK1YWYD0TH9XGN", + "kind": "memory", + "score": 0.984916627407074, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + }, + { + "expansion_handle": "memory:01M1X0RYH0B8FECY7X9RAA8ZWE", + "id": "01M1X0SH2RVH3EAKMFXYWAW8XZ", + "kind": "memory", + "score": 0.983895778656006, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X0S1ZFY1N7K6ABQSCYNE24", + "id": "01M1X0SH2RFRKQCQMT00JGT2P5", + "kind": "memory", + "score": 0.8592692017555237, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 837.8970999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3507, + "mcp_result_bytes": 3670, + "wire_bytes": 3706, + "reported_used_tokens": 3670, + "working_set_bytes": 281169920, + "peak_working_set_bytes": 282071040 + }, + { + "query": "how do I sign a Bedrock InvokeModel request with aws-sigv4 in blocking Rust?", + "ranked": [ + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider", + "aws-region-resolution", + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYQNNBEV2TCA2CR5B3P8", + "id": "01M1X0SHX56DDCPSRH2DN4EC8C", + "kind": "memory", + "score": 0.9998323917388916, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1X0RYH0B8FECY7X9RAA8ZWE", + "id": "01M1X0SHX53MJ1734NQNZCWKSX", + "kind": "memory", + "score": 0.9970844388008118, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X0S20Z7NDCY69PTPT27MYZ", + "id": "01M1X0SHX54W7181DK0QDGN42K", + "kind": "memory", + "score": 0.9468621611595154, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X0S23B6NYNB9ZTRCDV5CN8", + "id": "01M1X0SHX59CM7WYAG4W6SQBA4", + "kind": "memory", + "score": 0.9210098385810852, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1003.6194, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3434, + "mcp_result_bytes": 3597, + "wire_bytes": 3633, + "reported_used_tokens": 3597, + "working_set_bytes": 281190400, + "peak_working_set_bytes": 282107904 + }, + { + "query": "KIMETSU_RUNS_GC env opt-out TraceWriter create gc_old_runs caller", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYT4XV75C03N3X303GA9", + "id": "01M1X0SJXD5WCC7W2T3G3T77SN", + "kind": "memory", + "score": 0.999936580657959, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 985.9491, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 761, + "mcp_result_bytes": 842, + "wire_bytes": 878, + "reported_used_tokens": 842, + "working_set_bytes": 281235456, + "peak_working_set_bytes": 282148864 + }, + { + "query": "where should I put the KIMETSU_RUNS_GC=0 guard \u2014 inside the GC function or at the call site?", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYT4XV75C03N3X303GA9", + "id": "01M1X0SKVWXFYQJ1GJVX118KYB", + "kind": "memory", + "score": 0.9971211552619934, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1063.5295, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 762, + "mcp_result_bytes": 843, + "wire_bytes": 879, + "reported_used_tokens": 843, + "working_set_bytes": 281563136, + "peak_working_set_bytes": 282484736 + }, + { + "query": "git_init_boundary ProjectPaths::discover temp dir user brain isolation", + "ranked": [ + "init-project-git-boundary", + "git-worktree-brain-isolation", + "testing-temp-dirs-ci", + "kimetsu-memory-scopes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYV389BQAB7HQN0MA99V", + "id": "01M1X0SMWDEXJWHF5SC8ZB5H3X", + "kind": "memory", + "score": 0.9997712969779968, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + }, + { + "expansion_handle": "memory:01M1X0S0N2RNCRZ1Z1FT2DDS51", + "id": "01M1X0SMWD77YZ3NFWE3DJMZ1Q", + "kind": "memory", + "score": 0.9962491393089294, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root \u2014 if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + }, + { + "expansion_handle": "memory:01M1X0S1FN8CE8RYDY1CWHJXT3", + "id": "01M1X0SMWDT3T8KS2FVY935Q1W", + "kind": "memory", + "score": 0.9682154655456544, + "summary": "project:fact - [tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure." + }, + { + "expansion_handle": "memory:01M1X0S2G2687MBFR5WRACKDHK", + "id": "01M1X0SMWDYN4WKXZXRVP9Q4ZY", + "kind": "memory", + "score": 0.3057229816913605, + "summary": "project:fact - [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available \u2014 if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 904.448, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2580, + "mcp_result_bytes": 2715, + "wire_bytes": 2751, + "reported_used_tokens": 2715, + "working_set_bytes": 281853952, + "peak_working_set_bytes": 282767360 + }, + { + "query": "my test calls init_project but it writes to the real ~/.kimetsu instead of the temp folder \u2014 why?", + "ranked": [ + "init-project-git-boundary", + "cargo-feature-unification-embeddings", + "testing-fixture-drift", + "tokio-runtime-in-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYV389BQAB7HQN0MA99V", + "id": "01M1X0SNRVKBGK8QZXY42WDPNS", + "kind": "memory", + "score": 0.9995088577270508, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + }, + { + "expansion_handle": "memory:01M1X0RYFCG3DKA4C6N43J3PQW", + "id": "01M1X0SNRV7Z62S3FVVQ353XJ4", + "kind": "memory", + "score": 0.7287850975990295, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X0S1Q0NEBQJSWTFZPHE4ZT", + "id": "01M1X0SNRVFQWANCJR2V4SA9Q7", + "kind": "memory", + "score": 0.6596062183380127, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + }, + { + "expansion_handle": "memory:01M1X0S0XTZQGDG2VGR6DYQBX7", + "id": "01M1X0SNRVPF29C3Z10NPS15XF", + "kind": "memory", + "score": 0.3297702968120575, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1047.1725000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2833, + "mcp_result_bytes": 2980, + "wire_bytes": 3016, + "reported_used_tokens": 2980, + "working_set_bytes": 282365952, + "peak_working_set_bytes": 283279360 + }, + { + "query": "clap command version KIMETSU_VERSION_DISPLAY cfg feature embeddings", + "ranked": [ + "clap-version-build-flavor", + "cargo-feature-unification-embeddings" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYW44BMZQY7WYDDH5B2Z", + "id": "01M1X0SPSGX2PN0HZN90DQCESF", + "kind": "memory", + "score": 0.9996613264083862, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + }, + { + "expansion_handle": "memory:01M1X0RYFCG3DKA4C6N43J3PQW", + "id": "01M1X0SPSGWWKYN8WHX8522PSS", + "kind": "memory", + "score": 0.3973360061645508, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 848.0634, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1922, + "mcp_result_bytes": 2041, + "wire_bytes": 2077, + "reported_used_tokens": 2041, + "working_set_bytes": 282402816, + "peak_working_set_bytes": 283308032 + }, + { + "query": "how do I show the build flavor (lean vs embeddings) in the kimetsu --version output?", + "ranked": [ + "clap-version-build-flavor", + "cargo-feature-unification-embeddings", + "onnx-quantization-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYW44BMZQY7WYDDH5B2Z", + "id": "01M1X0SQMEP9512DNWRBF8VGX3", + "kind": "memory", + "score": 0.9978312849998474, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + }, + { + "expansion_handle": "memory:01M1X0RYFCG3DKA4C6N43J3PQW", + "id": "01M1X0SQMES1Q23V6SRJXTGMA1", + "kind": "memory", + "score": 0.8926984667778015, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X0S0A67XVTPTF2148D1KT6", + "id": "01M1X0SQMEE38E2DSK7YP5QJ1Y", + "kind": "memory", + "score": 0.8877003192901611, + "summary": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals \u2014 cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1076.5962, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2672, + "mcp_result_bytes": 2809, + "wire_bytes": 2845, + "reported_used_tokens": 2809, + "working_set_bytes": 282521600, + "peak_working_set_bytes": 283439104 + }, + { + "query": "Harbor pyiceberg os.getcwd stale WSL2 DrvFs worker-result subprocess re-exec", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYXPEWKAXHHVCP4PE2J9", + "id": "01M1X0SRNYSMAF4FX58TNKG29W", + "kind": "memory", + "score": 0.9998155236244202, + "summary": "project:fact - [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1078.8982, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1026, + "mcp_result_bytes": 1107, + "wire_bytes": 1143, + "reported_used_tokens": 1107, + "working_set_bytes": 282787840, + "peak_working_set_bytes": 283697152 + }, + { + "query": "why does my kbench sweep crash after the first trial with 'result.json missing' on WSL2?", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYXPEWKAXHHVCP4PE2J9", + "id": "01M1X0SSQQ7YSKQW08MSY0TNFR", + "kind": "memory", + "score": 0.998451828956604, + "summary": "project:fact - [2026-09-07] [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1108.0049999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1038, + "mcp_result_bytes": 1119, + "wire_bytes": 1155, + "reported_used_tokens": 1119, + "working_set_bytes": 283291648, + "peak_working_set_bytes": 284213248 + }, + { + "query": "rusqlite VACUUM transaction WAL checkpoint wal_checkpoint TRUNCATE", + "ranked": [ + "sqlite-vacuum-wal-checkpoint", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYZAKDZ57SBSECE2T8MD", + "id": "01M1X0STTK402T513BKZE0QPQE", + "kind": "memory", + "score": 0.9996871948242188, + "summary": "project:fact - [tags: rust sqlite vacuum rusqlite windows] When implementing SQLite VACUUM in rusqlite: VACUUM cannot run inside a transaction. rusqlite's Connection does not hold an implicit transaction, so `conn.execute_batch(\"VACUUM;\")` works directly. After VACUUM, run `PRAGMA wal_checkpoint(TRUNCATE);` before measuring file size \u2014 on Windows the WAL file can hold significant space that isn't reflected in the main db file until the checkpoint runs." + }, + { + "expansion_handle": "memory:01M1X0RZ695HPYBS44T4Z7G5RR", + "id": "01M1X0STTKSE4NKF07ESA0GGCW", + "kind": "memory", + "score": 0.5274003744125366, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 869.5553, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1507, + "mcp_result_bytes": 1610, + "wire_bytes": 1646, + "reported_used_tokens": 1610, + "working_set_bytes": 283320320, + "peak_working_set_bytes": 284221440 + }, + { + "query": "my SQLite VACUUM reports the file shrank but the disk usage stayed the same \u2014 Windows WAL?", + "ranked": [ + "sqlite-vacuum-wal-checkpoint", + "sqlite-wal-network-drive" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYZAKDZ57SBSECE2T8MD", + "id": "01M1X0SVPH8W6KE2CHGKFM5SN2", + "kind": "memory", + "score": 0.9155893921852112, + "summary": "project:fact - [tags: rust sqlite vacuum rusqlite windows] When implementing SQLite VACUUM in rusqlite: VACUUM cannot run inside a transaction. rusqlite's Connection does not hold an implicit transaction, so `conn.execute_batch(\"VACUUM;\")` works directly. After VACUUM, run `PRAGMA wal_checkpoint(TRUNCATE);` before measuring file size \u2014 on Windows the WAL file can hold significant space that isn't reflected in the main db file until the checkpoint runs." + }, + { + "expansion_handle": "memory:01M1X0RZ9WFMZG0GGATBEZ1Z23", + "id": "01M1X0SVPHVBDKW14Z0TGAGZZ2", + "kind": "memory", + "score": 0.902395486831665, + "summary": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1151.2273, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1357, + "mcp_result_bytes": 1460, + "wire_bytes": 1496, + "reported_used_tokens": 1460, + "working_set_bytes": 283856896, + "peak_working_set_bytes": 284774400 + }, + { + "query": "add_memory import dedup seen_ids snapshot pre-existing active memory IDs", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZ07DDZS1S2W366VR4AF", + "id": "01M1X0SWSNJXX0ZTTTZJNF2HTH", + "kind": "memory", + "score": 0.9999133348464966, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount \u2014 both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 949.3277, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 966, + "mcp_result_bytes": 1047, + "wire_bytes": 1083, + "reported_used_tokens": 1047, + "working_set_bytes": 284114944, + "peak_working_set_bytes": 285024256 + }, + { + "query": "brain import re-imports the same JSON file but the deduplication counter is wrong \u2014 why?", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZ07DDZS1S2W366VR4AF", + "id": "01M1X0SXQ02A0AEWB85CVW1NC2", + "kind": "memory", + "score": 0.9254016876220704, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount \u2014 both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 989.0221, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 965, + "mcp_result_bytes": 1046, + "wire_bytes": 1082, + "reported_used_tokens": 1046, + "working_set_bytes": 284430336, + "peak_working_set_bytes": 285347840 + }, + { + "query": "toml::from_str Value parse document unexpected content str.parse", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZ1GHPSENE70BT6R9EP6", + "id": "01M1X0SYNVDTSPKAT8KQ85BD1Y", + "kind": "memory", + "score": 0.9991866946220398, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 861.8758, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 734, + "mcp_result_bytes": 815, + "wire_bytes": 851, + "reported_used_tokens": 815, + "working_set_bytes": 284512256, + "peak_working_set_bytes": 285425664 + }, + { + "query": "how do I parse a TOML configuration file into a toml::Value in toml 0.9?", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZ1GHPSENE70BT6R9EP6", + "id": "01M1X0SZH7PNM0NTZ13RME8428", + "kind": "memory", + "score": 0.9992641806602478, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1018.8482999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 733, + "mcp_result_bytes": 814, + "wire_bytes": 850, + "reported_used_tokens": 814, + "working_set_bytes": 284557312, + "peak_working_set_bytes": 285470720 + }, + { + "query": "CIM CreationDate DMTF WMI ps etimes started_at assess_mcp_skew", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZ2JFJPBW9BYZQM4ADFY", + "id": "01M1X0T0GXV95BF5BYJ2687PST", + "kind": "memory", + "score": 0.9957948923110962, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 814.3071, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 924, + "mcp_result_bytes": 1013, + "wire_bytes": 1049, + "reported_used_tokens": 1013, + "working_set_bytes": 284606464, + "peak_working_set_bytes": 285511680 + }, + { + "query": "how do I read a process start time on both Windows and Linux in pure Rust?", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZ2JFJPBW9BYZQM4ADFY", + "id": "01M1X0T1ABXVZPV1D2VTY5G6N5", + "kind": "memory", + "score": 0.99687659740448, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1017.4766, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 921, + "mcp_result_bytes": 1010, + "wire_bytes": 1046, + "reported_used_tokens": 1010, + "working_set_bytes": 285032448, + "peak_working_set_bytes": 285958144 + }, + { + "query": "processes_locking_target decide_preflight_action BufRead Write update.rs", + "ranked": [ + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZ3Y82J7DVBNF9HN837A", + "id": "01M1X0T2A8SH2X5BHER4GJX16E", + "kind": "memory", + "score": 0.9995336532592772, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 878.4714, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1133, + "mcp_result_bytes": 1214, + "wire_bytes": 1250, + "reported_used_tokens": 1214, + "working_set_bytes": 285089792, + "peak_working_set_bytes": 286007296 + }, + { + "query": "how should I reuse the existing process enumerator in the update preflight check to avoid a second PowerShell query?", + "ranked": [ + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZ3Y82J7DVBNF9HN837A", + "id": "01M1X0T35PXTXGPJMGMBV831NW", + "kind": "memory", + "score": 0.9973384737968444, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1076.8553000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1132, + "mcp_result_bytes": 1213, + "wire_bytes": 1249, + "reported_used_tokens": 1213, + "working_set_bytes": 285429760, + "peak_working_set_bytes": 286347264 + }, + { + "query": "cfg_attr windows allow dead_code parse_unix_ps cross-platform tests", + "ranked": [ + "cfg-cross-platform-dead-code", + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZ574DS0ZYNTP7YS68N9", + "id": "01M1X0T47T9JD52HTX94438ZDH", + "kind": "memory", + "score": 0.9999476671218872, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + }, + { + "expansion_handle": "memory:01M1X0RZ2JFJPBW9BYZQM4ADFY", + "id": "01M1X0T47TF75W1DANFBA0GRT5", + "kind": "memory", + "score": 0.9764312505722046, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 863.0550000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1518, + "mcp_result_bytes": 1625, + "wire_bytes": 1661, + "reported_used_tokens": 1625, + "working_set_bytes": 285429760, + "peak_working_set_bytes": 286347264 + }, + { + "query": "how do I keep a function that is only called on Unix from triggering dead_code warnings on Windows?", + "ranked": [ + "cfg-cross-platform-dead-code" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZ574DS0ZYNTP7YS68N9", + "id": "01M1X0T52A3FHGE01HESJ78G3W", + "kind": "memory", + "score": 0.9988092184066772, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 998.8763, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 939, + "reported_used_tokens": 903, + "working_set_bytes": 285839360, + "peak_working_set_bytes": 286752768 + }, + { + "query": "deadlocking a Rust mutex in integration tests", + "ranked": [ + "mutex-deadlock-user-brain-disabled", + "testing-serial-vs-parallel", + "kimetsu-query-stemming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RY96B4DJ5PBZXCAERB1C", + "id": "01M1X0T61HRS1T0FVZ78AG0JZ7", + "kind": "memory", + "score": 0.9997490048408508, + "summary": "project:fact - [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure \u2014 `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + }, + { + "expansion_handle": "memory:01M1X0S1KD82X952R9BGBPS244", + "id": "01M1X0T61H5G49V61ZYAB1PJMP", + "kind": "memory", + "score": 0.9057517647743224, + "summary": "project:fact - [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`)." + }, + { + "expansion_handle": "memory:01M1X0S2PZHHMGHW25YMR7CTJ2", + "id": "01M1X0T61H0C0SCT97A39V2MPM", + "kind": "memory", + "score": 0.4889622032642365, + "summary": "project:fact - [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 959.1279999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1930, + "mcp_result_bytes": 2063, + "wire_bytes": 2099, + "reported_used_tokens": 2063, + "working_set_bytes": 285847552, + "peak_working_set_bytes": 286760960 + }, + { + "query": "benchmarking retrieval quality across embedders", + "ranked": [ + "kimetsu-bench-remote-embedder-singleton", + "onnx-quantization-drift", + "cargo-feature-unification-embeddings" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S2SFXFB41K3BPST4ACAR", + "id": "01M1X0T6ZFGG0KSDP5J0FTDJ54", + "kind": "memory", + "score": 0.988014280796051, + "summary": "project:fact - [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval." + }, + { + "expansion_handle": "memory:01M1X0S0A67XVTPTF2148D1KT6", + "id": "01M1X0T6ZFY9ZAXP7H1C09EH02", + "kind": "memory", + "score": 0.985597550868988, + "summary": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals \u2014 cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + }, + { + "expansion_handle": "memory:01M1X0RYFCG3DKA4C6N43J3PQW", + "id": "01M1X0T6ZFM1TXXS41N1VYSV9Z", + "kind": "memory", + "score": 0.5341982841491699, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 836.8882, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2537, + "mcp_result_bytes": 2658, + "wire_bytes": 2694, + "reported_used_tokens": 2658, + "working_set_bytes": 285863936, + "peak_working_set_bytes": 286773248 + }, + { + "query": "process memory working set RSS peak measurement Windows", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1016.9802999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 286015488, + "peak_working_set_bytes": 286908416 + }, + { + "query": "cloning a git repository server-side into a managed checkout", + "ranked": [ + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYAH80HT0J4XD4TW3J26", + "id": "01M1X0T8SCHMKPFP0P4P5FMGF3", + "kind": "memory", + "score": 0.9466677904129028, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 871.4755, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1261, + "mcp_result_bytes": 1342, + "wire_bytes": 1378, + "reported_used_tokens": 1342, + "working_set_bytes": 286019584, + "peak_working_set_bytes": 286924800 + }, + { + "query": "SigV4 signing HTTP requests in Rust", + "ranked": [ + "aws-presigned-urls", + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S23B6NYNB9ZTRCDV5CN8", + "id": "01M1X0T9MJJWSBAZKN66TCHRQ6", + "kind": "memory", + "score": 0.9992632269859314, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + }, + { + "expansion_handle": "memory:01M1X0RYQNNBEV2TCA2CR5B3P8", + "id": "01M1X0T9MJ4CZEAREYDS6JN7NS", + "kind": "memory", + "score": 0.9991399049758912, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1X0RYH0B8FECY7X9RAA8ZWE", + "id": "01M1X0T9MJJ7430DMZPNPKKEZN", + "kind": "memory", + "score": 0.9803794622421264, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 0.5, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 947.644, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2840, + "mcp_result_bytes": 2985, + "wire_bytes": 3021, + "reported_used_tokens": 2985, + "working_set_bytes": 286375936, + "peak_working_set_bytes": 287272960 + }, + { + "query": "cargo test --workspace feature flag changes broke my unit tests", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-dev-dep-leak", + "ci-flaky-quarantine" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYFCG3DKA4C6N43J3PQW", + "id": "01M1X0TAJB8NDHHG653DSF8PWX", + "kind": "memory", + "score": 0.997899889945984, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X0RZN0RW2M8XPRGDVPM9FC", + "id": "01M1X0TAJB8W87NGARFABX89BE", + "kind": "memory", + "score": 0.9901249408721924, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + }, + { + "expansion_handle": "memory:01M1X0S2CGT9TN4Z78Y5TBVQ41", + "id": "01M1X0TAJB7PF15Q8TQEVBJPCP", + "kind": "memory", + "score": 0.835382342338562, + "summary": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal \u2014 a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 875.9382999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2383, + "mcp_result_bytes": 2504, + "wire_bytes": 2540, + "reported_used_tokens": 2504, + "working_set_bytes": 286396416, + "peak_working_set_bytes": 287309824 + }, + { + "query": "how do I make pasta carbonara?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 931.8514, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 286404608, + "peak_working_set_bytes": 287318016 + }, + { + "query": "what is the offside rule in football?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 1099.0477, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 286896128, + "peak_working_set_bytes": 287805440 + }, + { + "query": "best way to train for a half marathon", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 1107.1618999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 286949376, + "peak_working_set_bytes": 287870976 + }, + { + "query": "my test passes when I run it alone but fails under cargo test --workspace", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYFCG3DKA4C6N43J3PQW", + "id": "01M1X0TEFZX5Z8QP3133W0YQZF", + "kind": "memory", + "score": 0.9907942414283752, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X0RZN0RW2M8XPRGDVPM9FC", + "id": "01M1X0TEFZN3T49ZWSBCWBSPJ5", + "kind": "memory", + "score": 0.986136794090271, + "summary": "project:fact - [2026-09-07] [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1052.7636, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1863, + "mcp_result_bytes": 1966, + "wire_bytes": 2002, + "reported_used_tokens": 1966, + "working_set_bytes": 287432704, + "peak_working_set_bytes": 288350208 + }, + { + "query": "all the project tests started hanging forever after I added my new test", + "ranked": [ + "cargo-feature-unification-embeddings", + "tokio-runtime-in-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYFCG3DKA4C6N43J3PQW", + "id": "01M1X0TFH6PRJT9YNHDCACNNM6", + "kind": "memory", + "score": 0.774284839630127, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X0S0XTZQGDG2VGR6DYQBX7", + "id": "01M1X0TFH6Y94X5JGWKQ18YJPW", + "kind": "memory", + "score": 0.33030807971954346, + "summary": "project:fact - [2026-09-07] [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1016.1251, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1763, + "mcp_result_bytes": 1874, + "wire_bytes": 1910, + "reported_used_tokens": 1874, + "working_set_bytes": 287432704, + "peak_working_set_bytes": 288350208 + }, + { + "query": "my integration test silently wrote memories into my real home brain instead of the temp workspace", + "ranked": [ + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYV389BQAB7HQN0MA99V", + "id": "01M1X0TGGHSKE122XQCQNC8FYS", + "kind": "memory", + "score": 0.9922831654548644, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 982.2897, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 780, + "mcp_result_bytes": 861, + "wire_bytes": 897, + "reported_used_tokens": 861, + "working_set_bytes": 287469568, + "peak_working_set_bytes": 288387072 + }, + { + "query": "where should the env-var opt-out check live for a cleanup feature triggered from a hot code path", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYT4XV75C03N3X303GA9", + "id": "01M1X0THFACC2DWAR8R2674WAY", + "kind": "memory", + "score": 0.9952055215835572, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1054.4488999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 761, + "mcp_result_bytes": 842, + "wire_bytes": 878, + "reported_used_tokens": 842, + "working_set_bytes": 287473664, + "peak_working_set_bytes": 288391168 + }, + { + "query": "the brain database file stays huge on Windows even after deleting most rows", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1029.6322, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 287506432, + "peak_working_set_bytes": 288428032 + }, + { + "query": "re-importing the same exported memories file counts them as new instead of deduplicated", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZ07DDZS1S2W366VR4AF", + "id": "01M1X0TKGBTW3K78DDWN8Q4JG7", + "kind": "memory", + "score": 0.9878425598144532, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount \u2014 both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1020.7324, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 965, + "mcp_result_bytes": 1046, + "wire_bytes": 1082, + "reported_used_tokens": 1046, + "working_set_bytes": 287506432, + "peak_working_set_bytes": 288428032 + }, + { + "query": "a helper function only called on Unix at runtime fails the dead-code lint on the Windows build", + "ranked": [ + "cfg-cross-platform-dead-code", + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZ574DS0ZYNTP7YS68N9", + "id": "01M1X0TMGDQGN4BM8AG5ZDTQ9V", + "kind": "memory", + "score": 0.9971064925193788, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + }, + { + "expansion_handle": "memory:01M1X0RZ3Y82J7DVBNF9HN837A", + "id": "01M1X0TMGD2A3SFY5Y5NFWAMC1", + "kind": "memory", + "score": 0.427912950515747, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1005.6022, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1728, + "mcp_result_bytes": 1827, + "wire_bytes": 1863, + "reported_used_tokens": 1827, + "working_set_bytes": 287531008, + "peak_working_set_bytes": 288452608 + }, + { + "query": "the second Terminal-Bench trial always crashes even though the first one passes", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYXPEWKAXHHVCP4PE2J9", + "id": "01M1X0TNGNADNSVS0VZ51F9RQJ", + "kind": "memory", + "score": 0.9963042736053468, + "summary": "project:fact - [2026-09-07] [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1082.7319, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1038, + "mcp_result_bytes": 1119, + "wire_bytes": 1155, + "reported_used_tokens": 1119, + "working_set_bytes": 287531008, + "peak_working_set_bytes": 288452608 + }, + { + "query": "how does doctor tell a running MCP server process is older than the kimetsu binary on disk", + "ranked": [ + "kimetsu-daemon-lifecycle", + "process-start-time-cross-platform", + "mcp-env-propagation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S2DSSS5EEX6KZFYDRRNA", + "id": "01M1X0TPJE6K9CYQV8ZD1785D7", + "kind": "memory", + "score": 0.9985345602035522, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1X0RZ2JFJPBW9BYZQM4ADFY", + "id": "01M1X0TPJE6CFMNJ2C9HH2K03Y", + "kind": "memory", + "score": 0.9438157677650452, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + }, + { + "expansion_handle": "memory:01M1X0S1TJ6VGH2S8KRTZQJT93", + "id": "01M1X0TPJEDCES2G3CZR7C9B51", + "kind": "memory", + "score": 0.33611738681793213, + "summary": "project:fact - [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment \u2014 changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 0.5, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1060.8912, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1936, + "mcp_result_bytes": 2061, + "wire_bytes": 2097, + "reported_used_tokens": 2061, + "working_set_bytes": 287531008, + "peak_working_set_bytes": 288452608 + }, + { + "query": "the self-update preflight needs the list of running kimetsu processes without re-running the OS query", + "ranked": [ + "windows-update-process-locking", + "kimetsu-daemon-lifecycle" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZ3Y82J7DVBNF9HN837A", + "id": "01M1X0TQJZYKHMZR294302B543", + "kind": "memory", + "score": 0.9972410202026368, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + }, + { + "expansion_handle": "memory:01M1X0S2DSSS5EEX6KZFYDRRNA", + "id": "01M1X0TQJZ3RMBQZZ4E5F3E3RD", + "kind": "memory", + "score": 0.8902595043182373, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1006.0351999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1658, + "mcp_result_bytes": 1757, + "wire_bytes": 1793, + "reported_used_tokens": 1757, + "working_set_bytes": 287531008, + "peak_working_set_bytes": 288452608 + }, + { + "query": "parsing the WMI DMTF CreationDate timestamp into epoch seconds without extra crates", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZ2JFJPBW9BYZQM4ADFY", + "id": "01M1X0TRJCHHFF26F4HHA2PXZW", + "kind": "memory", + "score": 0.9258026480674744, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1050.4435, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 924, + "mcp_result_bytes": 1013, + "wire_bytes": 1049, + "reported_used_tokens": 1013, + "working_set_bytes": 287674368, + "peak_working_set_bytes": 288587776 + }, + { + "query": "calling Bedrock InvokeModel from blocking reqwest without the aws sdk", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking", + "aws-region-resolution", + "aws-retry-throttling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYH0B8FECY7X9RAA8ZWE", + "id": "01M1X0TSK29KGVK8R7FEZQ2804", + "kind": "memory", + "score": 0.9991798996925354, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X0RYQNNBEV2TCA2CR5B3P8", + "id": "01M1X0TSK22RFANKEESR2NPX5W", + "kind": "memory", + "score": 0.999082326889038, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1X0S20Z7NDCY69PTPT27MYZ", + "id": "01M1X0TSK2J5SPX83PQ3ZK9DDB", + "kind": "memory", + "score": 0.8391201496124268, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X0S224RJWKJRAY1BV4VMM1", + "id": "01M1X0TSK2BM4S0FERTTPA180W", + "kind": "memory", + "score": 0.4906356632709503, + "summary": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with \u00b125% jitter." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1014.129, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3330, + "mcp_result_bytes": 3509, + "wire_bytes": 3545, + "reported_used_tokens": 3509, + "working_set_bytes": 287924224, + "peak_working_set_bytes": 288841728 + }, + { + "query": "how do I rotate the encryption key protecting the kimetsu brain database", + "ranked": [ + "kimetsu-eval-fixture-shape" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S2TQ6WSXCCQJX516YNBR", + "id": "01M1X0TTJWJTYTSBM6E9QMBWRD", + "kind": "memory", + "score": 0.8046634197235107, + "summary": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` \u2014 a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases)." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 1037.6102, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 817, + "mcp_result_bytes": 942, + "wire_bytes": 978, + "reported_used_tokens": 942, + "working_set_bytes": 287961088, + "peak_working_set_bytes": 288870400 + }, + { + "query": "which tokio runtime worker-thread settings does the kimetsu MCP server use", + "ranked": [ + "tokio-blocking-in-async", + "tokio-runtime-in-tests", + "mcp-stdout-protocol" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S0WE8VV1EREBQ8JXTVHF", + "id": "01M1X0TVKFH37M3VZQ53KD93AC", + "kind": "memory", + "score": 0.9973159432411194, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + }, + { + "expansion_handle": "memory:01M1X0S0XTZQGDG2VGR6DYQBX7", + "id": "01M1X0TVKFYBZ2YPC3P6CFT80G", + "kind": "memory", + "score": 0.8583173155784607, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + }, + { + "expansion_handle": "memory:01M1X0S1R25W8ZJDGRFWG990X7", + "id": "01M1X0TVKG6DRPZRQW3QCPV7CV", + "kind": "memory", + "score": 0.8141786456108093, + "summary": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 1058.8731, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1847, + "mcp_result_bytes": 1972, + "wire_bytes": 2008, + "reported_used_tokens": 1972, + "working_set_bytes": 287997952, + "peak_working_set_bytes": 288911360 + }, + { + "query": "how does kimetsu sync memories between two machines over the network", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 974.6616, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288014336, + "peak_working_set_bytes": 288927744 + }, + { + "query": "recovering a corrupted usearch ANN index after a power loss", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 944.0717, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 287956992, + "peak_working_set_bytes": 288927744 + }, + { + "query": "what postgres schema should I use to store kimetsu memories", + "ranked": [ + "kimetsu-memory-scopes", + "testing-fixture-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S2G2687MBFR5WRACKDHK", + "id": "01M1X0TYGW768XTZKKXJBE82FW", + "kind": "memory", + "score": 0.9890244603157043, + "summary": "project:fact - [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available \u2014 if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope." + }, + { + "expansion_handle": "memory:01M1X0S1Q0NEBQJSWTFZPHE4ZT", + "id": "01M1X0TYGWSC35X9VZGKBJKSRS", + "kind": "memory", + "score": 0.8922504782676697, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 1029.2843, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1389, + "mcp_result_bytes": 1488, + "wire_bytes": 1524, + "reported_used_tokens": 1488, + "working_set_bytes": 288350208, + "peak_working_set_bytes": 289251328 + }, + { + "query": "the whole CI job just froze forever with no failure output after my latest test PR", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1037.0723, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288354304, + "peak_working_set_bytes": 289271808 + }, + { + "query": "running the test suite left junk state in my home directory", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1054.5667999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288358400, + "peak_working_set_bytes": 289275904 + }, + { + "query": "I deleted a bunch of old rows but the file on disk is still the same size", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1010.0461, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288358400, + "peak_working_set_bytes": 289275904 + }, + { + "query": "adding one new crate quietly changed how the whole workspace builds", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-lockfile-drift", + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYFCG3DKA4C6N43J3PQW", + "id": "01M1X0V2HJM8X7160SATDBVKPQ", + "kind": "memory", + "score": 0.9941080808639526, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X0RZJGQK2RS7B2P2YYBFFY", + "id": "01M1X0V2HJGYQMQ71K6050J63K", + "kind": "memory", + "score": 0.9717232584953308, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this \u2014 it errors on any lockfile diff." + }, + { + "expansion_handle": "memory:01M1X0RZN0RW2M8XPRGDVPM9FC", + "id": "01M1X0V2HJ3C1HJ0S30SB9K9EP", + "kind": "memory", + "score": 0.9183088541030884, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1031.2650999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2374, + "mcp_result_bytes": 2495, + "wire_bytes": 2531, + "reported_used_tokens": 2495, + "working_set_bytes": 288358400, + "peak_working_set_bytes": 289280000 + }, + { + "query": "we cannot pull an async runtime into the agent just to talk to AWS", + "ranked": [ + "tokio-blocking-in-async", + "tokio-runtime-in-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S0WE8VV1EREBQ8JXTVHF", + "id": "01M1X0V3J0EWBEXRYR9MD341BH", + "kind": "memory", + "score": 0.7520647644996643, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + }, + { + "expansion_handle": "memory:01M1X0S0XTZQGDG2VGR6DYQBX7", + "id": "01M1X0V3J0JC8B8Q3GARMVM885", + "kind": "memory", + "score": 0.7233642935752869, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1070.7431000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1369, + "mcp_result_bytes": 1476, + "wire_bytes": 1512, + "reported_used_tokens": 1476, + "working_set_bytes": 288358400, + "peak_working_set_bytes": 289280000 + }, + { + "query": "users should be able to tell which build variant they installed from the version output", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1029.2685999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288358400, + "peak_working_set_bytes": 289280000 + }, + { + "query": "what gotchas should I expect writing process-inspection code that works on both Windows and Unix?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 985.4351, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288358400, + "peak_working_set_bytes": 289280000 + }, + { + "query": "why might tests behave differently on my machine than in the full CI run?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1001.3942, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288362496, + "peak_working_set_bytes": 289280000 + }, + { + "query": "what do I need to know before wiring kimetsu into a brand new host agent?", + "ranked": [ + "bridge-target-enum-seams", + "kimetsu-daemon-lifecycle", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYK4YHQ3NZXK16HEAQYY", + "id": "01M1X0V7HZCBWZ6EY51J82B04M", + "kind": "memory", + "score": 0.9741999506950378, + "summary": "project:fact - [2026-09-07] [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + }, + { + "expansion_handle": "memory:01M1X0S2DSSS5EEX6KZFYDRRNA", + "id": "01M1X0V7J0KX1EFMCKF991DSYH", + "kind": "memory", + "score": 0.9637662768363952, + "summary": "project:fact - [2026-09-07] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1X0RYD13DKH4PFCSN9N3JVS", + "id": "01M1X0V7J030DKY050FYHVX7EM", + "kind": "memory", + "score": 0.4149944484233856, + "summary": "project:fact - [2026-09-07] [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 0.6666666666666666, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1091.2848, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2390, + "mcp_result_bytes": 2555, + "wire_bytes": 2591, + "reported_used_tokens": 2555, + "working_set_bytes": 288485376, + "peak_working_set_bytes": 289402880 + }, + { + "query": "tell me everything relevant to running kimetsu against AWS", + "ranked": [ + "kimetsu-mrr-metric", + "aws-credentials-chain", + "cargo-feature-unification-embeddings", + "kimetsu-eval-fixture-shape" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S2W226CMQKAMAGT925D4", + "id": "01M1X0V8MZPK83Z3CGWT4NJVDN", + "kind": "memory", + "score": 0.984548270702362, + "summary": "project:fact - [tags: kimetsu bench mrr recall metrics evaluation] kimetsu bench reports MRR (Mean Reciprocal Rank) and Recall@K. MRR is 1/rank_of_first_relevant_result, averaged across cases; it penalizes models that rank the correct answer 2nd or 3rd. Recall@K is the fraction of cases where at least one relevant answer appears in the top K." + }, + { + "expansion_handle": "memory:01M1X0S1ZFY1N7K6ABQSCYNE24", + "id": "01M1X0V8MZX39BJF1FBWSR766X", + "kind": "memory", + "score": 0.9737622141838074, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + }, + { + "expansion_handle": "memory:01M1X0RYFCG3DKA4C6N43J3PQW", + "id": "01M1X0V8MZ38YN1VN7NWAS1ST6", + "kind": "memory", + "score": 0.9726329445838928, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X0S2TQ6WSXCCQJX516YNBR", + "id": "01M1X0V8MZ65WFYR64SCHDVNM5", + "kind": "memory", + "score": 0.9641559720039368, + "summary": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` \u2014 a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases)." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1086.608, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2883, + "mcp_result_bytes": 3066, + "wire_bytes": 3102, + "reported_used_tokens": 3066, + "working_set_bytes": 288493568, + "peak_working_set_bytes": 289402880 + }, + { + "query": "ingesting a cloned repo when the brain lives under a different root", + "ranked": [ + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYAH80HT0J4XD4TW3J26", + "id": "01M1X0V9NSDRQ1SB59YZ8WRFME", + "kind": "memory", + "score": 0.9995300769805908, + "summary": "project:fact - [2026-09-07] [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 979.409, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1274, + "mcp_result_bytes": 1355, + "wire_bytes": 1391, + "reported_used_tokens": 1355, + "working_set_bytes": 288497664, + "peak_working_set_bytes": 289406976 + }, + { + "query": "streamable-http transport entry for openclaw.json with a bearer token", + "ranked": [ + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYD13DKH4PFCSN9N3JVS", + "id": "01M1X0VAMGCTW08BJXG5NJZ4Z2", + "kind": "memory", + "score": 0.9921918511390686, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1012.0339, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 996, + "mcp_result_bytes": 1125, + "wire_bytes": 1161, + "reported_used_tokens": 1125, + "working_set_bytes": 288501760, + "peak_working_set_bytes": 289419264 + }, + { + "query": "serializing ingests with a tokio mutex to avoid checkout races", + "ranked": [ + "remote-ingest-split-roots", + "testing-serial-vs-parallel", + "tokio-select-cancellation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYAH80HT0J4XD4TW3J26", + "id": "01M1X0VBM5VY6NGCB3VD7NN3QA", + "kind": "memory", + "score": 0.9795480966567992, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1X0S1KD82X952R9BGBPS244", + "id": "01M1X0VBM516QSHRCN3M2P32JJ", + "kind": "memory", + "score": 0.9425267577171326, + "summary": "project:fact - [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`)." + }, + { + "expansion_handle": "memory:01M1X0S0ZA174TNE45K6MF0TSC", + "id": "01M1X0VBM596DZ0YVKEFB8CZTA", + "kind": "memory", + "score": 0.5619664192199707, + "summary": "project:fact - [tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1047.9504, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2376, + "mcp_result_bytes": 2493, + "wire_bytes": 2529, + "reported_used_tokens": 2493, + "working_set_bytes": 288509952, + "peak_working_set_bytes": 289431552 + }, + { + "query": "percent-encoding the colon in the bedrock model id for the invoke URL", + "ranked": [ + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYH0B8FECY7X9RAA8ZWE", + "id": "01M1X0VCN3MQ3F7ASSJW8MG88D", + "kind": "memory", + "score": 0.8341025710105896, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1008.0223, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1204, + "mcp_result_bytes": 1293, + "wire_bytes": 1329, + "reported_used_tokens": 1293, + "working_set_bytes": 288509952, + "peak_working_set_bytes": 289431552 + }, + { + "query": "deduplicating re-imported memories against pre-existing ids", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZ07DDZS1S2W366VR4AF", + "id": "01M1X0VDME42Q2FQNWMNXPTWSG", + "kind": "memory", + "score": 0.9991393089294434, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount \u2014 both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1053.1853, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 966, + "mcp_result_bytes": 1047, + "wire_bytes": 1083, + "reported_used_tokens": 1047, + "working_set_bytes": 288509952, + "peak_working_set_bytes": 289431552 + }, + { + "query": "parsing DMTF datetimes", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZ2JFJPBW9BYZQM4ADFY", + "id": "01M1X0VEN9WPGYHEEWXADVVEHP", + "kind": "memory", + "score": 0.9934942126274108, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 789.8465, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 924, + "mcp_result_bytes": 1013, + "wire_bytes": 1049, + "reported_used_tokens": 1013, + "working_set_bytes": 288555008, + "peak_working_set_bytes": 289435648 + }, + { + "query": "how should install derive a stable identifier from the git remote URL?", + "ranked": [ + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYD13DKH4PFCSN9N3JVS", + "id": "01M1X0VFE8PYHRJTVE5GQM618Q", + "kind": "memory", + "score": 0.98285174369812, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1010.1580000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 995, + "mcp_result_bytes": 1124, + "wire_bytes": 1160, + "reported_used_tokens": 1124, + "working_set_bytes": 288653312, + "peak_working_set_bytes": 289566720 + }, + { + "query": "the secret token must not end up written into the host config file", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1070.3622, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 228, + "mcp_result_bytes": 291, + "wire_bytes": 327, + "reported_used_tokens": 291, + "working_set_bytes": 288657408, + "peak_working_set_bytes": 289574912 + }, + { + "query": "keep the cleanup logic unit-testable without touching environment variables", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1055.3152, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288661504, + "peak_working_set_bytes": 289574912 + }, + { + "query": "how do we stop the server from cloning arbitrary repos clients request?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 991.9826, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 228, + "mcp_result_bytes": 291, + "wire_bytes": 327, + "reported_used_tokens": 291, + "working_set_bytes": 288661504, + "peak_working_set_bytes": 289579008 + }, + { + "query": "make sure a wrong guess about a host plugin API never breaks that host", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYMH38DNR143J812N83T", + "id": "01M1X0VKF4W7FES291ESBWSH9H", + "kind": "memory", + "score": 0.928434193134308, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 885.3527, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 803, + "mcp_result_bytes": 892, + "wire_bytes": 928, + "reported_used_tokens": 892, + "working_set_bytes": 288661504, + "peak_working_set_bytes": 289583104 + }, + { + "query": "which wire-format trick lets us reuse the existing Anthropic request builder for AWS?", + "ranked": [ + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYH0B8FECY7X9RAA8ZWE", + "id": "01M1X0VMB1Z0N57BH1PACB0WJE", + "kind": "memory", + "score": 0.9748817682266236, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1086.8823, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1203, + "mcp_result_bytes": 1292, + "wire_bytes": 1328, + "reported_used_tokens": 1292, + "working_set_bytes": 288722944, + "peak_working_set_bytes": 289640448 + }, + { + "query": "the self-update froze because something was still holding the executable", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1013.6895999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288739328, + "peak_working_set_bytes": 289648640 + }, + { + "query": "our notes about the extension API turned out wrong once we read the actual repo", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1051.1314, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288759808, + "peak_working_set_bytes": 289669120 + }, + { + "query": "half the benchmark trials die right after the first one finishes", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 984.807, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288763904, + "peak_working_set_bytes": 289681408 + }, + { + "query": "I need this parser visible to tests on every OS even though only one OS calls it", + "ranked": [ + "cfg-cross-platform-dead-code" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZ574DS0ZYNTP7YS68N9", + "id": "01M1X0VRCJ410XZJFRYXYFM7Z4", + "kind": "memory", + "score": 0.36490198969841, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1023.5981999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 939, + "reported_used_tokens": 903, + "working_set_bytes": 288768000, + "peak_working_set_bytes": 289681408 + }, + { + "query": "the config file content refuses to parse even though the TOML looks valid", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZ1GHPSENE70BT6R9EP6", + "id": "01M1X0VSDZCV0F2HZG1JGEEWGF", + "kind": "memory", + "score": 0.6614054441452026, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1126.6924000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 733, + "mcp_result_bytes": 814, + "wire_bytes": 850, + "reported_used_tokens": 814, + "working_set_bytes": 288772096, + "peak_working_set_bytes": 289689600 + }, + { + "query": "the remote server must refresh its checkout before answering file queries", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1092.3, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 228, + "mcp_result_bytes": 291, + "wire_bytes": 327, + "reported_used_tokens": 291, + "working_set_bytes": 288776192, + "peak_working_set_bytes": 289693696 + }, + { + "query": "tests must not climb to a parent git repository when resolving project paths", + "ranked": [ + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYV389BQAB7HQN0MA99V", + "id": "01M1X0VVHM17KHVHFDB7QAF873", + "kind": "memory", + "score": 0.9839988350868224, + "summary": "project:fact - [2026-09-07] [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1050.9772, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 794, + "mcp_result_bytes": 875, + "wire_bytes": 911, + "reported_used_tokens": 875, + "working_set_bytes": 288780288, + "peak_working_set_bytes": 289693696 + }, + { + "query": "how do I test request signing deterministically when timestamps change every run?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1035.7725, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288833536, + "peak_working_set_bytes": 289746944 + }, + { + "query": "adding a new variant to the host target enum - which places will I forget to update?", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RYK4YHQ3NZXK16HEAQYY", + "id": "01M1X0VXKB8206AE4Y1RWFNPC9", + "kind": "memory", + "score": 0.885076105594635, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1586.1805, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1058, + "mcp_result_bytes": 1139, + "wire_bytes": 1175, + "reported_used_tokens": 1139, + "working_set_bytes": 288841728, + "peak_working_set_bytes": 289755136 + }, + { + "query": "how do I enable GPU acceleration for kimetsu embedding inference", + "ranked": [ + "mcp-tool-timeouts", + "kimetsu-proactive-hooks" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S1SAR92694NXY6FKMJ33", + "id": "01M1X0VZ4P2VRGN7MARHAPYZ2H", + "kind": "memory", + "score": 0.9826309084892272, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + }, + { + "expansion_handle": "memory:01M1X0S2JH9AWTYF2E28N2ER76", + "id": "01M1X0VZ4PXRPDHZWWT033HHME", + "kind": "memory", + "score": 0.8807981610298157, + "summary": "project:fact - [tags: kimetsu proactive hooks context injection] kimetsu's proactive context injection runs before each agent turn (pre-turn hook) and injects relevant memories into the system prompt prefix. The hook invocation adds latency to the first token: embedding inference + vector search + reranking + context formatting. On a cold start, this can be 1-3 seconds." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 1052.0181, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1377, + "mcp_result_bytes": 1476, + "wire_bytes": 1512, + "reported_used_tokens": 1476, + "working_set_bytes": 288841728, + "peak_working_set_bytes": 289755136 + }, + { + "query": "how do I throttle kimetsu API spend per month", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 1006.699, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288899072, + "peak_working_set_bytes": 289816576 + }, + { + "query": "can the kimetsu brain database be stored in S3 instead of on disk", + "ranked": [ + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S23B6NYNB9ZTRCDV5CN8", + "id": "01M1X0W14WDCYYTBJW9D67W7R5", + "kind": "memory", + "score": 0.38596054911613464, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 937.3276000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 875, + "mcp_result_bytes": 956, + "wire_bytes": 992, + "reported_used_tokens": 956, + "working_set_bytes": 288911360, + "peak_working_set_bytes": 289828864 + }, + { + "query": "how do I plug a custom tokenizer into the FTS index", + "ranked": [ + "sqlite-fts5-tokenizer" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZB3TPV89DMSTSETEPAW", + "id": "01M1X0W22FQE9NCVFKWWCJ1QHY", + "kind": "memory", + "score": 0.9691632390022278, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 1052.2549000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 671, + "mcp_result_bytes": 756, + "wire_bytes": 792, + "reported_used_tokens": 756, + "working_set_bytes": 288915456, + "peak_working_set_bytes": 289828864 + }, + { + "query": "what should I check when kimetsu behaves differently on Windows than on Linux?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1033.7863, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 289169408, + "peak_working_set_bytes": 290082816 + }, + { + "query": "what are the moving parts of the kimetsu remote deployment story?", + "ranked": [ + "kimetsu-write-tools-gate", + "ci-secrets-masking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S2KNSRADJJPEW568DX62", + "id": "01M1X0W43X8ERGPNW39ZQC9ENF", + "kind": "memory", + "score": 0.9729357361793518, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level \u2014 disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1X0S2A9BNGKZYSQ6BKH2G8K", + "id": "01M1X0W43XBZ4ZD8NBPSQSZQ0F", + "kind": "memory", + "score": 0.8412115573883057, + "summary": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output \u2014 but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1024.6777, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1410, + "mcp_result_bytes": 1509, + "wire_bytes": 1546, + "reported_used_tokens": 1509, + "working_set_bytes": 289562624, + "peak_working_set_bytes": 290480128 + }, + { + "query": "which lessons cover guarding behavior behind environment variables?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 941.1064, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 289611776, + "peak_working_set_bytes": 290516992 + }, + { + "query": "SQLite BUSY error under concurrent writes", + "ranked": [ + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZ695HPYBS44T4Z7G5RR", + "id": "01M1X0W6175BDTMEHVD6EZ06HA", + "kind": "memory", + "score": 0.9978362917900084, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 904.3236999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 898, + "mcp_result_bytes": 979, + "wire_bytes": 1016, + "reported_used_tokens": 979, + "working_set_bytes": 289656832, + "peak_working_set_bytes": 290549760 + }, + { + "query": "SQLite WAL mode breaks when the database is on a network share", + "ranked": [ + "sqlite-wal-network-drive", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZ9WFMZG0GGATBEZ1Z23", + "id": "01M1X0W6X24ES2K1GKJNAFDRAS", + "kind": "memory", + "score": 0.999302864074707, + "summary": "project:fact - [2026-09-07] [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + }, + { + "expansion_handle": "memory:01M1X0RZ695HPYBS44T4Z7G5RR", + "id": "01M1X0W6X20YZBFZD63SKQRQ7D", + "kind": "memory", + "score": 0.9966553449630736, + "summary": "project:fact - [2026-09-07] [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1038.9854, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1448, + "mcp_result_bytes": 1547, + "wire_bytes": 1584, + "reported_used_tokens": 1547, + "working_set_bytes": 289685504, + "peak_working_set_bytes": 290598912 + }, + { + "query": "my SQLite WAL database causes SQLITE_IOERR_LOCK on a mapped drive", + "ranked": [ + "sqlite-wal-network-drive" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZ9WFMZG0GGATBEZ1Z23", + "id": "01M1X0W7XSX7P551JTR6EYQ0PX", + "kind": "memory", + "score": 0.99892657995224, + "summary": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1073.3523, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 748, + "mcp_result_bytes": 829, + "wire_bytes": 866, + "reported_used_tokens": 829, + "working_set_bytes": 289685504, + "peak_working_set_bytes": 290598912 + }, + { + "query": "FTS5 tokenizer configuration for Rust identifiers with underscores", + "ranked": [ + "sqlite-fts5-tokenizer", + "kimetsu-query-stemming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZB3TPV89DMSTSETEPAW", + "id": "01M1X0W8ZENXJMRKGK75S3K0GS", + "kind": "memory", + "score": 0.998104453086853, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + }, + { + "expansion_handle": "memory:01M1X0S2PZHHMGHW25YMR7CTJ2", + "id": "01M1X0W8ZE1PE2KQMA6BP46DF6", + "kind": "memory", + "score": 0.7023860812187195, + "summary": "project:fact - [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1016.8025, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1212, + "mcp_result_bytes": 1331, + "wire_bytes": 1368, + "reported_used_tokens": 1331, + "working_set_bytes": 289693696, + "peak_working_set_bytes": 290603008 + }, + { + "query": "I switched the FTS5 tokenizer but search stopped returning results", + "ranked": [ + "sqlite-fts5-tokenizer" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZB3TPV89DMSTSETEPAW", + "id": "01M1X0W9Z2STDV3M2DD7ET9ZEP", + "kind": "memory", + "score": 0.8194089531898499, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1041.576, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 670, + "mcp_result_bytes": 755, + "wire_bytes": 792, + "reported_used_tokens": 755, + "working_set_bytes": 289705984, + "peak_working_set_bytes": 290615296 + }, + { + "query": "optimal SQLite page size for storing embedding vectors", + "ranked": [ + "sqlite-page-size", + "onnx-dim-mismatch", + "onnx-cosine-vs-dot" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZCDN7TC41PYFAV8W2YR", + "id": "01M1X0WAZPA00X26M4SJG01RQH", + "kind": "memory", + "score": 0.9990121126174928, + "summary": "project:fact - [tags: sqlite page_size performance rusqlite] SQLite's default page_size is 4096 bytes. For a write-heavy brain database with large BLOB payloads (embedding vectors), raising page_size to 16384 reduces fragmentation and improves sequential scan throughput. `PRAGMA page_size = 16384;` must be set BEFORE the first table is created \u2014 changing it on an existing database requires a VACUUM afterward to rebuild all pages." + }, + { + "expansion_handle": "memory:01M1X0S0F0X8EQPVKXEKWVPP7B", + "id": "01M1X0WAZPGCDFWK5MRZ803APH", + "kind": "memory", + "score": 0.9881643056869508, + "summary": "project:fact - [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results \u2014 the ANN index shape mismatch isn't always caught at runtime." + }, + { + "expansion_handle": "memory:01M1X0S0DQ4D0RQ21RF41GE9GJ", + "id": "01M1X0WAZPQ0WSEE4Z0HSHEC89", + "kind": "memory", + "score": 0.9425415992736816, + "summary": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing \u2014 double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 972.4629, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1860, + "mcp_result_bytes": 1977, + "wire_bytes": 2014, + "reported_used_tokens": 1977, + "working_set_bytes": 289751040, + "peak_working_set_bytes": 290656256 + }, + { + "query": "ON DELETE CASCADE in SQLite does nothing \u2014 foreign keys not enforced", + "ranked": [ + "sqlite-foreign-keys-default-off" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZDJ4CCR1ZDZZZCNCB6P", + "id": "01M1X0WBY31XVMNZ53Q85DZXJ2", + "kind": "memory", + "score": 0.9996858835220336, + "summary": "project:fact - [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting \u2014 every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1066.1992, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 736, + "mcp_result_bytes": 817, + "wire_bytes": 854, + "reported_used_tokens": 817, + "working_set_bytes": 289751040, + "peak_working_set_bytes": 290660352 + }, + { + "query": "indexing a JSON metadata column in SQLite without a schema migration", + "ranked": [ + "sqlite-json1-extract", + "testing-fixture-drift", + "onnx-dim-mismatch", + "sqlite-partial-index" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZEPN5K7X87DKXJHE1BW", + "id": "01M1X0WCZRGSBDTFSKQTDXX0HD", + "kind": "memory", + "score": 0.9955366849899292, + "summary": "project:fact - [tags: sqlite json1 json_extract rusqlite] SQLite's json1 extension (built in since 3.38.0) lets you index and query JSONB columns with `json_extract(col, '$.field')`. To create a partial index over a JSON field: `CREATE INDEX idx ON memories (json_extract(metadata, '$.scope')) WHERE json_extract(metadata, '$.scope') IS NOT NULL;`. Use `json_each` for array fields." + }, + { + "expansion_handle": "memory:01M1X0S1Q0NEBQJSWTFZPHE4ZT", + "id": "01M1X0WCZRVD0JWXJFSDZYCZVJ", + "kind": "memory", + "score": 0.8227390646934509, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + }, + { + "expansion_handle": "memory:01M1X0S0F0X8EQPVKXEKWVPP7B", + "id": "01M1X0WCZRVYBPRFYKBAXNPHS9", + "kind": "memory", + "score": 0.38374292850494385, + "summary": "project:fact - [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results \u2014 the ANN index shape mismatch isn't always caught at runtime." + }, + { + "expansion_handle": "memory:01M1X0RZH6FN6D4B5YCSTDQ05J", + "id": "01M1X0WCZRFYBASRPBTXEY0MR4", + "kind": "memory", + "score": 0.3276048004627228, + "summary": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query \u2014 the planner uses the partial index only when the WHERE clause matches." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1031.4084, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2381, + "mcp_result_bytes": 2516, + "wire_bytes": 2553, + "reported_used_tokens": 2516, + "working_set_bytes": 289816576, + "peak_working_set_bytes": 290725888 + }, + { + "query": "prepare() vs prepare_cached() in rusqlite hot insert loop", + "ranked": [ + "sqlite-prepared-stmt-cache" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZG16DQW41R2X6VS12MN", + "id": "01M1X0WDZBGTXCJVWF35P8DJXQ", + "kind": "memory", + "score": 0.9993672966957092, + "summary": "project:fact - [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1031.2859, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 689, + "mcp_result_bytes": 770, + "wire_bytes": 807, + "reported_used_tokens": 770, + "working_set_bytes": 289845248, + "peak_working_set_bytes": 290750464 + }, + { + "query": "speed up bulk memory ingest by caching SQL statements", + "ranked": [ + "sqlite-prepared-stmt-cache" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZG16DQW41R2X6VS12MN", + "id": "01M1X0WF3N9H0Z007Z2XQW0M99", + "kind": "memory", + "score": 0.9823396801948548, + "summary": "project:fact - [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1577.9732, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 688, + "mcp_result_bytes": 769, + "wire_bytes": 806, + "reported_used_tokens": 769, + "working_set_bytes": 289861632, + "peak_working_set_bytes": 290766848 + }, + { + "query": "partial index on deleted_at IS NULL for faster active memory queries", + "ranked": [ + "sqlite-partial-index" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZH6FN6D4B5YCSTDQ05J", + "id": "01M1X0WGMSXME3ZBVBAWAKPV0R", + "kind": "memory", + "score": 0.9988954067230223, + "summary": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query \u2014 the planner uses the partial index only when the WHERE clause matches." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1296.1408999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 794, + "mcp_result_bytes": 875, + "wire_bytes": 912, + "reported_used_tokens": 875, + "working_set_bytes": 289861632, + "peak_working_set_bytes": 290766848 + }, + { + "query": "the brain query is slow because it scans all rows including soft-deleted ones", + "ranked": [ + "sqlite-partial-index" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZH6FN6D4B5YCSTDQ05J", + "id": "01M1X0WHYFDBMZEPD6XS5YMEC3", + "kind": "memory", + "score": 0.5760471224784851, + "summary": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query \u2014 the planner uses the partial index only when the WHERE clause matches." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1334.4989, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 793, + "mcp_result_bytes": 874, + "wire_bytes": 911, + "reported_used_tokens": 874, + "working_set_bytes": 289923072, + "peak_working_set_bytes": 290840576 + }, + { + "query": "Cargo.lock changed unexpectedly after adding a new workspace crate", + "ranked": [ + "cargo-lockfile-drift", + "cargo-feature-unification-embeddings", + "cargo-target-dir-sharing", + "cargo-patch-section" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZJGQK2RS7B2P2YYBFFY", + "id": "01M1X0WK5H2QP0TVC8G4S4HB4T", + "kind": "memory", + "score": 0.9991374015808104, + "summary": "project:fact - [2026-09-07] [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this \u2014 it errors on any lockfile diff." + }, + { + "expansion_handle": "memory:01M1X0RYFCG3DKA4C6N43J3PQW", + "id": "01M1X0WK5H449T7K79RP3SEKXP", + "kind": "memory", + "score": 0.9968542456626892, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X0RZPJXGMPZEWZSG7YHJTV", + "id": "01M1X0WK5HBTF678CE088EHXMW", + "kind": "memory", + "score": 0.9829630851745604, + "summary": "project:fact - [2026-09-07] [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps \u2014 use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + }, + { + "expansion_handle": "memory:01M1X0RZT8GNTG2NX4TSXZ1DYS", + "id": "01M1X0WK5H7T8GY2B3A6B6S4GJ", + "kind": "memory", + "score": 0.9262890815734864, + "summary": "project:fact - [2026-09-07] [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace \u2014 including transitive deps \u2014 that depend on `my-crate`. Remove the patch before publishing." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1069.3911, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3010, + "mcp_result_bytes": 3153, + "wire_bytes": 3190, + "reported_used_tokens": 3153, + "working_set_bytes": 289923072, + "peak_working_set_bytes": 290840576 + }, + { + "query": "how do I prevent CI from accepting a modified lockfile silently?", + "ranked": [ + "cargo-lockfile-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZJGQK2RS7B2P2YYBFFY", + "id": "01M1X0WM4KKRT4EHXYWYF8AXTN", + "kind": "memory", + "score": 0.9125379323959352, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this \u2014 it errors on any lockfile diff." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1082.5666, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 765, + "mcp_result_bytes": 846, + "wire_bytes": 883, + "reported_used_tokens": 846, + "working_set_bytes": 290136064, + "peak_working_set_bytes": 291053568 + }, + { + "query": "build.rs reruns on every incremental build even when nothing changed", + "ranked": [ + "cargo-build-script-rerun" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZKKRHWCKVB895A3TTRZ", + "id": "01M1X0WN6SAB7VSD770N6F7E8T", + "kind": "memory", + "score": 0.9996689558029176, + "summary": "project:fact - [2026-09-07] [tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1148.7142, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 698, + "mcp_result_bytes": 779, + "wire_bytes": 816, + "reported_used_tokens": 779, + "working_set_bytes": 290275328, + "peak_working_set_bytes": 291192832 + }, + { + "query": "incremental cargo build is slow because build script runs every time", + "ranked": [ + "cargo-build-script-rerun" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZKKRHWCKVB895A3TTRZ", + "id": "01M1X0WPA3QQ66S6TE3BWJXMB0", + "kind": "memory", + "score": 0.9978280663490297, + "summary": "project:fact - [tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1105.9, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 685, + "mcp_result_bytes": 766, + "wire_bytes": 803, + "reported_used_tokens": 766, + "working_set_bytes": 290279424, + "peak_working_set_bytes": 291192832 + }, + { + "query": "a dev-dependency is activating an embeddings feature in my production build", + "ranked": [ + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZN0RW2M8XPRGDVPM9FC", + "id": "01M1X0WQDBJ5T2CZ2R9SFK4SN8", + "kind": "memory", + "score": 0.9944193959236144, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1145.4768, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 931, + "mcp_result_bytes": 1012, + "wire_bytes": 1049, + "reported_used_tokens": 1012, + "working_set_bytes": 290353152, + "peak_working_set_bytes": 291266560 + }, + { + "query": "how do I prevent a test-only feature from bleeding into the non-test compilation?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 930.2764, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 290357248, + "peak_working_set_bytes": 291270656 + }, + { + "query": "linker errors in target/ caused by antivirus holding the exe file", + "ranked": [ + "windows-file-locking-av", + "cargo-target-dir-sharing" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S00S7WBT6YG9WY516E7R", + "id": "01M1X0WSD1E5XKWR6WRCSC6GXS", + "kind": "memory", + "score": 0.9997633099555968, + "summary": "project:fact - [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + }, + { + "expansion_handle": "memory:01M1X0RZPJXGMPZEWZSG7YHJTV", + "id": "01M1X0WSD2V8G20RRBEH6ATF1D", + "kind": "memory", + "score": 0.7463976740837097, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps \u2014 use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 965.2834, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1523, + "mcp_result_bytes": 1622, + "wire_bytes": 1659, + "reported_used_tokens": 1622, + "working_set_bytes": 290390016, + "peak_working_set_bytes": 291299328 + }, + { + "query": "Access is denied (os error 5) when linking on Windows \u2014 how do I fix this?", + "ranked": [ + "windows-file-locking-av" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S00S7WBT6YG9WY516E7R", + "id": "01M1X0WTBR24M7JPX1HFGRPG47", + "kind": "memory", + "score": 0.9977193474769592, + "summary": "project:fact - [2026-09-07] [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 981.324, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 769, + "mcp_result_bytes": 850, + "wire_bytes": 887, + "reported_used_tokens": 850, + "working_set_bytes": 290398208, + "peak_working_set_bytes": 291307520 + }, + { + "query": "incremental build broke with a type mismatch after switching branches", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZQXJ5FN0W81RATRACYX", + "id": "01M1X0WVAP4MSC4YNCMNXKYVEG", + "kind": "memory", + "score": 0.7971777319908142, + "summary": "project:fact - [2026-09-07] [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1021.1984000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 890, + "mcp_result_bytes": 971, + "wire_bytes": 1008, + "reported_used_tokens": 971, + "working_set_bytes": 290398208, + "peak_working_set_bytes": 291311616 + }, + { + "query": "cargo reports a type error that references a type not in the codebase", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZQXJ5FN0W81RATRACYX", + "id": "01M1X0WW9QNB3QE1KXA6ASRF0H", + "kind": "memory", + "score": 0.7925198078155518, + "summary": "project:fact - [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 917.7891000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 877, + "mcp_result_bytes": 958, + "wire_bytes": 995, + "reported_used_tokens": 958, + "working_set_bytes": 290435072, + "peak_working_set_bytes": 291356672 + }, + { + "query": "compile fastembed at O2 in debug builds to avoid slow embedding inference", + "ranked": [ + "cargo-profile-override", + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZRY0ANM96SJZ9PXYYY2", + "id": "01M1X0WX68R8D2KF6S9KA4RR4T", + "kind": "memory", + "score": 0.9932281374931335, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1X0S1SAR92694NXY6FKMJ33", + "id": "01M1X0WX68WW7AANZCS46JHK74", + "kind": "memory", + "score": 0.987656831741333, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 962.9822, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1322, + "mcp_result_bytes": 1421, + "wire_bytes": 1458, + "reported_used_tokens": 1421, + "working_set_bytes": 290443264, + "peak_working_set_bytes": 291360768 + }, + { + "query": "override compilation profile for a single crate in a Cargo workspace", + "ranked": [ + "cargo-patch-section", + "cargo-profile-override", + "cargo-target-dir-sharing", + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZT8GNTG2NX4TSXZ1DYS", + "id": "01M1X0WY4PV330TDHB4BABZT8W", + "kind": "memory", + "score": 0.9984123706817628, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace \u2014 including transitive deps \u2014 that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1X0RZRY0ANM96SJZ9PXYYY2", + "id": "01M1X0WY4PGMEZGX738QY9Q54C", + "kind": "memory", + "score": 0.9979992508888244, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1X0RZPJXGMPZEWZSG7YHJTV", + "id": "01M1X0WY4P8GYWSZJXY83RHDE5", + "kind": "memory", + "score": 0.9956549406051636, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps \u2014 use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + }, + { + "expansion_handle": "memory:01M1X0RZN0RW2M8XPRGDVPM9FC", + "id": "01M1X0WY4Q86EN1TD1NCRDGAAZ", + "kind": "memory", + "score": 0.9820712208747864, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 0.5, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1012.0606999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2682, + "mcp_result_bytes": 2821, + "wire_bytes": 2858, + "reported_used_tokens": 2821, + "working_set_bytes": 290455552, + "peak_working_set_bytes": 291377152 + }, + { + "query": "[patch.crates-io] workspace dependency override", + "ranked": [ + "cargo-patch-section", + "cargo-lockfile-drift", + "cargo-dev-dep-leak", + "cargo-target-dir-sharing" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZT8GNTG2NX4TSXZ1DYS", + "id": "01M1X0WZ3XVV3G6JSQSYE3MSH3", + "kind": "memory", + "score": 0.9999405145645142, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace \u2014 including transitive deps \u2014 that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1X0RZJGQK2RS7B2P2YYBFFY", + "id": "01M1X0WZ3X5CJ09N73VJ4CAGVQ", + "kind": "memory", + "score": 0.9975811243057252, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this \u2014 it errors on any lockfile diff." + }, + { + "expansion_handle": "memory:01M1X0RZN0RW2M8XPRGDVPM9FC", + "id": "01M1X0WZ3XP0P87YYGZSE9MZXE", + "kind": "memory", + "score": 0.994149684906006, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + }, + { + "expansion_handle": "memory:01M1X0RZPJXGMPZEWZSG7YHJTV", + "id": "01M1X0WZ3XG9EKJ9SGY82584AR", + "kind": "memory", + "score": 0.7471600770950317, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps \u2014 use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 759.4119999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2755, + "mcp_result_bytes": 2894, + "wire_bytes": 2931, + "reported_used_tokens": 2894, + "working_set_bytes": 290471936, + "peak_working_set_bytes": 291377152 + }, + { + "query": "pin minimum supported Rust version in Cargo.toml", + "ranked": [ + "cargo-msrv" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZVDK0XKD0VSZBMK5RYW", + "id": "01M1X0WZWMAX1NBM1PG8XA54E3", + "kind": "memory", + "score": 0.999652862548828, + "summary": "project:fact - [tags: cargo rust msrv edition compatibility] Set `rust-version` in each `Cargo.toml` to declare the minimum supported Rust version (MSRV). Cargo enforces this with `--check`: `cargo check` fails if the toolchain is older than `rust-version`. Keep MSRV as old as your oldest supported deployment target." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 890.9034, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 693, + "mcp_result_bytes": 774, + "wire_bytes": 811, + "reported_used_tokens": 774, + "working_set_bytes": 290471936, + "peak_working_set_bytes": 291389440 + }, + { + "query": "Windows path over 260 characters causes OS error 3 during Cargo build", + "ranked": [ + "windows-long-paths", + "windows-file-locking-av" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZZJ9WFDJA9XFE36FC0F", + "id": "01M1X0X0RDFFY9752HZVW05F5D", + "kind": "memory", + "score": 0.9964189529418944, + "summary": "project:fact - [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe." + }, + { + "expansion_handle": "memory:01M1X0S00S7WBT6YG9WY516E7R", + "id": "01M1X0X0RDAK3WVZ006B3TCB1S", + "kind": "memory", + "score": 0.9571694135665894, + "summary": "project:fact - [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 871.2860000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1297, + "mcp_result_bytes": 1406, + "wire_bytes": 1443, + "reported_used_tokens": 1406, + "working_set_bytes": 290492416, + "peak_working_set_bytes": 291401728 + }, + { + "query": "how do I enable long file paths for Cargo on Windows?", + "ranked": [ + "windows-long-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZZJ9WFDJA9XFE36FC0F", + "id": "01M1X0X1JWM9YDHG63VZKT8YB2", + "kind": "memory", + "score": 0.9998334646224976, + "summary": "project:fact - [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 933.5882, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 769, + "mcp_result_bytes": 860, + "wire_bytes": 897, + "reported_used_tokens": 860, + "working_set_bytes": 290516992, + "peak_working_set_bytes": 291434496 + }, + { + "query": "intermittent sharing violation errors when Rust linker writes the exe on Windows", + "ranked": [ + "windows-file-locking-av", + "windows-long-paths", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S00S7WBT6YG9WY516E7R", + "id": "01M1X0X2G7Q4XF92YP5ZC8HZ0B", + "kind": "memory", + "score": 0.999750316143036, + "summary": "project:fact - [2026-09-07] [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + }, + { + "expansion_handle": "memory:01M1X0RZZJ9WFDJA9XFE36FC0F", + "id": "01M1X0X2G735WYD15PP7D71KF6", + "kind": "memory", + "score": 0.4757097661495209, + "summary": "project:fact - [2026-09-07] [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe." + }, + { + "expansion_handle": "memory:01M1X0RZ695HPYBS44T4Z7G5RR", + "id": "01M1X0X2G7PKS9Z611CYZQHVKY", + "kind": "memory", + "score": 0.38107830286026, + "summary": "project:fact - [2026-09-07] [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 906.0984, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2006, + "mcp_result_bytes": 2133, + "wire_bytes": 2170, + "reported_used_tokens": 2133, + "working_set_bytes": 290537472, + "peak_working_set_bytes": 291450880 + }, + { + "query": "Rust walkdir follows junctions differently from symlinks on Windows", + "ranked": [ + "windows-junctions-vs-symlinks" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S04XN5BWK8VDYDRE06FK", + "id": "01M1X0X3D5BN1VC96DRJ6PCADH", + "kind": "memory", + "score": 0.9996020197868348, + "summary": "project:fact - [tags: windows junctions symlinks rust std::fs] On Windows, directory junctions (NTFS reparse points) behave like symlinks for directory traversal but `std::fs::symlink_metadata` returns `FileType::is_symlink() = false` for junctions (only true for regular symlinks). Use `std::fs::read_link` \u2014 it succeeds for both junction and symlink. `walkdir` crate's `follow_links` follows both, but its `is_symlink()` method correctly reports only actual symlinks." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 957.4997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 845, + "mcp_result_bytes": 926, + "wire_bytes": 963, + "reported_used_tokens": 926, + "working_set_bytes": 290766848, + "peak_working_set_bytes": 291680256 + }, + { + "query": "UNC path canonicalize returns verbatim prefix \u2014 how do I strip it?", + "ranked": [ + "windows-unc-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S0204JN6C6BD7MV9MD43", + "id": "01M1X0X4AGKHJRZQEAXQDJ6YRY", + "kind": "memory", + "score": 0.9988629817962646, + "summary": "project:fact - [tags: windows unc-paths rust std::fs] Windows UNC paths (`\\\\server\\share\\...`) are not supported by most Rust `std::fs` operations unless passed through the extended-length prefix `\\\\?\\UNC\\server\\share\\...`. `std::path::Path::new(\"\\\\\\\\server\\\\share\")` works for basic operations but breaks with `canonicalize()` which returns the verbatim prefix form. When walking directory trees that may start on UNC paths, use the `dunce` crate to strip the verbatim prefix before comparing or displaying paths." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 955.3394, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 908, + "mcp_result_bytes": 1025, + "wire_bytes": 1062, + "reported_used_tokens": 1025, + "working_set_bytes": 290803712, + "peak_working_set_bytes": 291729408 + }, + { + "query": "UTF-8 memory text prints as mojibake in the Windows console", + "ranked": [ + "windows-console-encoding" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S03GHHJ6GSCGY57RSCDY", + "id": "01M1X0X58CNKGM1CJ09A2XWEAB", + "kind": "memory", + "score": 0.9996604919433594, + "summary": "project:fact - [tags: windows console encoding utf8 rust] Windows console code page defaults to the system ANSI code page (usually CP1252 or CP932), not UTF-8. Rust's `println!` writes UTF-8 bytes which display as mojibake in a non-UTF-8 console. Fix at process startup: call `SetConsoleOutputCP(65001)` via `winapi` or `windows-sys`, or set `PYTHONUTF8=1`/`RUST_LOG` before launch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 929.7386, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 757, + "mcp_result_bytes": 838, + "wire_bytes": 875, + "reported_used_tokens": 838, + "working_set_bytes": 290803712, + "peak_working_set_bytes": 291729408 + }, + { + "query": "process exit code is 4294967295 instead of -1 on Windows", + "ranked": [ + "windows-exit-codes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S068AZ25N1V0GPXQTBWG", + "id": "01M1X0X65RYBX574ZGVM0VHTFA", + "kind": "memory", + "score": 0.9966622591018676, + "summary": "project:fact - [tags: windows exit-codes rust process child] On Windows, process exit codes are 32-bit unsigned integers (DWORD). Rust's `ExitStatus::code()` returns `Option` \u2014 it's `None` if the process was killed by a signal (which Windows doesn't use; instead, TerminateProcess with a code). Conventional codes: 0=success, 1=generic error, 0xC0000005=access violation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 974.6397, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 753, + "mcp_result_bytes": 834, + "wire_bytes": 871, + "reported_used_tokens": 834, + "working_set_bytes": 290816000, + "peak_working_set_bytes": 291729408 + }, + { + "query": "tokenizer.json must match the ONNX model \u2014 what breaks if it doesn't?", + "ranked": [ + "onnx-tokenizer-mismatch" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S09030PYG8RSGQGD8KFX", + "id": "01M1X0X74GBP8KWHG439VMZYFY", + "kind": "memory", + "score": 0.9991299510002136, + "summary": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly \u2014 specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings \u2014 cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1005.0886000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 959, + "mcp_result_bytes": 1040, + "wire_bytes": 1077, + "reported_used_tokens": 1040, + "working_set_bytes": 290848768, + "peak_working_set_bytes": 291762176 + }, + { + "query": "embedding quality degraded after I swapped in the INT8 quantized model", + "ranked": [ + "onnx-quantization-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S0A67XVTPTF2148D1KT6", + "id": "01M1X0X84HJXXQJ0ZWYHS17AMF", + "kind": "memory", + "score": 0.997980535030365, + "summary": "project:fact - [2026-09-07] [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals \u2014 cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1038.7032000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 990, + "mcp_result_bytes": 1071, + "wire_bytes": 1108, + "reported_used_tokens": 1071, + "working_set_bytes": 290856960, + "peak_working_set_bytes": 291774464 + }, + { + "query": "missing attention mask causes low-norm embeddings in batch inference", + "ranked": [ + "onnx-batch-padding" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S0BAQN6XNV5X3HS7E8CW", + "id": "01M1X0X950GMV1J6WP0HSZ2VW6", + "kind": "memory", + "score": 0.9998397827148438, + "summary": "project:fact - [tags: onnx batch padding attention-mask embeddings] When running batch inference with an ONNX model, all inputs in the batch must be padded to the same sequence length. The `attention_mask` tensor marks which tokens are real (1) and which are padding (0). Failing to pass `attention_mask` causes the model to average-pool over padding tokens, producing systematically lower-norm embeddings." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1014.6561999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 781, + "mcp_result_bytes": 862, + "wire_bytes": 899, + "reported_used_tokens": 862, + "working_set_bytes": 290873344, + "peak_working_set_bytes": 291778560 + }, + { + "query": "ONNX model download fails in a Docker container with no home directory", + "ranked": [ + "onnx-model-cache-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S0CMD47HSRETGAXMQMBV", + "id": "01M1X0XA49HCM7JYGV0BJKXR7C", + "kind": "memory", + "score": 0.9887272119522096, + "summary": "project:fact - [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1003.8687, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 755, + "mcp_result_bytes": 838, + "wire_bytes": 875, + "reported_used_tokens": 838, + "working_set_bytes": 290877440, + "peak_working_set_bytes": 291786752 + }, + { + "query": "fastembed cache path environment variable for CI", + "ranked": [ + "onnx-model-cache-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S0CMD47HSRETGAXMQMBV", + "id": "01M1X0XB3JWJ3H834DPZ2RCS6V", + "kind": "memory", + "score": 0.9995118379592896, + "summary": "project:fact - [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 975.5047999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 756, + "mcp_result_bytes": 839, + "wire_bytes": 876, + "reported_used_tokens": 839, + "working_set_bytes": 290947072, + "peak_working_set_bytes": 291852288 + }, + { + "query": "cosine similarity vs dot product for L2-normalized embedding vectors", + "ranked": [ + "onnx-cosine-vs-dot", + "onnx-tokenizer-mismatch", + "onnx-quantization-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S0DQ4D0RQ21RF41GE9GJ", + "id": "01M1X0XC2HSFD2W213H6Z0595X", + "kind": "memory", + "score": 0.9999407529830932, + "summary": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing \u2014 double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + }, + { + "expansion_handle": "memory:01M1X0S09030PYG8RSGQGD8KFX", + "id": "01M1X0XC2HGKD0GJCNWSYP7GY5", + "kind": "memory", + "score": 0.9514977931976318, + "summary": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly \u2014 specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings \u2014 cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo." + }, + { + "expansion_handle": "memory:01M1X0S0A67XVTPTF2148D1KT6", + "id": "01M1X0XC2H2P6WH1QQXRXB9KSJ", + "kind": "memory", + "score": 0.941756010055542, + "summary": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals \u2014 cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 946.4418, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2245, + "mcp_result_bytes": 2362, + "wire_bytes": 2399, + "reported_used_tokens": 2362, + "working_set_bytes": 290988032, + "peak_working_set_bytes": 291901440 + }, + { + "query": "stored vectors have wrong dimension after switching embedding models", + "ranked": [ + "onnx-dim-mismatch", + "onnx-cosine-vs-dot", + "onnx-tokenizer-mismatch" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S0F0X8EQPVKXEKWVPP7B", + "id": "01M1X0XCZ8412HGGPM0134AZCW", + "kind": "memory", + "score": 0.9997621178627014, + "summary": "project:fact - [2026-09-07] [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results \u2014 the ANN index shape mismatch isn't always caught at runtime." + }, + { + "expansion_handle": "memory:01M1X0S0DQ4D0RQ21RF41GE9GJ", + "id": "01M1X0XCZ8M0GKNKKQEJAJZ2Y1", + "kind": "memory", + "score": 0.997715711593628, + "summary": "project:fact - [2026-09-07] [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing \u2014 double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + }, + { + "expansion_handle": "memory:01M1X0S09030PYG8RSGQGD8KFX", + "id": "01M1X0XCZ8JZJ5TV6DGSS6MY12", + "kind": "memory", + "score": 0.9388805031776428, + "summary": "project:fact - [2026-09-07] [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly \u2014 specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings \u2014 cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 838.1026, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2049, + "mcp_result_bytes": 2166, + "wire_bytes": 2203, + "reported_used_tokens": 2166, + "working_set_bytes": 290988032, + "peak_working_set_bytes": 291901440 + }, + { + "query": "E5 and Instructor models need a query prefix \u2014 what happens without it?", + "ranked": [ + "onnx-prefix-instructions", + "onnx-cosine-vs-dot" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S0JMA4R1XF3FYEZ2G8GP", + "id": "01M1X0XDTB9Q9X1N398VKQJW2H", + "kind": "memory", + "score": 0.996955633163452, + "summary": "project:fact - [tags: onnx embeddings prefix instruction e5 query passage] E5 and Instructor family models require a text prefix on BOTH query and passage sides to produce meaningful similarities: query prefix `\"query: \"`, passage prefix `\"passage: \"`. Omitting the prefix can drop MRR by 10-15 percentage points on out-of-domain datasets. Check the model's README for the exact prefix string \u2014 it varies by model family." + }, + { + "expansion_handle": "memory:01M1X0S0DQ4D0RQ21RF41GE9GJ", + "id": "01M1X0XDTC19FCGEP8WB4T8VXD", + "kind": "memory", + "score": 0.9543967247009276, + "summary": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing \u2014 double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 998.8251, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1339, + "mcp_result_bytes": 1446, + "wire_bytes": 1483, + "reported_used_tokens": 1446, + "working_set_bytes": 290992128, + "peak_working_set_bytes": 291905536 + }, + { + "query": "ORT thread pool contention when running multiple bench processes in parallel", + "ranked": [ + "onnx-ort-threading" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S0KT5FASD3GD1V6Z619Q", + "id": "01M1X0XER4KSMRPAFHWHMK2H59", + "kind": "memory", + "score": 0.9998078942298888, + "summary": "project:fact - [2026-09-07] [tags: onnx ort thread-pool parallelism cpu] ORT (ONNX Runtime) creates its own inter-op and intra-op thread pools. In a multi-process bench setup, each child inherits these pools and they compete for CPU cores. Set `SessionOptionsBuilder::with_intra_threads(1).with_inter_threads(1)` if you're running many parallel bench processes \u2014 this sacrifices per-inference throughput for lower contention." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 919.5893000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 802, + "mcp_result_bytes": 883, + "wire_bytes": 920, + "reported_used_tokens": 883, + "working_set_bytes": 290992128, + "peak_working_set_bytes": 291905536 + }, + { + "query": "git worktrees share the .kimetsu brain \u2014 how do I isolate test runs?", + "ranked": [ + "git-worktree-brain-isolation", + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S0N2RNCRZ1Z1FT2DDS51", + "id": "01M1X0XFN8DWN2QN5KB2W2A7P8", + "kind": "memory", + "score": 0.9996256828308104, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root \u2014 if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + }, + { + "expansion_handle": "memory:01M1X0RYV389BQAB7HQN0MA99V", + "id": "01M1X0XFN8XCMJDFPE3YRNEY2Z", + "kind": "memory", + "score": 0.9904396533966064, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 919.8287, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1435, + "mcp_result_bytes": 1534, + "wire_bytes": 1571, + "reported_used_tokens": 1534, + "working_set_bytes": 290992128, + "peak_working_set_bytes": 291909632 + }, + { + "query": "when is it safe to use --no-verify on git commit?", + "ranked": [ + "git-hooks-bypass", + "git-reflog-rescue" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S0P8DJZAFV66XE4ZETQ4", + "id": "01M1X0XGJZRY6Z873PJA12YHVK", + "kind": "memory", + "score": 0.9956986904144288, + "summary": "project:fact - [2026-09-07] [tags: git hooks bypass pre-commit skip] `git commit --no-verify` skips ALL hooks (pre-commit and commit-msg). Never use this in shared team repos where hooks enforce quality gates (lint, tests, memory harvest). Instead, fix the failing hook." + }, + { + "expansion_handle": "memory:01M1X0S0V9GB5KCN0SD1FF7GKG", + "id": "01M1X0XGJZQVB2EJAZ0SCC8SJ7", + "kind": "memory", + "score": 0.5084817409515381, + "summary": "project:fact - [2026-09-07] [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone \u2014 they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only \u2014 remote reflog is not accessible via normal git commands." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 975.2258, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1193, + "mcp_result_bytes": 1292, + "wire_bytes": 1329, + "reported_used_tokens": 1292, + "working_set_bytes": 290996224, + "peak_working_set_bytes": 291909632 + }, + { + "query": "reduce clone size and bandwidth for server-side repo ingest", + "ranked": [ + "git-sparse-checkout", + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S0QFSZWZR5VQ7MF6E5H8", + "id": "01M1X0XHHRNZ1CWN3WAK8XTBMC", + "kind": "memory", + "score": 0.9969936609268188, + "summary": "project:fact - [tags: git sparse-checkout partial-clone bandwidth] `git sparse-checkout init --cone` combined with `git clone --filter=blob:none` (partial clone) fetches only the commit graph and tree objects, not blobs. Individual blobs are fetched on demand when accessed. This cuts clone time for large repos from minutes to seconds." + }, + { + "expansion_handle": "memory:01M1X0RYAH80HT0J4XD4TW3J26", + "id": "01M1X0XHHR7ZC2SPA27WQ4SEF6", + "kind": "memory", + "score": 0.8199672698974609, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1096.7839999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1744, + "mcp_result_bytes": 1843, + "wire_bytes": 1880, + "reported_used_tokens": 1843, + "working_set_bytes": 291000320, + "peak_working_set_bytes": 291909632 + }, + { + "query": "spurious diffs from Windows CRLF line ending conversion in git", + "ranked": [ + "git-line-endings-windows" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S0RSJABHZA1Y7AXPD2B4", + "id": "01M1X0XJJSYDWE5WQNQMYV33HB", + "kind": "memory", + "score": 0.9993343949317932, + "summary": "project:fact - [tags: git line-endings windows crlf autocrlf] On Windows, `core.autocrlf=true` (git's default for Windows installs) converts LF to CRLF on checkout and CRLF to LF on commit. This causes spurious diffs when files are edited on Windows then committed \u2014 the content is identical but the line endings differ in the index vs the working tree. Fix: set `core.autocrlf=false` and `.gitattributes` with `* text=auto eol=lf` for the repo." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 981.0996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 940, + "reported_used_tokens": 903, + "working_set_bytes": 291057664, + "peak_working_set_bytes": 291962880 + }, + { + "query": "git submodule always gets the wrong commit in CI", + "ranked": [ + "git-submodule-pinning", + "git-hooks-bypass" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S0T3PSASF7293R9DKD26", + "id": "01M1X0XKHAXWV8726Y3A7A0DXP", + "kind": "memory", + "score": 0.9992856383323668, + "summary": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip \u2014 this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version." + }, + { + "expansion_handle": "memory:01M1X0S0P8DJZAFV66XE4ZETQ4", + "id": "01M1X0XKHA8PVMGT85WV8K677T", + "kind": "memory", + "score": 0.6295387744903564, + "summary": "project:fact - [tags: git hooks bypass pre-commit skip] `git commit --no-verify` skips ALL hooks (pre-commit and commit-msg). Never use this in shared team repos where hooks enforce quality gates (lint, tests, memory harvest). Instead, fix the failing hook." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 920.3299999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1157, + "mcp_result_bytes": 1256, + "wire_bytes": 1293, + "reported_used_tokens": 1256, + "working_set_bytes": 291225600, + "peak_working_set_bytes": 292139008 + }, + { + "query": "accidentally ran git reset --hard and lost commits \u2014 can I recover?", + "ranked": [ + "git-reflog-rescue" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S0V9GB5KCN0SD1FF7GKG", + "id": "01M1X0XME83CVP5T5R871ZCXWD", + "kind": "memory", + "score": 0.9995450377464294, + "summary": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone \u2014 they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only \u2014 remote reflog is not accessible via normal git commands." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 953.9199000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 762, + "mcp_result_bytes": 843, + "wire_bytes": 880, + "reported_used_tokens": 843, + "working_set_bytes": 291434496, + "peak_working_set_bytes": 292331520 + }, + { + "query": "blocking SQLite call from an async tokio handler causes latency spikes", + "ranked": [ + "tokio-blocking-in-async" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S0WE8VV1EREBQ8JXTVHF", + "id": "01M1X0XNC2QKW80FCFSBVHTHG1", + "kind": "memory", + "score": 0.9996535778045654, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 863.2117999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 766, + "mcp_result_bytes": 847, + "wire_bytes": 884, + "reported_used_tokens": 847, + "working_set_bytes": 291495936, + "peak_working_set_bytes": 292405248 + }, + { + "query": "Cannot start a runtime from within a runtime in a tokio test", + "ranked": [ + "tokio-runtime-in-tests", + "tokio-blocking-in-async" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S0XTZQGDG2VGR6DYQBX7", + "id": "01M1X0XP7MS35GH1WZJS4A6TDH", + "kind": "memory", + "score": 0.9997126460075378, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + }, + { + "expansion_handle": "memory:01M1X0S0WE8VV1EREBQ8JXTVHF", + "id": "01M1X0XP7MVWA0NDARMEKDSEM8", + "kind": "memory", + "score": 0.5779464840888977, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 894.5774, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1370, + "mcp_result_bytes": 1477, + "wire_bytes": 1514, + "reported_used_tokens": 1477, + "working_set_bytes": 291782656, + "peak_working_set_bytes": 292696064 + }, + { + "query": "tokio select cancels the other branch and loses the value in the channel", + "ranked": [ + "tokio-select-cancellation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S0ZA174TNE45K6MF0TSC", + "id": "01M1X0XQ2GNW8Q11H7TGJWCNXM", + "kind": "memory", + "score": 0.9981033802032472, + "summary": "project:fact - [tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 903.9371000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 751, + "mcp_result_bytes": 832, + "wire_bytes": 869, + "reported_used_tokens": 832, + "working_set_bytes": 291831808, + "peak_working_set_bytes": 292749312 + }, + { + "query": "mpsc channel backpressure causing senders to stall", + "ranked": [ + "tokio-channel-backpressure" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S10MPVQRK546CV31BYEC", + "id": "01M1X0XQZ876FHCEYYPWHZ9SXQ", + "kind": "memory", + "score": 0.9999104738235474, + "summary": "project:fact - [tags: tokio mpsc channel backpressure async rust] `tokio::sync::mpsc::channel(N)` with a bounded buffer provides backpressure: senders block when the buffer is full. This prevents unbounded memory growth but can cause sender tasks to stall. Choosing N: too small causes frequent backpressure (throughput drops); too large defeats the purpose." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 971.2101, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 733, + "mcp_result_bytes": 814, + "wire_bytes": 851, + "reported_used_tokens": 814, + "working_set_bytes": 291856384, + "peak_working_set_bytes": 292761600 + }, + { + "query": "overhead from calling spawn_blocking on every single query request", + "ranked": [ + "tokio-spawn-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S11XTPPWAH7B5CE4GBXX", + "id": "01M1X0XRXQD28DKX6NBKFZ1CQA", + "kind": "memory", + "score": 0.9961729645729064, + "summary": "project:fact - [tags: tokio spawn_blocking thread-pool rust blocking] `tokio::task::spawn_blocking` places work on a dedicated blocking thread pool (default up to 512 threads, configurable via `Builder::max_blocking_threads`). Each call creates or reuses a thread \u2014 there's no true pooling, threads may be created on demand. For many short-duration blocking calls (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 930.387, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 746, + "mcp_result_bytes": 827, + "wire_bytes": 864, + "reported_used_tokens": 827, + "working_set_bytes": 291909632, + "peak_working_set_bytes": 292818944 + }, + { + "query": "axum server panics during shutdown because the DB pool is already closed", + "ranked": [ + "tokio-shutdown-ordering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S15HR0X7TN920GCR1P5Q", + "id": "01M1X0XSTET86MD6QPKDNQ41PG", + "kind": "memory", + "score": 0.98052579164505, + "summary": "project:fact - [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries \u2014 the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 969.1227, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 931, + "mcp_result_bytes": 1012, + "wire_bytes": 1049, + "reported_used_tokens": 1012, + "working_set_bytes": 291995648, + "peak_working_set_bytes": 292913152 + }, + { + "query": "reqwest Client created per-request defeats connection pooling", + "ranked": [ + "http-connection-pooling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S16SXG4EPVR2DDCDNNT2", + "id": "01M1X0XTT68XST3PE447AW38CG", + "kind": "memory", + "score": 0.9998082518577576, + "summary": "project:fact - [tags: http reqwest connection-pool keep-alive rust] reqwest's `Client` holds a connection pool; always create ONE `Client` instance and clone it for each handler \u2014 cloning is cheap (Arc under the hood). Creating a `Client::new()` per request defeats connection pooling and causes TCP connection exhaustion under load. The default pool settings: max_idle_per_host=usize::MAX (unbounded), idle_timeout=90s." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 903.9307, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 797, + "mcp_result_bytes": 878, + "wire_bytes": 915, + "reported_used_tokens": 878, + "working_set_bytes": 291999744, + "peak_working_set_bytes": 292913152 + }, + { + "query": "LLM request times out during streaming \u2014 which timeout setting applies?", + "ranked": [ + "http-timeout-layering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S1830CQ8W624575SQQTT", + "id": "01M1X0XVMXM406KSDYJMGJH5BH", + "kind": "memory", + "score": 0.9987107515335084, + "summary": "project:fact - [tags: http reqwest timeout connect read total rust] reqwest has three distinct timeout knobs: `connect_timeout`, `read_timeout`, and `timeout` (total). They compose: if all three are set, the request fails at whichever fires first. For LLM API calls with streaming responses, `read_timeout` must be larger than the slowest expected token (often 30-60s) while `connect_timeout` can be tight (3-5s)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 762.4218000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 788, + "mcp_result_bytes": 869, + "wire_bytes": 906, + "reported_used_tokens": 869, + "working_set_bytes": 292028416, + "peak_working_set_bytes": 292941824 + }, + { + "query": "how do I safely retry a POST to the LLM API without creating duplicates?", + "ranked": [ + "http-retry-idempotency" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S19BBEHZB3ZHH63ZVAE6", + "id": "01M1X0XWCJCYM3FY4GVKTM7QEZ", + "kind": "memory", + "score": 0.9995805621147156, + "summary": "project:fact - [tags: http retry idempotency post put reqwest] Only retry idempotent requests automatically. GET, HEAD, PUT, DELETE are idempotent. POST is NOT \u2014 retrying a POST may create duplicate resources." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 907.0477, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 585, + "mcp_result_bytes": 666, + "wire_bytes": 703, + "reported_used_tokens": 666, + "working_set_bytes": 292065280, + "peak_working_set_bytes": 292982784 + }, + { + "query": "custom enterprise root CA not trusted by rustls on Windows", + "ranked": [ + "http-tls-roots", + "http-proxy-env" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S1ANMNG8JZY9DVNMT02Q", + "id": "01M1X0XX8Q9GA65YNPDDJ1KR5N", + "kind": "memory", + "score": 0.9998220801353456, + "summary": "project:fact - [tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle \u2014 the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle." + }, + { + "expansion_handle": "memory:01M1X0S1D51GMG36B98NG7G4HB", + "id": "01M1X0XX8QTPMKNYK6DCE1CHE1", + "kind": "memory", + "score": 0.38715291023254395, + "summary": "project:fact - [tags: http proxy environment reqwest rust corporate] reqwest respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` environment variables by default (with `default-tls` or `rustls-tls`). In a corporate network, these may redirect traffic through an intercepting proxy that breaks mTLS or adds latency. To disable proxy usage entirely: `reqwest::ClientBuilder::no_proxy()`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 877.9808999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1311, + "mcp_result_bytes": 1410, + "wire_bytes": 1447, + "reported_used_tokens": 1410, + "working_set_bytes": 292073472, + "peak_working_set_bytes": 292986880 + }, + { + "query": "parsing server-sent events when a single TCP chunk contains a partial SSE frame", + "ranked": [ + "http-streaming-bodies" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S1BTADS7XCZY4FS8SNEE", + "id": "01M1X0XY493BBANXD216S541FP", + "kind": "memory", + "score": 0.9667426943778992, + "summary": "project:fact - [2026-09-07] [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding \u2014 a chunk may split across frame boundaries." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1227.0287, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 859, + "mcp_result_bytes": 940, + "wire_bytes": 977, + "reported_used_tokens": 940, + "working_set_bytes": 292081664, + "peak_working_set_bytes": 292995072 + }, + { + "query": "reqwest does not use the system proxy settings on Windows", + "ranked": [ + "http-proxy-env", + "http-tls-roots", + "http-connection-pooling", + "http-streaming-bodies" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S1D51GMG36B98NG7G4HB", + "id": "01M1X0XZAFFD4NXS7EZHQ1WA9Q", + "kind": "memory", + "score": 0.9997830986976624, + "summary": "project:fact - [tags: http proxy environment reqwest rust corporate] reqwest respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` environment variables by default (with `default-tls` or `rustls-tls`). In a corporate network, these may redirect traffic through an intercepting proxy that breaks mTLS or adds latency. To disable proxy usage entirely: `reqwest::ClientBuilder::no_proxy()`." + }, + { + "expansion_handle": "memory:01M1X0S1ANMNG8JZY9DVNMT02Q", + "id": "01M1X0XZAF7X7KW6030XFWXYAR", + "kind": "memory", + "score": 0.9808586239814758, + "summary": "project:fact - [tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle \u2014 the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle." + }, + { + "expansion_handle": "memory:01M1X0S16SXG4EPVR2DDCDNNT2", + "id": "01M1X0XZAF4C826C6494SA6GCX", + "kind": "memory", + "score": 0.719273030757904, + "summary": "project:fact - [tags: http reqwest connection-pool keep-alive rust] reqwest's `Client` holds a connection pool; always create ONE `Client` instance and clone it for each handler \u2014 cloning is cheap (Arc under the hood). Creating a `Client::new()` per request defeats connection pooling and causes TCP connection exhaustion under load. The default pool settings: max_idle_per_host=usize::MAX (unbounded), idle_timeout=90s." + }, + { + "expansion_handle": "memory:01M1X0S1BTADS7XCZY4FS8SNEE", + "id": "01M1X0XZAFTMZY68YHV7VHPH5J", + "kind": "memory", + "score": 0.7009692192077637, + "summary": "project:fact - [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding \u2014 a chunk may split across frame boundaries." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 893.6611, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2497, + "mcp_result_bytes": 2632, + "wire_bytes": 2669, + "reported_used_tokens": 2632, + "working_set_bytes": 292093952, + "peak_working_set_bytes": 293003264 + }, + { + "query": "insta snapshot tests fail in CI because output includes a timestamp", + "ranked": [ + "testing-snapshot-churn", + "ci-flaky-quarantine" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S1ED2V912BFX0XSFXN71", + "id": "01M1X0Y07844AN2HE04DF2D1CY", + "kind": "memory", + "score": 0.999855637550354, + "summary": "project:fact - [tags: testing snapshot insta assert churn rust] Snapshot tests (e.g. with the `insta` crate) fail whenever the output changes, even for intended changes. In CI, they fail loudly; locally, `cargo insta review` walks you through accepting or rejecting changes." + }, + { + "expansion_handle": "memory:01M1X0S2CGT9TN4Z78Y5TBVQ41", + "id": "01M1X0Y078CZ45W559JPHDEP1P", + "kind": "memory", + "score": 0.5997360348701477, + "summary": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal \u2014 a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 841.5775, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1196, + "mcp_result_bytes": 1295, + "wire_bytes": 1332, + "reported_used_tokens": 1295, + "working_set_bytes": 292163584, + "peak_working_set_bytes": 293076992 + }, + { + "query": "two test workers writing to the same temp directory path race each other", + "ranked": [ + "testing-temp-dirs-ci" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S1FN8CE8RYDY1CWHJXT3", + "id": "01M1X0Y11C1S76SVT952JWVVE5", + "kind": "memory", + "score": 0.9889234900474548, + "summary": "project:fact - [tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 869.2999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 755, + "mcp_result_bytes": 836, + "wire_bytes": 873, + "reported_used_tokens": 836, + "working_set_bytes": 292167680, + "peak_working_set_bytes": 293076992 + }, + { + "query": "test passes locally but fails on a slow CI runner due to a 100ms sleep", + "ranked": [ + "testing-time-dependent-flakes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S1H1GBMGZBPJ8GQ4CSR2", + "id": "01M1X0Y1WY84NGYA2EM0JAG2MT", + "kind": "memory", + "score": 0.808289110660553, + "summary": "project:fact - [tags: testing time flaky clock mock rust] Tests that depend on wall-clock time are inherently flaky under load (slow CI runners, GC pauses). Abstract time behind a trait (`Clock: Fn() -> SystemTime`) injected at construction, and supply a fake in tests. For tests checking that something happened \"within N seconds\", use a generous multiple of the expected duration (10x is not unreasonable for CI)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 987.3853, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 790, + "mcp_result_bytes": 875, + "wire_bytes": 912, + "reported_used_tokens": 875, + "working_set_bytes": 292171776, + "peak_working_set_bytes": 293093376 + }, + { + "query": "proptest found a hash collision in text normalization that example tests missed", + "ranked": [ + "testing-property-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S1J72QR4DBX90KFA0TBQ", + "id": "01M1X0Y2WF2GT3MPH70Y501GM8", + "kind": "memory", + "score": 0.9994783997535706, + "summary": "project:fact - [tags: testing property-based proptest quickcheck rust] Property-based tests (proptest, quickcheck) find edge cases that example-based tests miss. For kimetsu's memory text normalization, proptest found that zero-width joiner characters and right-to-left marks caused hash collisions. Run proptest with `PROPTEST_CASES=10000` in CI for thorough coverage." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 995.0635, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 744, + "mcp_result_bytes": 825, + "wire_bytes": 862, + "reported_used_tokens": 825, + "working_set_bytes": 292175872, + "peak_working_set_bytes": 293093376 + }, + { + "query": "set_var in tests races when cargo test runs them in parallel", + "ranked": [ + "testing-serial-vs-parallel" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S1KD82X952R9BGBPS244", + "id": "01M1X0Y3TYSJG1PQMN9GGGNYS5", + "kind": "memory", + "score": 0.9997344613075256, + "summary": "project:fact - [2026-09-07] [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 916.1112999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 832, + "mcp_result_bytes": 913, + "wire_bytes": 950, + "reported_used_tokens": 913, + "working_set_bytes": 292179968, + "peak_working_set_bytes": 293101568 + }, + { + "query": "hardcoded JSON fixtures broke after a schema migration", + "ranked": [ + "testing-fixture-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S1Q0NEBQJSWTFZPHE4ZT", + "id": "01M1X0Y4PW4CCJE0MPBX67F9R5", + "kind": "memory", + "score": 0.9998371601104736, + "summary": "project:fact - [2026-09-07] [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 744.6712, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 783, + "mcp_result_bytes": 864, + "wire_bytes": 901, + "reported_used_tokens": 864, + "working_set_bytes": 292192256, + "peak_working_set_bytes": 293101568 + }, + { + "query": "debug print in the MCP handler corrupts the JSON-Lines protocol stream", + "ranked": [ + "mcp-stdout-protocol" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S1R25W8ZJDGRFWG990X7", + "id": "01M1X0Y5E97WMNHTWDJBPPGWXX", + "kind": "memory", + "score": 0.9997472167015076, + "summary": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 903.6366, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 705, + "mcp_result_bytes": 786, + "wire_bytes": 823, + "reported_used_tokens": 786, + "working_set_bytes": 292225024, + "peak_working_set_bytes": 293138432 + }, + { + "query": "kimetsu MCP tool call times out because embedding model is re-initialized every call", + "ranked": [ + "mcp-tool-timeouts", + "mcp-schema-validation", + "kimetsu-bench-remote-embedder-singleton" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S1SAR92694NXY6FKMJ33", + "id": "01M1X0Y6AGNR8VZ4SJHEVHPA1A", + "kind": "memory", + "score": 0.9995898604393004, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + }, + { + "expansion_handle": "memory:01M1X0S1VSK8KGDEPQF3ESYE9F", + "id": "01M1X0Y6AG6F3Y0NFFYWCS7AX8", + "kind": "memory", + "score": 0.6027993559837341, + "summary": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array \u2014 omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error." + }, + { + "expansion_handle": "memory:01M1X0S2SFXFB41K3BPST4ACAR", + "id": "01M1X0Y6AGKGXQXS1TSSQZTKFS", + "kind": "memory", + "score": 0.5117799639701843, + "summary": "project:fact - [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 807.5885000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2085, + "mcp_result_bytes": 2202, + "wire_bytes": 2239, + "reported_used_tokens": 2202, + "working_set_bytes": 292225024, + "peak_working_set_bytes": 293138432 + }, + { + "query": "env var set after host launch is not visible to the MCP server process", + "ranked": [ + "mcp-env-propagation", + "kimetsu-daemon-lifecycle" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S1TJ6VGH2S8KRTZQJT93", + "id": "01M1X0Y73WFQZ16TXCR4X9RQC9", + "kind": "memory", + "score": 0.9984827637672424, + "summary": "project:fact - [2026-09-07] [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment \u2014 changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate." + }, + { + "expansion_handle": "memory:01M1X0S2DSSS5EEX6KZFYDRRNA", + "id": "01M1X0Y73W74JTM6KE5YD2N64Z", + "kind": "memory", + "score": 0.9977922439575196, + "summary": "project:fact - [2026-09-07] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 943.0186, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1267, + "mcp_result_bytes": 1366, + "wire_bytes": 1403, + "reported_used_tokens": 1366, + "working_set_bytes": 292225024, + "peak_working_set_bytes": 293146624 + }, + { + "query": "MCP tool call fails because a required field is missing from the JSON input", + "ranked": [ + "mcp-schema-validation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S1VSK8KGDEPQF3ESYE9F", + "id": "01M1X0Y81CKFQP8KQAD4W6CVG3", + "kind": "memory", + "score": 0.998538613319397, + "summary": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array \u2014 omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 904.784, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 798, + "mcp_result_bytes": 879, + "wire_bytes": 916, + "reported_used_tokens": 879, + "working_set_bytes": 292225024, + "peak_working_set_bytes": 293146624 + }, + { + "query": "Claude Code rejects the tool name with a hyphen in it", + "ranked": [ + "mcp-tool-naming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S1WXQG18AAW2MVWESA3A", + "id": "01M1X0Y8XSY78WT3PZSXXATRZD", + "kind": "memory", + "score": 0.9982439279556274, + "summary": "project:fact - [tags: mcp tool naming convention kimetsu] MCP tool names must be valid identifiers for all host agents. Claude Code restricts tool names to `[a-zA-Z0-9_-]` and max 64 chars. Use `snake_case` (kimetsu_brain_context, kimetsu_brain_record) \u2014 hyphen is technically allowed but some hosts reject it." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 866.3182, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 687, + "mcp_result_bytes": 768, + "wire_bytes": 805, + "reported_used_tokens": 768, + "working_set_bytes": 292331520, + "peak_working_set_bytes": 293244928 + }, + { + "query": "MCP response path uses backslashes and the host rejects it", + "ranked": [ + "mcp-transcript-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S1Y68V1FN5D4FBNXE46N", + "id": "01M1X0Y9RJH2WCANH7XGP3KAGX", + "kind": "memory", + "score": 0.9984637498855592, + "summary": "project:fact - [tags: mcp transcript paths kimetsu hooks runs] kimetsu writes run transcripts to `/.kimetsu/runs//`. The post-session hook reads the latest run's transcript to trigger memory harvest. On Windows, the path uses backslashes internally but the MCP JSON must use forward slashes or the host may reject path-type arguments." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 892.4171, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 724, + "mcp_result_bytes": 805, + "wire_bytes": 842, + "reported_used_tokens": 805, + "working_set_bytes": 292339712, + "peak_working_set_bytes": 293249024 + }, + { + "query": "AWS credentials not found \u2014 which env var does kimetsu read for Bedrock?", + "ranked": [ + "aws-credentials-chain", + "aws-region-resolution", + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S1ZFY1N7K6ABQSCYNE24", + "id": "01M1X0YAMFR7YFBE25TR0WP20A", + "kind": "memory", + "score": 0.9990235567092896, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + }, + { + "expansion_handle": "memory:01M1X0S20Z7NDCY69PTPT27MYZ", + "id": "01M1X0YAMFEG6R09SVGSTBK68X", + "kind": "memory", + "score": 0.9968422651290894, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X0RYH0B8FECY7X9RAA8ZWE", + "id": "01M1X0YAMFAWA7XR437YAGTQ06", + "kind": "memory", + "score": 0.9849756360054016, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X0RYQNNBEV2TCA2CR5B3P8", + "id": "01M1X0YAMFA6D59Q95ZZ8RX9YB", + "kind": "memory", + "score": 0.9203452467918396, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 921.5297999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3455, + "mcp_result_bytes": 3618, + "wire_bytes": 3655, + "reported_used_tokens": 3618, + "working_set_bytes": 292458496, + "peak_working_set_bytes": 293371904 + }, + { + "query": "Bedrock InvokeModel fails because the region is not configured", + "ranked": [ + "aws-region-resolution", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S20Z7NDCY69PTPT27MYZ", + "id": "01M1X0YBH9834JT1YRMK7EVBJ0", + "kind": "memory", + "score": 0.99688321352005, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X0RYQNNBEV2TCA2CR5B3P8", + "id": "01M1X0YBH988BS861XADZHKSD6", + "kind": "memory", + "score": 0.6450709104537964, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 936.1049, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1810, + "mcp_result_bytes": 1929, + "wire_bytes": 1966, + "reported_used_tokens": 1929, + "working_set_bytes": 292474880, + "peak_working_set_bytes": 293392384 + }, + { + "query": "how do I handle ThrottlingException from Bedrock with exponential backoff?", + "ranked": [ + "aws-retry-throttling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S224RJWKJRAY1BV4VMM1", + "id": "01M1X0YCEYY7KCXXEGSYTJ8HCR", + "kind": "memory", + "score": 0.9997082352638244, + "summary": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with \u00b125% jitter." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 965.2108000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 771, + "mcp_result_bytes": 868, + "wire_bytes": 905, + "reported_used_tokens": 868, + "working_set_bytes": 292474880, + "peak_working_set_bytes": 293392384 + }, + { + "query": "generating a presigned S3 URL for brain export without exposing credentials", + "ranked": [ + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S23B6NYNB9ZTRCDV5CN8", + "id": "01M1X0YDCSVG7QHVJRA1HYKCE1", + "kind": "memory", + "score": 0.9990487694740297, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 895.562, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 875, + "mcp_result_bytes": 956, + "wire_bytes": 993, + "reported_used_tokens": 956, + "working_set_bytes": 292564992, + "peak_working_set_bytes": 293478400 + }, + { + "query": "IMDSv2 token required for instance metadata \u2014 PUT before GET", + "ranked": [ + "aws-instance-metadata" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S24M60B12M04NNSFQJ2S", + "id": "01M1X0YE8WX4WNENNCX28VTEW5", + "kind": "memory", + "score": 0.9997182488441468, + "summary": "project:fact - [2026-09-07] [tags: aws imds instance-metadata ec2 token] The AWS Instance Metadata Service v2 (IMDSv2) requires a session token: PUT `http://169.254.169.254/latest/api/token` with `X-aws-ec2-metadata-token-ttl-seconds: 21600` to get a token, then GET metadata with `X-aws-ec2-metadata-token: `. IMDSv1 (no token) is disabled on hardened instances. The metadata endpoint is only reachable from within EC2 \u2014 a connection timeout means you're not on EC2." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 898.2209, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 851, + "mcp_result_bytes": 932, + "wire_bytes": 969, + "reported_used_tokens": 932, + "working_set_bytes": 292573184, + "peak_working_set_bytes": 293486592 + }, + { + "query": "Cargo cache key strategy for GitHub Actions to avoid toolchain version collisions", + "ranked": [ + "ci-cache-keys" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S282DH86Y1GYAT84M520", + "id": "01M1X0YF53JZ54PVJP0NFVRMR6", + "kind": "memory", + "score": 0.998869240283966, + "summary": "project:fact - [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key \u2014 macOS and Windows have incompatible artifact formats." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1320.0636000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 788, + "mcp_result_bytes": 869, + "wire_bytes": 906, + "reported_used_tokens": 869, + "working_set_bytes": 292581376, + "peak_working_set_bytes": 293494784 + }, + { + "query": "CI matrix has 18 jobs and costs too much \u2014 how do I reduce it?", + "ranked": [ + "ci-matrix-explosion" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S294RAAK8EAXTDPF9SJ2", + "id": "01M1X0YGE85X5BDKFW6ZMR2Q6D", + "kind": "memory", + "score": 0.999057948589325, + "summary": "project:fact - [tags: ci github-actions matrix jobs resources] A CI matrix combining OS (3) x Rust toolchain (3) x features (2) = 18 jobs. Each spawns a runner; at $0.008/min for Ubuntu and $0.016/min for Windows, a 10-minute build costs $2.40 per push. Reduce: test the full matrix only on PRs to main; on feature branches, test only Linux+stable." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 933.8376000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 722, + "mcp_result_bytes": 803, + "wire_bytes": 840, + "reported_used_tokens": 803, + "working_set_bytes": 292716544, + "peak_working_set_bytes": 293634048 + }, + { + "query": "GitHub Actions secret accidentally printed in build logs", + "ranked": [ + "ci-secrets-masking", + "ci-cache-keys", + "ci-artifact-retention" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S2A9BNGKZYSQ6BKH2G8K", + "id": "01M1X0YHBZCTR22AXA7T50815T", + "kind": "memory", + "score": 0.9963951706886292, + "summary": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output \u2014 but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable." + }, + { + "expansion_handle": "memory:01M1X0S282DH86Y1GYAT84M520", + "id": "01M1X0YHC0BSV23YJZF8R1YZ23", + "kind": "memory", + "score": 0.4342843890190125, + "summary": "project:fact - [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key \u2014 macOS and Windows have incompatible artifact formats." + }, + { + "expansion_handle": "memory:01M1X0S2BFXRZH49QWV3Q3Y4Z7", + "id": "01M1X0YHBZ2GWKDECKE1J515T1", + "kind": "memory", + "score": 0.3422144949436188, + "summary": "project:fact - [tags: ci github-actions artifacts retention benchmark] GitHub Actions artifacts are retained for 90 days (default). For benchmark results, use `actions/upload-artifact` with `retention-days: 365` for long-term tracking. The free tier has 500MB storage \u2014 per-combo JSON files from kimetsu bench (each ~60KB) add up fast if you upload them on every push." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 904.6710999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1791, + "mcp_result_bytes": 1908, + "wire_bytes": 1945, + "reported_used_tokens": 1908, + "working_set_bytes": 292847616, + "peak_working_set_bytes": 293761024 + }, + { + "query": "how long do GitHub Actions artifacts persist and what's the storage limit?", + "ranked": [ + "ci-artifact-retention" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S2BFXRZH49QWV3Q3Y4Z7", + "id": "01M1X0YJ7JDXR949AWKRBZR6V4", + "kind": "memory", + "score": 0.999624252319336, + "summary": "project:fact - [tags: ci github-actions artifacts retention benchmark] GitHub Actions artifacts are retained for 90 days (default). For benchmark results, use `actions/upload-artifact` with `retention-days: 365` for long-term tracking. The free tier has 500MB storage \u2014 per-combo JSON files from kimetsu bench (each ~60KB) add up fast if you upload them on every push." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 946.9573999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 744, + "mcp_result_bytes": 825, + "wire_bytes": 862, + "reported_used_tokens": 825, + "working_set_bytes": 293130240, + "peak_working_set_bytes": 294047744 + }, + { + "query": "timing-based test flake in CI \u2014 quarantine or fix?", + "ranked": [ + "ci-flaky-quarantine", + "testing-time-dependent-flakes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S2CGT9TN4Z78Y5TBVQ41", + "id": "01M1X0YK55SC6WDW3T6P6PMENN", + "kind": "memory", + "score": 0.9994743466377258, + "summary": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal \u2014 a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output." + }, + { + "expansion_handle": "memory:01M1X0S1H1GBMGZBPJ8GQ4CSR2", + "id": "01M1X0YK55MQ8TSGXMCQFTFV6K", + "kind": "memory", + "score": 0.9849997162818908, + "summary": "project:fact - [tags: testing time flaky clock mock rust] Tests that depend on wall-clock time are inherently flaky under load (slow CI runners, GC pauses). Abstract time behind a trait (`Clock: Fn() -> SystemTime`) injected at construction, and supply a fake in tests. For tests checking that something happened \"within N seconds\", use a generous multiple of the expected duration (10x is not unreasonable for CI)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 897.9669, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1340, + "mcp_result_bytes": 1443, + "wire_bytes": 1480, + "reported_used_tokens": 1443, + "working_set_bytes": 293273600, + "peak_working_set_bytes": 294195200 + }, + { + "query": "kimetsu doctor says the MCP server is running \u2014 how do I stop it before an update?", + "ranked": [ + "kimetsu-daemon-lifecycle", + "mcp-env-propagation", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S2DSSS5EEX6KZFYDRRNA", + "id": "01M1X0YM196T8D2AT32F7GB0XZ", + "kind": "memory", + "score": 0.9989782571792604, + "summary": "project:fact - [2026-09-07] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1X0S1TJ6VGH2S8KRTZQJT93", + "id": "01M1X0YM191DEZWG4TFX67TQPZ", + "kind": "memory", + "score": 0.9049031734466552, + "summary": "project:fact - [2026-09-07] [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment \u2014 changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate." + }, + { + "expansion_handle": "memory:01M1X0RYD13DKH4PFCSN9N3JVS", + "id": "01M1X0YM1A4XX4SPKETSJ59SV6", + "kind": "memory", + "score": 0.4812128245830536, + "summary": "project:fact - [2026-09-07] [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 922.2891, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2046, + "mcp_result_bytes": 2211, + "wire_bytes": 2248, + "reported_used_tokens": 2211, + "working_set_bytes": 293298176, + "peak_working_set_bytes": 294211584 + }, + { + "query": "noise capsules consuming token budget without contributing retrieval signal", + "ranked": [ + "kimetsu-capsule-budgets" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S2EZAX6QBY1851YFGVCN", + "id": "01M1X0YMY46JZNC9S6R0X8SV7F", + "kind": "memory", + "score": 0.9997420907020568, + "summary": "project:fact - [tags: kimetsu capsule tokens budget retrieval] kimetsu retrieval enforces a token budget per capsule type: memory capsules are capped at 6000 tokens total (across all retrieved memories), file capsules at 3000 tokens. When a memory is large and would exceed the budget, it is truncated at a sentence boundary. The budget is enforced AFTER reranking \u2014 reranking may reorder results so that a truncated high-ranked memory displaces a full lower-ranked one." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 758.4157, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 847, + "mcp_result_bytes": 928, + "wire_bytes": 965, + "reported_used_tokens": 928, + "working_set_bytes": 293335040, + "peak_working_set_bytes": 294248448 + }, + { + "query": "kimetsu_brain_record writes to the wrong brain location \u2014 user vs project scope", + "ranked": [ + "kimetsu-memory-scopes", + "kimetsu-write-tools-gate", + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S2G2687MBFR5WRACKDHK", + "id": "01M1X0YNP1YG0H02JPC7JJ12HS", + "kind": "memory", + "score": 0.999030828475952, + "summary": "project:fact - [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available \u2014 if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope." + }, + { + "expansion_handle": "memory:01M1X0S2KNSRADJJPEW568DX62", + "id": "01M1X0YNP1PHS9VVFCF0RQ2WM8", + "kind": "memory", + "score": 0.9838979840278624, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level \u2014 disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1X0RYV389BQAB7HQN0MA99V", + "id": "01M1X0YNP1YXPAYSXAKC8AV70T", + "kind": "memory", + "score": 0.3852712512016296, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 874.0636000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2098, + "mcp_result_bytes": 2215, + "wire_bytes": 2252, + "reported_used_tokens": 2215, + "working_set_bytes": 293380096, + "peak_working_set_bytes": 294289408 + }, + { + "query": "how do I configure kimetsu to use Claude Haiku for harvesting but Opus for the agent?", + "ranked": [ + "kimetsu-distiller-config" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S2HADV90PNV3X81DE5VS", + "id": "01M1X0YPHA0AHRSFGDFWATKKTY", + "kind": "memory", + "score": 0.9989088773727416, + "summary": "project:fact - [tags: kimetsu distiller harvest config provider] The kimetsu distiller (auto-harvester) uses a SEPARATE provider configuration from the main agent: `distiller.provider`, `distiller.model`, `distiller.api_key`. This allows running the agent on an expensive model (Claude Opus) while harvesting with a cheap model (Claude Haiku). If `distiller.provider` is not set, it inherits `provider`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 927.5934, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 778, + "mcp_result_bytes": 859, + "wire_bytes": 896, + "reported_used_tokens": 859, + "working_set_bytes": 293445632, + "peak_working_set_bytes": 294359040 + }, + { + "query": "first agent turn is slow because kimetsu proactive hook runs embedding inference", + "ranked": [ + "kimetsu-proactive-hooks", + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S2JH9AWTYF2E28N2ER76", + "id": "01M1X0YQEV3AZKBAHXJ630ARFB", + "kind": "memory", + "score": 0.999568521976471, + "summary": "project:fact - [2026-09-07] [tags: kimetsu proactive hooks context injection] kimetsu's proactive context injection runs before each agent turn (pre-turn hook) and injects relevant memories into the system prompt prefix. The hook invocation adds latency to the first token: embedding inference + vector search + reranking + context formatting. On a cold start, this can be 1-3 seconds." + }, + { + "expansion_handle": "memory:01M1X0S1SAR92694NXY6FKMJ33", + "id": "01M1X0YQEVK10MNPZBZ62RKTQA", + "kind": "memory", + "score": 0.9405298233032228, + "summary": "project:fact - [2026-09-07] [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 957.9834000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1403, + "mcp_result_bytes": 1502, + "wire_bytes": 1539, + "reported_used_tokens": 1502, + "working_set_bytes": 293462016, + "peak_working_set_bytes": 294375424 + }, + { + "query": "make the kimetsu brain read-only for certain repos on a shared remote server", + "ranked": [ + "kimetsu-write-tools-gate", + "remote-ingest-split-roots", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S2KNSRADJJPEW568DX62", + "id": "01M1X0YRCGCFWQ242CCGNANS16", + "kind": "memory", + "score": 0.997682809829712, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level \u2014 disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1X0RYAH80HT0J4XD4TW3J26", + "id": "01M1X0YRCGDWDGQ5EFRHP40DFJ", + "kind": "memory", + "score": 0.9957050681114196, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1X0RYD13DKH4PFCSN9N3JVS", + "id": "01M1X0YRCG1DXT7KERE7X3SVDC", + "kind": "memory", + "score": 0.9909282326698304, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 894.8382, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2725, + "mcp_result_bytes": 2890, + "wire_bytes": 2927, + "reported_used_tokens": 2890, + "working_set_bytes": 293523456, + "peak_working_set_bytes": 294436864 + }, + { + "query": "kimetsu FTS search misses 'deadlocking' when memory says 'deadlock'", + "ranked": [ + "kimetsu-query-stemming", + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S2PZHHMGHW25YMR7CTJ2", + "id": "01M1X0YSA8HMR154AM29Z56BAM", + "kind": "memory", + "score": 0.9904030561447144, + "summary": "project:fact - [2026-09-07] [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression." + }, + { + "expansion_handle": "memory:01M1X0RY96B4DJ5PBZXCAERB1C", + "id": "01M1X0YSA9BR7PRC3A9RZN7BNS", + "kind": "memory", + "score": 0.91664320230484, + "summary": "project:fact - [2026-09-07] [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure \u2014 `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 920.582, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1363, + "mcp_result_bytes": 1478, + "wire_bytes": 1515, + "reported_used_tokens": 1478, + "working_set_bytes": 293535744, + "peak_working_set_bytes": 294445056 + }, + { + "query": "how does pool size affect retrieval recall and latency in the bench?", + "ranked": [ + "kimetsu-rerank-pool" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S2RAMMBQTQ0BN46XW46J", + "id": "01M1X0YT5283PFGBD5B314JR2C", + "kind": "memory", + "score": 0.9998373985290528, + "summary": "project:fact - [tags: kimetsu reranker pool size ann retrieval] kimetsu's retrieval pipeline: ANN (approximate nearest neighbor) retrieves a pool of candidates, then the reranker reorders them, then the top-K are returned. The pool size (default 6 for production, 12 in bench) controls the recall-latency tradeoff: larger pool = higher recall = more reranker calls = more latency. For the jina-tiny reranker, pool 12 adds ~80ms vs pool 6." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 896.4978, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 813, + "mcp_result_bytes": 894, + "wire_bytes": 931, + "reported_used_tokens": 894, + "working_set_bytes": 293556224, + "peak_working_set_bytes": 294473728 + }, + { + "query": "second embedder in a remote bench run gets worse results than the first", + "ranked": [ + "kimetsu-bench-remote-embedder-singleton" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S2SFXFB41K3BPST4ACAR", + "id": "01M1X0YV1CRAVMYE0VVDSRCSC9", + "kind": "memory", + "score": 0.9939629435539246, + "summary": "project:fact - [2026-09-07] [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 900.8557999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 895, + "mcp_result_bytes": 976, + "wire_bytes": 1013, + "reported_used_tokens": 976, + "working_set_bytes": 293556224, + "peak_working_set_bytes": 294473728 + }, + { + "query": "what is the expected JSON schema for kimetsu brain bench dataset files?", + "ranked": [ + "kimetsu-eval-fixture-shape", + "testing-fixture-drift", + "kimetsu-mrr-metric", + "mcp-schema-validation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S2TQ6WSXCCQJX516YNBR", + "id": "01M1X0YVXDRE8Z95EG2E0JC2HA", + "kind": "memory", + "score": 0.9996767044067384, + "summary": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` \u2014 a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases)." + }, + { + "expansion_handle": "memory:01M1X0S1Q0NEBQJSWTFZPHE4ZT", + "id": "01M1X0YVXDRP9R9255GAN6NS17", + "kind": "memory", + "score": 0.9682880640029908, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + }, + { + "expansion_handle": "memory:01M1X0S2W226CMQKAMAGT925D4", + "id": "01M1X0YVXDY4SWBFYVT5S835A8", + "kind": "memory", + "score": 0.8818408250808716, + "summary": "project:fact - [tags: kimetsu bench mrr recall metrics evaluation] kimetsu bench reports MRR (Mean Reciprocal Rank) and Recall@K. MRR is 1/rank_of_first_relevant_result, averaged across cases; it penalizes models that rank the correct answer 2nd or 3rd. Recall@K is the fraction of cases where at least one relevant answer appears in the top K." + }, + { + "expansion_handle": "memory:01M1X0S1VSK8KGDEPQF3ESYE9F", + "id": "01M1X0YVXDMV1XF2VNNV9Z4H2N", + "kind": "memory", + "score": 0.6527947187423706, + "summary": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array \u2014 omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 944.8191, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2424, + "mcp_result_bytes": 2603, + "wire_bytes": 2640, + "reported_used_tokens": 2603, + "working_set_bytes": 293556224, + "peak_working_set_bytes": 294473728 + }, + { + "query": "what does MRR mean and how do I interpret a 0.01 difference between combos?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 957.6502, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 228, + "mcp_result_bytes": 291, + "wire_bytes": 328, + "reported_used_tokens": 291, + "working_set_bytes": 293560320, + "peak_working_set_bytes": 294473728 + }, + { + "query": "SQLITE_BUSY keeps appearing even with WAL mode enabled", + "ranked": [ + "sqlite-busy-timeout-wal", + "sqlite-wal-network-drive" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZ695HPYBS44T4Z7G5RR", + "id": "01M1X0YXT58TEZS5ABCJMXAVP1", + "kind": "memory", + "score": 0.9982662796974182, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + }, + { + "expansion_handle": "memory:01M1X0RZ9WFMZG0GGATBEZ1Z23", + "id": "01M1X0YXT5WTM2E329F5FF4BMZ", + "kind": "memory", + "score": 0.7844027280807495, + "summary": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1015.5475, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1423, + "mcp_result_bytes": 1522, + "wire_bytes": 1559, + "reported_used_tokens": 1522, + "working_set_bytes": 293646336, + "peak_working_set_bytes": 294559744 + }, + { + "query": "my brain file got huge again right after I compacted it", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 932.5572, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 293646336, + "peak_working_set_bytes": 294559744 + }, + { + "query": "all my FTS queries stopped returning results after I changed the tokenizer config", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 940.3361, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 293658624, + "peak_working_set_bytes": 294572032 + }, + { + "query": "something is preventing the kimetsu binary from being replaced during update", + "ranked": [ + "kimetsu-daemon-lifecycle", + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S2DSSS5EEX6KZFYDRRNA", + "id": "01M1X0Z0K7DBXEBVX3XGC460AC", + "kind": "memory", + "score": 0.9678457975387572, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1X0RZ3Y82J7DVBNF9HN837A", + "id": "01M1X0Z0K7TQ6DM1CW3T8H286W", + "kind": "memory", + "score": 0.9395453929901124, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 0.6666666666666666, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 863.8702999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1657, + "mcp_result_bytes": 1756, + "wire_bytes": 1793, + "reported_used_tokens": 1756, + "working_set_bytes": 293662720, + "peak_working_set_bytes": 294576128 + }, + { + "query": "tool call results not appearing in the context \u2014 is the semantic floor too high?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 965.7805999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 228, + "mcp_result_bytes": 291, + "wire_bytes": 328, + "reported_used_tokens": 291, + "working_set_bytes": 293662720, + "peak_working_set_bytes": 294584320 + }, + { + "query": "CARGO_INCREMENTAL=0 in CI prevents a class of spurious compilation errors", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZQXJ5FN0W81RATRACYX", + "id": "01M1X0Z2CYKRXDBJS14259JNX7", + "kind": "memory", + "score": 0.7995238304138184, + "summary": "project:fact - [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 951.6261000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 877, + "mcp_result_bytes": 958, + "wire_bytes": 995, + "reported_used_tokens": 958, + "working_set_bytes": 293732352, + "peak_working_set_bytes": 294649856 + }, + { + "query": "how do I check whether my Cargo workspace respects the MSRV constraint?", + "ranked": [ + "cargo-msrv", + "cargo-dev-dep-leak", + "cargo-patch-section", + "cargo-target-dir-sharing" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZVDK0XKD0VSZBMK5RYW", + "id": "01M1X0Z3AYGX3SND53MRK43BJF", + "kind": "memory", + "score": 0.9921064376831056, + "summary": "project:fact - [tags: cargo rust msrv edition compatibility] Set `rust-version` in each `Cargo.toml` to declare the minimum supported Rust version (MSRV). Cargo enforces this with `--check`: `cargo check` fails if the toolchain is older than `rust-version`. Keep MSRV as old as your oldest supported deployment target." + }, + { + "expansion_handle": "memory:01M1X0RZN0RW2M8XPRGDVPM9FC", + "id": "01M1X0Z3AZ7P3FKM7X08E8Q64P", + "kind": "memory", + "score": 0.887407660484314, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + }, + { + "expansion_handle": "memory:01M1X0RZT8GNTG2NX4TSXZ1DYS", + "id": "01M1X0Z3AZ5X61VG482VDAYY4H", + "kind": "memory", + "score": 0.7220955491065979, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace \u2014 including transitive deps \u2014 that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1X0RZPJXGMPZEWZSG7YHJTV", + "id": "01M1X0Z3AZF5KWAS40X294NJQK", + "kind": "memory", + "score": 0.4095200598239898, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps \u2014 use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 994.891, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2682, + "mcp_result_bytes": 2821, + "wire_bytes": 2858, + "reported_used_tokens": 2821, + "working_set_bytes": 293756928, + "peak_working_set_bytes": 294678528 + }, + { + "query": "rusqlite connection opened but ON DELETE CASCADE cascade never fires", + "ranked": [ + "sqlite-foreign-keys-default-off" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0RZDJ4CCR1ZDZZZCNCB6P", + "id": "01M1X0Z4AKXMG3BJTZA2ABSY78", + "kind": "memory", + "score": 0.9922945499420166, + "summary": "project:fact - [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting \u2014 every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 975.7312000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 735, + "mcp_result_bytes": 816, + "wire_bytes": 853, + "reported_used_tokens": 816, + "working_set_bytes": 293810176, + "peak_working_set_bytes": 294707200 + }, + { + "query": "I cannot connect to kimetsu-remote \u2014 something about TLS cert validation failed", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1030.0658, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 293830656, + "peak_working_set_bytes": 294748160 + }, + { + "query": "graceful shutdown fails because in-flight SQLite queries are still running when pool closes", + "ranked": [ + "tokio-shutdown-ordering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S15HR0X7TN920GCR1P5Q", + "id": "01M1X0Z6A0XTYCTDYK753DX83Z", + "kind": "memory", + "score": 0.9996342658996582, + "summary": "project:fact - [2026-09-07] [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries \u2014 the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 998.404, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 947, + "mcp_result_bytes": 1028, + "wire_bytes": 1065, + "reported_used_tokens": 1028, + "working_set_bytes": 293851136, + "peak_working_set_bytes": 294764544 + }, + { + "query": "kimetsu-remote response takes 8 seconds \u2014 which stage is slow?", + "ranked": [ + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S1SAR92694NXY6FKMJ33", + "id": "01M1X0Z78QYPY3NE32J17E6K5E", + "kind": "memory", + "score": 0.9876242876052856, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 995.5110000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 858, + "mcp_result_bytes": 939, + "wire_bytes": 976, + "reported_used_tokens": 939, + "working_set_bytes": 294178816, + "peak_working_set_bytes": 295084032 + }, + { + "query": "git reflog to rescue accidentally deleted branch", + "ranked": [ + "git-reflog-rescue" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S0V9GB5KCN0SD1FF7GKG", + "id": "01M1X0Z871FM0WT2B8HD7051MW", + "kind": "memory", + "score": 0.998464822769165, + "summary": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone \u2014 they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only \u2014 remote reflog is not accessible via normal git commands." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 989.5199, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 761, + "mcp_result_bytes": 842, + "wire_bytes": 879, + "reported_used_tokens": 842, + "working_set_bytes": 294178816, + "peak_working_set_bytes": 295088128 + }, + { + "query": "git submodule --remote advances the pinned SHA unexpectedly", + "ranked": [ + "git-submodule-pinning", + "git-reflog-rescue", + "ci-secrets-masking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S0T3PSASF7293R9DKD26", + "id": "01M1X0Z95TQYAP202DRGKFF58Y", + "kind": "memory", + "score": 0.9998551607131958, + "summary": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip \u2014 this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version." + }, + { + "expansion_handle": "memory:01M1X0S0V9GB5KCN0SD1FF7GKG", + "id": "01M1X0Z95TN8C0SQPC180EP8PH", + "kind": "memory", + "score": 0.8857361078262329, + "summary": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone \u2014 they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only \u2014 remote reflog is not accessible via normal git commands." + }, + { + "expansion_handle": "memory:01M1X0S2A9BNGKZYSQ6BKH2G8K", + "id": "01M1X0Z95T5131CN6CQ87HET7W", + "kind": "memory", + "score": 0.8434544205665588, + "summary": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output \u2014 but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 920.384, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1771, + "mcp_result_bytes": 1888, + "wire_bytes": 1925, + "reported_used_tokens": 1888, + "working_set_bytes": 294178816, + "peak_working_set_bytes": 295088128 + }, + { + "query": "axum SSE streaming drops the last event when client disconnects", + "ranked": [ + "http-streaming-bodies" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S1BTADS7XCZY4FS8SNEE", + "id": "01M1X0ZA2WFQJCAEHTJ7QG75TW", + "kind": "memory", + "score": 0.9926375150680542, + "summary": "project:fact - [2026-09-07] [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding \u2014 a chunk may split across frame boundaries." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1013.1637, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 859, + "mcp_result_bytes": 940, + "wire_bytes": 977, + "reported_used_tokens": 940, + "working_set_bytes": 294182912, + "peak_working_set_bytes": 295096320 + }, + { + "query": "how do I detect that I am running inside a git worktree vs the main checkout?", + "ranked": [ + "git-worktree-brain-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S0N2RNCRZ1Z1FT2DDS51", + "id": "01M1X0ZB2ZYRR7XP3RZA92GDQ5", + "kind": "memory", + "score": 0.9857924580574036, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root \u2014 if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1034.6117, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 881, + "mcp_result_bytes": 962, + "wire_bytes": 999, + "reported_used_tokens": 962, + "working_set_bytes": 294187008, + "peak_working_set_bytes": 295104512 + }, + { + "query": "ONNX Runtime intra-op threads causing CPU contention during parallel bench", + "ranked": [ + "onnx-ort-threading", + "tokio-blocking-in-async" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S0KT5FASD3GD1V6Z619Q", + "id": "01M1X0ZC2FWHCR16DJM5Y614VJ", + "kind": "memory", + "score": 0.9999210834503174, + "summary": "project:fact - [tags: onnx ort thread-pool parallelism cpu] ORT (ONNX Runtime) creates its own inter-op and intra-op thread pools. In a multi-process bench setup, each child inherits these pools and they compete for CPU cores. Set `SessionOptionsBuilder::with_intra_threads(1).with_inter_threads(1)` if you're running many parallel bench processes \u2014 this sacrifices per-inference throughput for lower contention." + }, + { + "expansion_handle": "memory:01M1X0S0WE8VV1EREBQ8JXTVHF", + "id": "01M1X0ZC2FHJ4M58WDXTY5VSAW", + "kind": "memory", + "score": 0.5390238761901855, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 854.3838000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1328, + "mcp_result_bytes": 1427, + "wire_bytes": 1464, + "reported_used_tokens": 1427, + "working_set_bytes": 294187008, + "peak_working_set_bytes": 295104512 + }, + { + "query": "what is the right way to supply AWS session token alongside access key and secret?", + "ranked": [ + "aws-credentials-chain" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0S1ZFY1N7K6ABQSCYNE24", + "id": "01M1X0ZCWZJ3FJZEQA71SYPTF4", + "kind": "memory", + "score": 0.9493365287780762, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 906.0455, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 895, + "mcp_result_bytes": 976, + "wire_bytes": 1013, + "reported_used_tokens": 976, + "working_set_bytes": 294187008, + "peak_working_set_bytes": 295104512 + } + ], + "id": "existing-development-100", + "dimension": "retrieval", + "tier": "hard", + "score": 0.8182539682539681, + "skipped": false, + "detail": "positive-recall@4=0.84 mrr=0.85 stale-hit=n/a resolution=n/a false-injection=0.538 (n=13) positive-n=197 negative-n=13 (210 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 0.8182539682539681, + 1 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 0.8182539682539681, + "n": 1, + "ci95": null + } + }, + "overall_index": 0.8182539682539681, + "scenario_weighted_index": 0.8182539682539681 +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-retrieval/development/2-candidate.json b/docs/audits/2026-09-07-retrieval/development/2-candidate.json new file mode 100644 index 0000000..616735d --- /dev/null +++ b/docs/audits/2026-09-07-retrieval/development/2-candidate.json @@ -0,0 +1,6391 @@ +{ + "generated_at": "2026-09-07T04:07:11.6772144Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\tmp-tests\\brainbench-development-100.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "test_env_lock inside with_user_brain_disabled deadlock", + "ranked": [ + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FCEFP4GRHY8D3PHNZVNZ", + "id": "01M1X0FKMVQ2352GN2MJNDXWYA", + "kind": "memory", + "score": 0.9999797344207764, + "summary": "project:fact - [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure \u2014 `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2759.1429000000003, + "first_query": true, + "server_startup_ms": 98.5393, + "model_text_bytes": 796, + "mcp_result_bytes": 877, + "wire_bytes": 912, + "reported_used_tokens": 877, + "working_set_bytes": 687398912, + "peak_working_set_bytes": 688295936 + }, + { + "query": "why does my test hang after calling with_user_brain_disabled when I also lock test_env_lock?", + "ranked": [ + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FCEFP4GRHY8D3PHNZVNZ", + "id": "01M1X0FMQ1QVZRDQQ00EH70R1G", + "kind": "memory", + "score": 0.9996507167816162, + "summary": "project:fact - [2026-09-07] [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure \u2014 `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1284.3522, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 808, + "mcp_result_bytes": 889, + "wire_bytes": 924, + "reported_used_tokens": 889, + "working_set_bytes": 704000000, + "peak_working_set_bytes": 704929792 + }, + { + "query": "ingest_repo_at_root brain_root files_root kimetsu remote", + "ranked": [ + "remote-ingest-split-roots", + "kimetsu-write-tools-gate", + "remote-mcp-host-wiring", + "kimetsu-bench-remote-embedder-singleton" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FCFT22SRB88D2ZQV8BRK", + "id": "01M1X0FNZTRH7RGE0T1F1P74VW", + "kind": "memory", + "score": 0.9999620914459229, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1X0FH76GS665H39MXQKH81R", + "id": "01M1X0FNZT7N3XBRQAPEXCZ3QH", + "kind": "memory", + "score": 0.9088719487190248, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level \u2014 disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1X0FCJ72EZEB2W227G8E2PV", + "id": "01M1X0FNZTPV9PGSHGH1CEEPE0", + "kind": "memory", + "score": 0.8598380088806152, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + }, + { + "expansion_handle": "memory:01M1X0FHDM9E41829DQ14VK8XT", + "id": "01M1X0FNZTK4F4DJFGW3VMWZT8", + "kind": "memory", + "score": 0.6798340678215027, + "summary": "project:fact - [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1723.588, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3381, + "mcp_result_bytes": 3564, + "wire_bytes": 3599, + "reported_used_tokens": 3564, + "working_set_bytes": 829202432, + "peak_working_set_bytes": 830111744 + }, + { + "query": "why does the remote server index the wrong directory when I run kimetsu brain ingest?", + "ranked": [ + "remote-ingest-split-roots", + "git-sparse-checkout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FCFT22SRB88D2ZQV8BRK", + "id": "01M1X0FQN2FHFMD5JG9CGDS6QF", + "kind": "memory", + "score": 0.9676534533500672, + "summary": "project:fact - [2026-09-07] [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1X0FFAJQ9WXZWCPRAXYDE0J", + "id": "01M1X0FQN2KW4SJEBVBAV4BGNJ", + "kind": "memory", + "score": 0.6956315040588379, + "summary": "project:fact - [2026-09-07] [tags: git sparse-checkout partial-clone bandwidth] `git sparse-checkout init --cone` combined with `git clone --filter=blob:none` (partial clone) fetches only the commit graph and tree objects, not blobs. Individual blobs are fetched on demand when accessed. This cuts clone time for large repos from minutes to seconds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1790.7256, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1770, + "mcp_result_bytes": 1869, + "wire_bytes": 1904, + "reported_used_tokens": 1869, + "working_set_bytes": 921305088, + "peak_working_set_bytes": 922234880 + }, + { + "query": "kimetsu plugin install --remote mcp.json authorization bearer token", + "ranked": [ + "remote-mcp-host-wiring", + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FCJ72EZEB2W227G8E2PV", + "id": "01M1X0FSD410088ZBEDWMN4DV3", + "kind": "memory", + "score": 0.9999654293060304, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + }, + { + "expansion_handle": "memory:01M1X0FCSYC83J9JBNY21XF5N1", + "id": "01M1X0FSD48JPW6RFWJ9FW3BS8", + "kind": "memory", + "score": 0.9509484171867372, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1745.4115, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1572, + "mcp_result_bytes": 1727, + "wire_bytes": 1762, + "reported_used_tokens": 1727, + "working_set_bytes": 921403392, + "peak_working_set_bytes": 922316800 + }, + { + "query": "how do I wire a remote kimetsu brain into Claude Code without storing the token in the config file?", + "ranked": [ + "remote-mcp-host-wiring", + "bedrock-kimetsu-provider", + "mcp-tool-naming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FCJ72EZEB2W227G8E2PV", + "id": "01M1X0FV3PMH3ZZR3YCMETXDYN", + "kind": "memory", + "score": 0.9997368454933168, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + }, + { + "expansion_handle": "memory:01M1X0FCP7QJPKBMCYASHDFZC7", + "id": "01M1X0FV3PS4WQQWZR5VMM97YE", + "kind": "memory", + "score": 0.9267048239707948, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X0FGG7B802AF0HEGPBJMGV", + "id": "01M1X0FV3PN7P9XXE6YY5JFBFV", + "kind": "memory", + "score": 0.6635663509368896, + "summary": "project:fact - [tags: mcp tool naming convention kimetsu] MCP tool names must be valid identifiers for all host agents. Claude Code restricts tool names to `[a-zA-Z0-9_-]` and max 64 chars. Use `snake_case` (kimetsu_brain_context, kimetsu_brain_record) \u2014 hyphen is technically allowed but some hosts reject it." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1537.8044, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2430, + "mcp_result_bytes": 2603, + "wire_bytes": 2638, + "reported_used_tokens": 2603, + "working_set_bytes": 924069888, + "peak_working_set_bytes": 924991488 + }, + { + "query": "cargo feature unification kimetsu-brain embeddings fastembed test failure", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-dev-dep-leak", + "cargo-profile-override", + "clap-version-build-flavor" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FCMGF9PH24HGZV9KENFV", + "id": "01M1X0FWKB7JASF33BQ5WCKYWR", + "kind": "memory", + "score": 0.9999688863754272, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X0FDSYBHHE1BWJEBE0P4AZ", + "id": "01M1X0FWKBD0ENPSCCJFQ633ZZ", + "kind": "memory", + "score": 0.6766564249992371, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + }, + { + "expansion_handle": "memory:01M1X0FDXBKT432V6HFK7JTA3K", + "id": "01M1X0FWKBTVNSFJEK63Y6YYD8", + "kind": "memory", + "score": 0.667348325252533, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1X0FD1AF8EJPFVTZ239ESCW", + "id": "01M1X0FWKBKMJFJ29TVCYAPNS6", + "kind": "memory", + "score": 0.6029285192489624, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1348.8934000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3090, + "mcp_result_bytes": 3245, + "wire_bytes": 3280, + "reported_used_tokens": 3245, + "working_set_bytes": 924938240, + "peak_working_set_bytes": 925859840 + }, + { + "query": "my integration tests pass in isolation but break when I run cargo test --workspace \u2014 embedder changed?", + "ranked": [ + "cargo-feature-unification-embeddings" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FCMGF9PH24HGZV9KENFV", + "id": "01M1X0FXY0E02G44D7WZ5P29G8", + "kind": "memory", + "score": 0.9974289536476136, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1455.6739, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1147, + "mcp_result_bytes": 1232, + "wire_bytes": 1267, + "reported_used_tokens": 1232, + "working_set_bytes": 925048832, + "peak_working_set_bytes": 925970432 + }, + { + "query": "build_anthropic_body bedrock-2023-05-31 InvokeModel blocking reqwest", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FCP7QJPKBMCYASHDFZC7", + "id": "01M1X0FZB6SRY9Q4ASY1EX3HQ2", + "kind": "memory", + "score": 0.9999423027038574, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X0FCX344YDYSFM8JDDK4G1", + "id": "01M1X0FZB6HD5PQTVBJPHQCF3T", + "kind": "memory", + "score": 0.9967412352561952, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1343.0772, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2193, + "mcp_result_bytes": 2320, + "wire_bytes": 2356, + "reported_used_tokens": 2320, + "working_set_bytes": 925159424, + "peak_working_set_bytes": 926072832 + }, + { + "query": "how do I add AWS Bedrock as a model provider in Kimetsu without pulling in the aws-sdk?", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-region-resolution", + "aws-sigv4-bedrock-blocking", + "aws-retry-throttling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FCP7QJPKBMCYASHDFZC7", + "id": "01M1X0G0ND65DFN60TS7DXKAE2", + "kind": "memory", + "score": 0.9999690055847168, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X0FGMCP8YBCDTS3WYEGCXD", + "id": "01M1X0G0NDCBT2N1TNTH1VDFWC", + "kind": "memory", + "score": 0.9984136819839478, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X0FCX344YDYSFM8JDDK4G1", + "id": "01M1X0G0NDH6ZE5M458BV9JJWQ", + "kind": "memory", + "score": 0.9974077343940736, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1X0FGNK72S7ATWPNR442PEP", + "id": "01M1X0G0NDTAEH9MFNZHYD77R1", + "kind": "memory", + "score": 0.9369313716888428, + "summary": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with \u00b125% jitter." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1537.1117000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3330, + "mcp_result_bytes": 3509, + "wire_bytes": 3545, + "reported_used_tokens": 3509, + "working_set_bytes": 925683712, + "peak_working_set_bytes": 926601216 + }, + { + "query": "BridgeTarget enum seams plugin_install_inner plugin_status_inner resolve_setup_hosts", + "ranked": [ + "bridge-target-enum-seams", + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FCRJX7EACVPMGWA07XWD", + "id": "01M1X0G25FXZHKYB96WZS36SSZ", + "kind": "memory", + "score": 0.9999773502349854, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + }, + { + "expansion_handle": "memory:01M1X0FCSYC83J9JBNY21XF5N1", + "id": "01M1X0G25FJM979M2CQTYEV39H", + "kind": "memory", + "score": 0.7292461395263672, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1560.7798000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1636, + "mcp_result_bytes": 1743, + "wire_bytes": 1779, + "reported_used_tokens": 1743, + "working_set_bytes": 925868032, + "peak_working_set_bytes": 926781440 + }, + { + "query": "I added a new host to the bridge enum but cargo gives me compile errors in five different match arms \u2014 what did I miss?", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FCRJX7EACVPMGWA07XWD", + "id": "01M1X0G3PT694B5RA5XTE05DFT", + "kind": "memory", + "score": 0.997710347175598, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1797.757, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1059, + "mcp_result_bytes": 1140, + "wire_bytes": 1176, + "reported_used_tokens": 1140, + "working_set_bytes": 926126080, + "peak_working_set_bytes": 927039488 + }, + { + "query": "Pi extension factory defineExtension agent_end session_shutdown kimetsu.ts", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FCSYC83J9JBNY21XF5N1", + "id": "01M1X0G5EN64CZDK5NEJJ37J9G", + "kind": "memory", + "score": 0.9995530247688292, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1786.7749000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 804, + "mcp_result_bytes": 893, + "wire_bytes": 929, + "reported_used_tokens": 893, + "working_set_bytes": 926150656, + "peak_working_set_bytes": 927064064 + }, + { + "query": "how does Pi (earendil-works/pi) load plugins and what lifecycle hooks does it expose?", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FCSYC83J9JBNY21XF5N1", + "id": "01M1X0G76CJV8Q110NFGCTZWY0", + "kind": "memory", + "score": 0.9957007765769958, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1821.1679, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 803, + "mcp_result_bytes": 892, + "wire_bytes": 928, + "reported_used_tokens": 892, + "working_set_bytes": 926474240, + "peak_working_set_bytes": 927383552 + }, + { + "query": "aws-sigv4 SigningParams apply_to_request_http1x reqwest sign-http", + "ranked": [ + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider", + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FCX344YDYSFM8JDDK4G1", + "id": "01M1X0G914S13Z03THG2SJCA1Q", + "kind": "memory", + "score": 0.999910831451416, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1X0FCP7QJPKBMCYASHDFZC7", + "id": "01M1X0G91470G229KZ9K7DPNK8", + "kind": "memory", + "score": 0.9909924268722534, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X0FGPV1QN2NJJ22691MFGC", + "id": "01M1X0G914MYDPNTTA79GRR2RE", + "kind": "memory", + "score": 0.9447544813156128, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1467.8124, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2839, + "mcp_result_bytes": 2984, + "wire_bytes": 3020, + "reported_used_tokens": 2984, + "working_set_bytes": 926478336, + "peak_working_set_bytes": 927383552 + }, + { + "query": "how do I sign a Bedrock InvokeModel request with aws-sigv4 in blocking Rust?", + "ranked": [ + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider", + "aws-presigned-urls", + "aws-credentials-chain" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FCX344YDYSFM8JDDK4G1", + "id": "01M1X0GAD54ZYS4EBXA0BJCQKH", + "kind": "memory", + "score": 0.99993896484375, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1X0FCP7QJPKBMCYASHDFZC7", + "id": "01M1X0GAD5NBABGXBY7SY8P1WA", + "kind": "memory", + "score": 0.9999256134033204, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X0FGPV1QN2NJJ22691MFGC", + "id": "01M1X0GAD54XWKYSRT8XZVRWMF", + "kind": "memory", + "score": 0.9729819893836976, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + }, + { + "expansion_handle": "memory:01M1X0FGJWSYM3MH0XM7B9KC50", + "id": "01M1X0GAD5FBD3B8HYDSKS7Y0Z", + "kind": "memory", + "score": 0.5573575496673584, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1531.5357000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3506, + "mcp_result_bytes": 3669, + "wire_bytes": 3705, + "reported_used_tokens": 3669, + "working_set_bytes": 927272960, + "peak_working_set_bytes": 928190464 + }, + { + "query": "KIMETSU_RUNS_GC env opt-out TraceWriter create gc_old_runs caller", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FCZ789Q8G31JFHGNZZMX", + "id": "01M1X0GBWYBV3TRKY5V2JBYVMN", + "kind": "memory", + "score": 0.9999783039093018, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1178.7096, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 762, + "mcp_result_bytes": 843, + "wire_bytes": 879, + "reported_used_tokens": 843, + "working_set_bytes": 927301632, + "peak_working_set_bytes": 928215040 + }, + { + "query": "where should I put the KIMETSU_RUNS_GC=0 guard \u2014 inside the GC function or at the call site?", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FCZ789Q8G31JFHGNZZMX", + "id": "01M1X0GD1X935H9E3J1YPNX1HQ", + "kind": "memory", + "score": 0.9999699592590332, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1347.9141000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 762, + "mcp_result_bytes": 843, + "wire_bytes": 879, + "reported_used_tokens": 843, + "working_set_bytes": 928108544, + "peak_working_set_bytes": 929030144 + }, + { + "query": "git_init_boundary ProjectPaths::discover temp dir user brain isolation", + "ranked": [ + "init-project-git-boundary", + "git-worktree-brain-isolation", + "testing-temp-dirs-ci" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FD0AKH41YXD7612WYVVX", + "id": "01M1X0GEC3FW9A3QWQ49XKN0NY", + "kind": "memory", + "score": 0.9999781847000122, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + }, + { + "expansion_handle": "memory:01M1X0FF865VP00ZZP7GC92453", + "id": "01M1X0GEC4NW54QF4QC5KY4EXA", + "kind": "memory", + "score": 0.998727023601532, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root \u2014 if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + }, + { + "expansion_handle": "memory:01M1X0FG2M8GQXGWMSNVWE993Y", + "id": "01M1X0GEC408QRPB4K3CBVBH5Q", + "kind": "memory", + "score": 0.8577821850776672, + "summary": "project:fact - [tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1288.9512, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1961, + "mcp_result_bytes": 2078, + "wire_bytes": 2114, + "reported_used_tokens": 2078, + "working_set_bytes": 928436224, + "peak_working_set_bytes": 929349632 + }, + { + "query": "my test calls init_project but it writes to the real ~/.kimetsu instead of the temp folder \u2014 why?", + "ranked": [ + "init-project-git-boundary", + "cargo-feature-unification-embeddings", + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FD0AKH41YXD7612WYVVX", + "id": "01M1X0GFMJX6YC1RJWRPD4FQJY", + "kind": "memory", + "score": 0.9999724626541138, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + }, + { + "expansion_handle": "memory:01M1X0FCMGF9PH24HGZV9KENFV", + "id": "01M1X0GFMJFEQKVZJZRMVG2K86", + "kind": "memory", + "score": 0.5625059604644775, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X0FCSYC83J9JBNY21XF5N1", + "id": "01M1X0GFMJJ96QVEDEHQ1QGE8V", + "kind": "memory", + "score": 0.5564239621162415, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1797.4492, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2263, + "mcp_result_bytes": 2392, + "wire_bytes": 2428, + "reported_used_tokens": 2392, + "working_set_bytes": 928444416, + "peak_working_set_bytes": 929357824 + }, + { + "query": "clap command version KIMETSU_VERSION_DISPLAY cfg feature embeddings", + "ranked": [ + "clap-version-build-flavor", + "cargo-feature-unification-embeddings" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FD1AF8EJPFVTZ239ESCW", + "id": "01M1X0GHD53RX5J3DCGN5E8MFP", + "kind": "memory", + "score": 0.9999767541885376, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + }, + { + "expansion_handle": "memory:01M1X0FCMGF9PH24HGZV9KENFV", + "id": "01M1X0GHD573EZNXF9ZX7F7E90", + "kind": "memory", + "score": 0.9207596778869628, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1282.9876, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1922, + "mcp_result_bytes": 2041, + "wire_bytes": 2077, + "reported_used_tokens": 2041, + "working_set_bytes": 928448512, + "peak_working_set_bytes": 929357824 + }, + { + "query": "how do I show the build flavor (lean vs embeddings) in the kimetsu --version output?", + "ranked": [ + "clap-version-build-flavor", + "cargo-feature-unification-embeddings" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FD1AF8EJPFVTZ239ESCW", + "id": "01M1X0GJMPNVQ24ASV7FDCBRD4", + "kind": "memory", + "score": 0.9999544620513916, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + }, + { + "expansion_handle": "memory:01M1X0FCMGF9PH24HGZV9KENFV", + "id": "01M1X0GJMPTHTAGW1RY2V4RX0A", + "kind": "memory", + "score": 0.8056868314743042, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1495.4503, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1923, + "mcp_result_bytes": 2042, + "wire_bytes": 2078, + "reported_used_tokens": 2042, + "working_set_bytes": 928919552, + "peak_working_set_bytes": 929837056 + }, + { + "query": "Harbor pyiceberg os.getcwd stale WSL2 DrvFs worker-result subprocess re-exec", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FD2TZ5H9CHVTMKFNQ2KP", + "id": "01M1X0GM3SH49MXH0A4NV3ZB5N", + "kind": "memory", + "score": 0.9999732971191406, + "summary": "project:fact - [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1488.1283999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1026, + "mcp_result_bytes": 1107, + "wire_bytes": 1143, + "reported_used_tokens": 1107, + "working_set_bytes": 929234944, + "peak_working_set_bytes": 930144256 + }, + { + "query": "why does my kbench sweep crash after the first trial with 'result.json missing' on WSL2?", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FD2TZ5H9CHVTMKFNQ2KP", + "id": "01M1X0GNJ0K9BSX02XGBKEV8JH", + "kind": "memory", + "score": 0.9995384216308594, + "summary": "project:fact - [2026-09-07] [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1507.8487, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1039, + "mcp_result_bytes": 1120, + "wire_bytes": 1156, + "reported_used_tokens": 1120, + "working_set_bytes": 929431552, + "peak_working_set_bytes": 930353152 + }, + { + "query": "rusqlite VACUUM transaction WAL checkpoint wal_checkpoint TRUNCATE", + "ranked": [ + "sqlite-vacuum-wal-checkpoint", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FD4EGNAHRFMGV4230ZWJ", + "id": "01M1X0GQ187R5HSDS0XKBXJ5XA", + "kind": "memory", + "score": 0.9998983144760132, + "summary": "project:fact - [tags: rust sqlite vacuum rusqlite windows] When implementing SQLite VACUUM in rusqlite: VACUUM cannot run inside a transaction. rusqlite's Connection does not hold an implicit transaction, so `conn.execute_batch(\"VACUUM;\")` works directly. After VACUUM, run `PRAGMA wal_checkpoint(TRUNCATE);` before measuring file size \u2014 on Windows the WAL file can hold significant space that isn't reflected in the main db file until the checkpoint runs." + }, + { + "expansion_handle": "memory:01M1X0FDC458S72EPQTSVEKVE2", + "id": "01M1X0GQ18DQ0CCBVTEVWJP5RD", + "kind": "memory", + "score": 0.9661141633987428, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1451.8495, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1507, + "mcp_result_bytes": 1610, + "wire_bytes": 1646, + "reported_used_tokens": 1610, + "working_set_bytes": 930742272, + "peak_working_set_bytes": 931643392 + }, + { + "query": "my SQLite VACUUM reports the file shrank but the disk usage stayed the same \u2014 Windows WAL?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1366.6553000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 931065856, + "peak_working_set_bytes": 931983360 + }, + { + "query": "add_memory import dedup seen_ids snapshot pre-existing active memory IDs", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FD5E3Q725AP0NSZR1WY2", + "id": "01M1X0GSSJH9HYDF63FT81F55T", + "kind": "memory", + "score": 0.9999771118164062, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount \u2014 both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1350.3995, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 966, + "mcp_result_bytes": 1047, + "wire_bytes": 1083, + "reported_used_tokens": 1047, + "working_set_bytes": 931123200, + "peak_working_set_bytes": 932032512 + }, + { + "query": "brain import re-imports the same JSON file but the deduplication counter is wrong \u2014 why?", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FD5E3Q725AP0NSZR1WY2", + "id": "01M1X0GV3VXGP0J87EFW08PD1Z", + "kind": "memory", + "score": 0.7890511751174927, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount \u2014 both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1397.66, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 965, + "mcp_result_bytes": 1046, + "wire_bytes": 1082, + "reported_used_tokens": 1046, + "working_set_bytes": 931573760, + "peak_working_set_bytes": 932491264 + }, + { + "query": "toml::from_str Value parse document unexpected content str.parse", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FD6V2DDTEC5GRNZHQFHG", + "id": "01M1X0GWFQX3F4JZ10XEM9XQ8T", + "kind": "memory", + "score": 0.9994491934776306, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1187.9915999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 734, + "mcp_result_bytes": 815, + "wire_bytes": 851, + "reported_used_tokens": 815, + "working_set_bytes": 931577856, + "peak_working_set_bytes": 932491264 + }, + { + "query": "how do I parse a TOML configuration file into a toml::Value in toml 0.9?", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FD6V2DDTEC5GRNZHQFHG", + "id": "01M1X0GXMH9FS4SBNJC9WKKAYN", + "kind": "memory", + "score": 0.9999773502349854, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1273.2669999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 733, + "mcp_result_bytes": 814, + "wire_bytes": 850, + "reported_used_tokens": 814, + "working_set_bytes": 931631104, + "peak_working_set_bytes": 932544512 + }, + { + "query": "CIM CreationDate DMTF WMI ps etimes started_at assess_mcp_skew", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FD82EJN72T3NAVYJWRSC", + "id": "01M1X0GYW4S10SYKK40CCFS6R0", + "kind": "memory", + "score": 0.9999735355377196, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1197.3681000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 924, + "mcp_result_bytes": 1013, + "wire_bytes": 1049, + "reported_used_tokens": 1013, + "working_set_bytes": 931631104, + "peak_working_set_bytes": 932544512 + }, + { + "query": "how do I read a process start time on both Windows and Linux in pure Rust?", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FD82EJN72T3NAVYJWRSC", + "id": "01M1X0H01N3WHVTDW8XBN3WJPN", + "kind": "memory", + "score": 0.9942069053649902, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1323.0221, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 923, + "mcp_result_bytes": 1012, + "wire_bytes": 1048, + "reported_used_tokens": 1012, + "working_set_bytes": 931631104, + "peak_working_set_bytes": 932556800 + }, + { + "query": "processes_locking_target decide_preflight_action BufRead Write update.rs", + "ranked": [ + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FD9HA1WJQ1EZS663ST0M", + "id": "01M1X0H1BJTE4HNHNFMD9AHW65", + "kind": "memory", + "score": 0.999950647354126, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1199.3115, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1132, + "mcp_result_bytes": 1213, + "wire_bytes": 1249, + "reported_used_tokens": 1213, + "working_set_bytes": 931635200, + "peak_working_set_bytes": 932556800 + }, + { + "query": "how should I reuse the existing process enumerator in the update preflight check to avoid a second PowerShell query?", + "ranked": [ + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FD9HA1WJQ1EZS663ST0M", + "id": "01M1X0H2GCB3Q2CV8JX86B2SNV", + "kind": "memory", + "score": 0.9999661445617676, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1377.3169, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1132, + "mcp_result_bytes": 1213, + "wire_bytes": 1249, + "reported_used_tokens": 1213, + "working_set_bytes": 931905536, + "peak_working_set_bytes": 932823040 + }, + { + "query": "cfg_attr windows allow dead_code parse_unix_ps cross-platform tests", + "ranked": [ + "cfg-cross-platform-dead-code", + "process-start-time-cross-platform", + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FDB1ES3VMS0Q1XQHTTQZ", + "id": "01M1X0H3VJAR38C1MWA6TBGFKD", + "kind": "memory", + "score": 0.9999802112579346, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + }, + { + "expansion_handle": "memory:01M1X0FD82EJN72T3NAVYJWRSC", + "id": "01M1X0H3VJXVQ2BGP9RMWD6YJF", + "kind": "memory", + "score": 0.8274164795875549, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + }, + { + "expansion_handle": "memory:01M1X0FD9HA1WJQ1EZS663ST0M", + "id": "01M1X0H3VJKBHM456RM23KJ6FA", + "kind": "memory", + "score": 0.7637738585472107, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1148.1207, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2422, + "mcp_result_bytes": 2547, + "wire_bytes": 2583, + "reported_used_tokens": 2547, + "working_set_bytes": 932098048, + "peak_working_set_bytes": 933011456 + }, + { + "query": "how do I keep a function that is only called on Unix from triggering dead_code warnings on Windows?", + "ranked": [ + "cfg-cross-platform-dead-code" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FDB1ES3VMS0Q1XQHTTQZ", + "id": "01M1X0H4ZNARDWF42YP0V3VXDY", + "kind": "memory", + "score": 0.9998409748077391, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1320.0351, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 939, + "reported_used_tokens": 903, + "working_set_bytes": 932257792, + "peak_working_set_bytes": 933171200 + }, + { + "query": "deadlocking a Rust mutex in integration tests", + "ranked": [ + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FCEFP4GRHY8D3PHNZVNZ", + "id": "01M1X0H69DB7DEX4VVT6XPD4RF", + "kind": "memory", + "score": 0.9991264939308168, + "summary": "project:fact - [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure \u2014 `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1270.0694, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 796, + "mcp_result_bytes": 877, + "wire_bytes": 913, + "reported_used_tokens": 877, + "working_set_bytes": 932356096, + "peak_working_set_bytes": 933269504 + }, + { + "query": "benchmarking retrieval quality across embedders", + "ranked": [ + "onnx-quantization-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FEX1YECY93K1TVT2HPRW", + "id": "01M1X0H7GHXY374YVNWQEJE2BY", + "kind": "memory", + "score": 0.7459741234779358, + "summary": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals \u2014 cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 1251.8792999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 978, + "mcp_result_bytes": 1059, + "wire_bytes": 1095, + "reported_used_tokens": 1059, + "working_set_bytes": 932360192, + "peak_working_set_bytes": 933269504 + }, + { + "query": "process memory working set RSS peak measurement Windows", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1434.2207, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 937451520, + "peak_working_set_bytes": 938344448 + }, + { + "query": "cloning a git repository server-side into a managed checkout", + "ranked": [ + "remote-ingest-split-roots", + "git-sparse-checkout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FCFT22SRB88D2ZQV8BRK", + "id": "01M1X0HA4BVSEZYCHKM10JBZGB", + "kind": "memory", + "score": 0.9940817952156068, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1X0FFAJQ9WXZWCPRAXYDE0J", + "id": "01M1X0HA4CHES3KZJ4MRCES517", + "kind": "memory", + "score": 0.9041922092437744, + "summary": "project:fact - [tags: git sparse-checkout partial-clone bandwidth] `git sparse-checkout init --cone` combined with `git clone --filter=blob:none` (partial clone) fetches only the commit graph and tree objects, not blobs. Individual blobs are fetched on demand when accessed. This cuts clone time for large repos from minutes to seconds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1491.0243, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1744, + "mcp_result_bytes": 1843, + "wire_bytes": 1879, + "reported_used_tokens": 1843, + "working_set_bytes": 937467904, + "peak_working_set_bytes": 938373120 + }, + { + "query": "SigV4 signing HTTP requests in Rust", + "ranked": [ + "aws-sigv4-bedrock-blocking", + "aws-presigned-urls", + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FCX344YDYSFM8JDDK4G1", + "id": "01M1X0HBJVXTWH936EPCFBQQQ3", + "kind": "memory", + "score": 0.995133101940155, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1X0FGPV1QN2NJJ22691MFGC", + "id": "01M1X0HBJVE36H5J4PRM3HSWFP", + "kind": "memory", + "score": 0.9750049114227296, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + }, + { + "expansion_handle": "memory:01M1X0FCP7QJPKBMCYASHDFZC7", + "id": "01M1X0HBJV26EC025HP23XDSJE", + "kind": "memory", + "score": 0.936759352684021, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1480.9894, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2838, + "mcp_result_bytes": 2983, + "wire_bytes": 3019, + "reported_used_tokens": 2983, + "working_set_bytes": 937410560, + "peak_working_set_bytes": 938373120 + }, + { + "query": "cargo test --workspace feature flag changes broke my unit tests", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FCMGF9PH24HGZV9KENFV", + "id": "01M1X0HD1F1NQ2F5YEQS7ZJW5T", + "kind": "memory", + "score": 0.9825970530509948, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X0FDSYBHHE1BWJEBE0P4AZ", + "id": "01M1X0HD1FZG4AEXR5DQNYE15C", + "kind": "memory", + "score": 0.8159734606742859, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1268.4139, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1837, + "mcp_result_bytes": 1940, + "wire_bytes": 1976, + "reported_used_tokens": 1940, + "working_set_bytes": 937414656, + "peak_working_set_bytes": 938373120 + }, + { + "query": "how do I make pasta carbonara?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 1630.3597, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 937414656, + "peak_working_set_bytes": 938373120 + }, + { + "query": "what is the offside rule in football?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 1551.2253, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 937623552, + "peak_working_set_bytes": 938532864 + }, + { + "query": "best way to train for a half marathon", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 1459.9725, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 937787392, + "peak_working_set_bytes": 938708992 + }, + { + "query": "my test passes when I run it alone but fails under cargo test --workspace", + "ranked": [ + "cargo-feature-unification-embeddings" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FCMGF9PH24HGZV9KENFV", + "id": "01M1X0HJTBX00VVVDP15Q50BVP", + "kind": "memory", + "score": 0.9996844530105592, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1460.0074, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1148, + "mcp_result_bytes": 1233, + "wire_bytes": 1269, + "reported_used_tokens": 1233, + "working_set_bytes": 937820160, + "peak_working_set_bytes": 938737664 + }, + { + "query": "all the project tests started hanging forever after I added my new test", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1442.6174, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 937832448, + "peak_working_set_bytes": 938741760 + }, + { + "query": "my integration test silently wrote memories into my real home brain instead of the temp workspace", + "ranked": [ + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FD0AKH41YXD7612WYVVX", + "id": "01M1X0HNNPFKPHSF3Z9JN3EJ6Q", + "kind": "memory", + "score": 0.7656484842300415, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1735.5315999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 780, + "mcp_result_bytes": 861, + "wire_bytes": 897, + "reported_used_tokens": 861, + "working_set_bytes": 937902080, + "peak_working_set_bytes": 938819584 + }, + { + "query": "where should the env-var opt-out check live for a cleanup feature triggered from a hot code path", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FCZ789Q8G31JFHGNZZMX", + "id": "01M1X0HQB078M56DMPZHYPEEK0", + "kind": "memory", + "score": 0.9995137453079224, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1333.9692, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 761, + "mcp_result_bytes": 842, + "wire_bytes": 878, + "reported_used_tokens": 842, + "working_set_bytes": 937922560, + "peak_working_set_bytes": 938840064 + }, + { + "query": "the brain database file stays huge on Windows even after deleting most rows", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1279.7255, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 938008576, + "peak_working_set_bytes": 938930176 + }, + { + "query": "re-importing the same exported memories file counts them as new instead of deduplicated", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FD5E3Q725AP0NSZR1WY2", + "id": "01M1X0HSWXJ7P5YPT552RZXMQC", + "kind": "memory", + "score": 0.9581347703933716, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount \u2014 both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1764.6066999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 965, + "mcp_result_bytes": 1046, + "wire_bytes": 1082, + "reported_used_tokens": 1046, + "working_set_bytes": 938057728, + "peak_working_set_bytes": 938975232 + }, + { + "query": "a helper function only called on Unix at runtime fails the dead-code lint on the Windows build", + "ranked": [ + "cfg-cross-platform-dead-code" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FDB1ES3VMS0Q1XQHTTQZ", + "id": "01M1X0HVM57XGY3P42DH854GVK", + "kind": "memory", + "score": 0.9773045778274536, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1328.2682000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 939, + "reported_used_tokens": 903, + "working_set_bytes": 938070016, + "peak_working_set_bytes": 938991616 + }, + { + "query": "the second Terminal-Bench trial always crashes even though the first one passes", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FD2TZ5H9CHVTMKFNQ2KP", + "id": "01M1X0HWXJ4HE9A5NHVBXCVC05", + "kind": "memory", + "score": 0.9474697113037108, + "summary": "project:fact - [2026-09-07] [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1413.7512000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1038, + "mcp_result_bytes": 1119, + "wire_bytes": 1155, + "reported_used_tokens": 1119, + "working_set_bytes": 938082304, + "peak_working_set_bytes": 938995712 + }, + { + "query": "how does doctor tell a running MCP server process is older than the kimetsu binary on disk", + "ranked": [ + "kimetsu-daemon-lifecycle" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FH1B8EG42XEA3S29MF4X", + "id": "01M1X0HYA2B0E6KSEVVWFMXX3Y", + "kind": "memory", + "score": 0.9223618507385254, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1553.7384, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 752, + "mcp_result_bytes": 833, + "wire_bytes": 869, + "reported_used_tokens": 833, + "working_set_bytes": 938094592, + "peak_working_set_bytes": 939012096 + }, + { + "query": "the self-update preflight needs the list of running kimetsu processes without re-running the OS query", + "ranked": [ + "windows-update-process-locking", + "kimetsu-daemon-lifecycle" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FD9HA1WJQ1EZS663ST0M", + "id": "01M1X0HZV649AHHAHRWFGEQVMK", + "kind": "memory", + "score": 0.9925383925437928, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + }, + { + "expansion_handle": "memory:01M1X0FH1B8EG42XEA3S29MF4X", + "id": "01M1X0HZV6K1GDHM8SPGK7AEPB", + "kind": "memory", + "score": 0.5589243769645691, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1341.3696, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1658, + "mcp_result_bytes": 1757, + "wire_bytes": 1793, + "reported_used_tokens": 1757, + "working_set_bytes": 938119168, + "peak_working_set_bytes": 939020288 + }, + { + "query": "parsing the WMI DMTF CreationDate timestamp into epoch seconds without extra crates", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FD82EJN72T3NAVYJWRSC", + "id": "01M1X0J14AWYAPCX0WPE39H86F", + "kind": "memory", + "score": 0.9751563668251038, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1597.9168000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 924, + "mcp_result_bytes": 1013, + "wire_bytes": 1049, + "reported_used_tokens": 1013, + "working_set_bytes": 938119168, + "peak_working_set_bytes": 939032576 + }, + { + "query": "calling Bedrock InvokeModel from blocking reqwest without the aws sdk", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking", + "aws-region-resolution" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FCP7QJPKBMCYASHDFZC7", + "id": "01M1X0J2PH5R24GS62XHPC64ZQ", + "kind": "memory", + "score": 0.99994158744812, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X0FCX344YDYSFM8JDDK4G1", + "id": "01M1X0J2PHA0X6928T1N4HCANR", + "kind": "memory", + "score": 0.9989731311798096, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1X0FGMCP8YBCDTS3WYEGCXD", + "id": "01M1X0J2PH0Q6A7CVVYTGPD73Z", + "kind": "memory", + "score": 0.6464323401451111, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1619.3292000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2787, + "mcp_result_bytes": 2932, + "wire_bytes": 2968, + "reported_used_tokens": 2932, + "working_set_bytes": 938127360, + "peak_working_set_bytes": 939044864 + }, + { + "query": "how do I rotate the encryption key protecting the kimetsu brain database", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 1373.4922000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 938151936, + "peak_working_set_bytes": 939061248 + }, + { + "query": "which tokio runtime worker-thread settings does the kimetsu MCP server use", + "ranked": [ + "tokio-blocking-in-async", + "tokio-runtime-in-tests", + "tokio-spawn-blocking", + "mcp-stdout-protocol" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FFFE7JSJHPASKCZ506M2", + "id": "01M1X0J5KTB9E03TM3NJZZYVXP", + "kind": "memory", + "score": 0.9897258877754213, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + }, + { + "expansion_handle": "memory:01M1X0FFGR17NXBNS2X6A04RAC", + "id": "01M1X0J5KT152TT87ZATRAVQG5", + "kind": "memory", + "score": 0.9347747564315796, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + }, + { + "expansion_handle": "memory:01M1X0FFMQF838NAMWG1GHHFKT", + "id": "01M1X0J5KTJK2CDJFMW45P3NZX", + "kind": "memory", + "score": 0.840076208114624, + "summary": "project:fact - [tags: tokio spawn_blocking thread-pool rust blocking] `tokio::task::spawn_blocking` places work on a dedicated blocking thread pool (default up to 512 threads, configurable via `Builder::max_blocking_threads`). Each call creates or reuses a thread \u2014 there's no true pooling, threads may be created on demand. For many short-duration blocking calls (e.g." + }, + { + "expansion_handle": "memory:01M1X0FGB6CRFP501Z6A7S6SKX", + "id": "01M1X0J5KV0ZJQRSNZP65J5KHR", + "kind": "memory", + "score": 0.6246721744537354, + "summary": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 1334.8918, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2364, + "mcp_result_bytes": 2507, + "wire_bytes": 2543, + "reported_used_tokens": 2507, + "working_set_bytes": 938147840, + "peak_working_set_bytes": 939061248 + }, + { + "query": "how does kimetsu sync memories between two machines over the network", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 1265.1240000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 938156032, + "peak_working_set_bytes": 939069440 + }, + { + "query": "recovering a corrupted usearch ANN index after a power loss", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 1169.5884, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 938160128, + "peak_working_set_bytes": 939073536 + }, + { + "query": "what postgres schema should I use to store kimetsu memories", + "ranked": [ + "onnx-dim-mismatch" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FF1TSSPK6CTCD80P6Z9V", + "id": "01M1X0J99XM8PWMZBS7C0VDGPE", + "kind": "memory", + "score": 0.6243454217910767, + "summary": "project:fact - [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results \u2014 the ANN index shape mismatch isn't always caught at runtime." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 1241.9861999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 740, + "mcp_result_bytes": 821, + "wire_bytes": 857, + "reported_used_tokens": 821, + "working_set_bytes": 938156032, + "peak_working_set_bytes": 939073536 + }, + { + "query": "the whole CI job just froze forever with no failure output after my latest test PR", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1317.8528000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 938147840, + "peak_working_set_bytes": 939073536 + }, + { + "query": "running the test suite left junk state in my home directory", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1316.9378000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 938725376, + "peak_working_set_bytes": 939642880 + }, + { + "query": "I deleted a bunch of old rows but the file on disk is still the same size", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1267.4838, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 938725376, + "peak_working_set_bytes": 939642880 + }, + { + "query": "adding one new crate quietly changed how the whole workspace builds", + "ranked": [ + "cargo-lockfile-drift", + "cargo-feature-unification-embeddings" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FDQJFQGF4MJJFV5Q6913", + "id": "01M1X0JEB4W4WRG91H0FYX7JK3", + "kind": "memory", + "score": 0.9958756566047668, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this \u2014 it errors on any lockfile diff." + }, + { + "expansion_handle": "memory:01M1X0FCMGF9PH24HGZV9KENFV", + "id": "01M1X0JEB3A3PC3DXVNS0WA35Y", + "kind": "memory", + "score": 0.9790327548980712, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 0.5, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1990.2377, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1671, + "mcp_result_bytes": 1774, + "wire_bytes": 1810, + "reported_used_tokens": 1774, + "working_set_bytes": 938762240, + "peak_working_set_bytes": 939683840 + }, + { + "query": "we cannot pull an async runtime into the agent just to talk to AWS", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2143.0218, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 938762240, + "peak_working_set_bytes": 939683840 + }, + { + "query": "users should be able to tell which build variant they installed from the version output", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1770.4875, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 938774528, + "peak_working_set_bytes": 939692032 + }, + { + "query": "what gotchas should I expect writing process-inspection code that works on both Windows and Unix?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1545.6861, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 938844160, + "peak_working_set_bytes": 939765760 + }, + { + "query": "why might tests behave differently on my machine than in the full CI run?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1466.7695, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 938897408, + "peak_working_set_bytes": 939814912 + }, + { + "query": "what do I need to know before wiring kimetsu into a brand new host agent?", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FCRJX7EACVPMGWA07XWD", + "id": "01M1X0JQ1MJMB477QN6DWVVQ6H", + "kind": "memory", + "score": 0.8340779542922974, + "summary": "project:fact - [2026-09-07] [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 0.3333333333333333, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1645.7233, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1072, + "mcp_result_bytes": 1153, + "wire_bytes": 1189, + "reported_used_tokens": 1153, + "working_set_bytes": 938897408, + "peak_working_set_bytes": 939814912 + }, + { + "query": "tell me everything relevant to running kimetsu against AWS", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1458.6468, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 938946560, + "peak_working_set_bytes": 939855872 + }, + { + "query": "ingesting a cloned repo when the brain lives under a different root", + "ranked": [ + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FCFT22SRB88D2ZQV8BRK", + "id": "01M1X0JT2TNBQWSZRQFM7XJK6A", + "kind": "memory", + "score": 0.9854778051376344, + "summary": "project:fact - [2026-09-07] [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1617.8533000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1274, + "mcp_result_bytes": 1355, + "wire_bytes": 1391, + "reported_used_tokens": 1355, + "working_set_bytes": 940208128, + "peak_working_set_bytes": 941117440 + }, + { + "query": "streamable-http transport entry for openclaw.json with a bearer token", + "ranked": [ + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FCJ72EZEB2W227G8E2PV", + "id": "01M1X0JVMVT3DV8M648VVHSP1E", + "kind": "memory", + "score": 0.9984819293022156, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1797.1436999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 996, + "mcp_result_bytes": 1125, + "wire_bytes": 1161, + "reported_used_tokens": 1125, + "working_set_bytes": 940216320, + "peak_working_set_bytes": 941133824 + }, + { + "query": "serializing ingests with a tokio mutex to avoid checkout races", + "ranked": [ + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FCFT22SRB88D2ZQV8BRK", + "id": "01M1X0JXDZ643AB0WV6CJEP0C2", + "kind": "memory", + "score": 0.9989351630210876, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1695.8309, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1261, + "mcp_result_bytes": 1342, + "wire_bytes": 1378, + "reported_used_tokens": 1342, + "working_set_bytes": 940224512, + "peak_working_set_bytes": 941146112 + }, + { + "query": "percent-encoding the colon in the bedrock model id for the invoke URL", + "ranked": [ + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FCP7QJPKBMCYASHDFZC7", + "id": "01M1X0JZ247AGRNHXAAPY2G4X0", + "kind": "memory", + "score": 0.9490103721618652, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1561.0574000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1204, + "mcp_result_bytes": 1293, + "wire_bytes": 1329, + "reported_used_tokens": 1293, + "working_set_bytes": 940236800, + "peak_working_set_bytes": 941150208 + }, + { + "query": "deduplicating re-imported memories against pre-existing ids", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FD5E3Q725AP0NSZR1WY2", + "id": "01M1X0K0KHVN66ZXMBQE7ZB2XB", + "kind": "memory", + "score": 0.996511161327362, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount \u2014 both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1486.8772, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 966, + "mcp_result_bytes": 1047, + "wire_bytes": 1083, + "reported_used_tokens": 1047, + "working_set_bytes": 940240896, + "peak_working_set_bytes": 941150208 + }, + { + "query": "parsing DMTF datetimes", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FD82EJN72T3NAVYJWRSC", + "id": "01M1X0K21D5XFVEMHS220CA42D", + "kind": "memory", + "score": 0.990456759929657, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1080.7994999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 923, + "mcp_result_bytes": 1012, + "wire_bytes": 1048, + "reported_used_tokens": 1012, + "working_set_bytes": 940244992, + "peak_working_set_bytes": 941150208 + }, + { + "query": "how should install derive a stable identifier from the git remote URL?", + "ranked": [ + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FCJ72EZEB2W227G8E2PV", + "id": "01M1X0K33D9VWF4N1J3FVXQC6C", + "kind": "memory", + "score": 0.9954527020454408, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1629.9804, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 995, + "mcp_result_bytes": 1124, + "wire_bytes": 1160, + "reported_used_tokens": 1124, + "working_set_bytes": 940789760, + "peak_working_set_bytes": 941703168 + }, + { + "query": "the secret token must not end up written into the host config file", + "ranked": [ + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FCJ72EZEB2W227G8E2PV", + "id": "01M1X0K4PFW1WHR9D95BW5WT6W", + "kind": "memory", + "score": 0.8757492899894714, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1801.7206999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 996, + "mcp_result_bytes": 1125, + "wire_bytes": 1161, + "reported_used_tokens": 1125, + "working_set_bytes": 940797952, + "peak_working_set_bytes": 941715456 + }, + { + "query": "keep the cleanup logic unit-testable without touching environment variables", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1357.8245, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 940797952, + "peak_working_set_bytes": 941715456 + }, + { + "query": "how do we stop the server from cloning arbitrary repos clients request?", + "ranked": [ + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FCFT22SRB88D2ZQV8BRK", + "id": "01M1X0K7S0QC6P95FD9VH3XK2G", + "kind": "memory", + "score": 0.6168935894966125, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1600.3212, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1261, + "mcp_result_bytes": 1342, + "wire_bytes": 1378, + "reported_used_tokens": 1342, + "working_set_bytes": 940834816, + "peak_working_set_bytes": 941752320 + }, + { + "query": "make sure a wrong guess about a host plugin API never breaks that host", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FCSYC83J9JBNY21XF5N1", + "id": "01M1X0K9ARYMPW28RVWF3XJZ6D", + "kind": "memory", + "score": 0.9010460376739502, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1604.2106, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 803, + "mcp_result_bytes": 892, + "wire_bytes": 928, + "reported_used_tokens": 892, + "working_set_bytes": 941092864, + "peak_working_set_bytes": 942014464 + }, + { + "query": "which wire-format trick lets us reuse the existing Anthropic request builder for AWS?", + "ranked": [ + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FCP7QJPKBMCYASHDFZC7", + "id": "01M1X0KAXMPJWSXM4AZMMQ7P2E", + "kind": "memory", + "score": 0.9861636757850648, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1684.4189000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1203, + "mcp_result_bytes": 1292, + "wire_bytes": 1328, + "reported_used_tokens": 1292, + "working_set_bytes": 941133824, + "peak_working_set_bytes": 942051328 + }, + { + "query": "the self-update froze because something was still holding the executable", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1362.9285, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 941264896, + "peak_working_set_bytes": 942174208 + }, + { + "query": "our notes about the extension API turned out wrong once we read the actual repo", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FCSYC83J9JBNY21XF5N1", + "id": "01M1X0KDWQJEA1ZNGPQ5468D8E", + "kind": "memory", + "score": 0.7192176580429077, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1812.7952, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 803, + "mcp_result_bytes": 892, + "wire_bytes": 928, + "reported_used_tokens": 892, + "working_set_bytes": 941289472, + "peak_working_set_bytes": 942198784 + }, + { + "query": "half the benchmark trials die right after the first one finishes", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1390.4079, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 941301760, + "peak_working_set_bytes": 942219264 + }, + { + "query": "I need this parser visible to tests on every OS even though only one OS calls it", + "ranked": [ + "cfg-cross-platform-dead-code" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FDB1ES3VMS0Q1XQHTTQZ", + "id": "01M1X0KH0JDNPACJ4CJ2MBR5PM", + "kind": "memory", + "score": 0.5910465121269226, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1493.8929, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 821, + "mcp_result_bytes": 902, + "wire_bytes": 938, + "reported_used_tokens": 902, + "working_set_bytes": 941355008, + "peak_working_set_bytes": 942260224 + }, + { + "query": "the config file content refuses to parse even though the TOML looks valid", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FD6V2DDTEC5GRNZHQFHG", + "id": "01M1X0KJF78RFDVJDR38GYH2XN", + "kind": "memory", + "score": 0.8025842905044556, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1538.2338, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 733, + "mcp_result_bytes": 814, + "wire_bytes": 850, + "reported_used_tokens": 814, + "working_set_bytes": 941355008, + "peak_working_set_bytes": 942272512 + }, + { + "query": "the remote server must refresh its checkout before answering file queries", + "ranked": [ + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FCFT22SRB88D2ZQV8BRK", + "id": "01M1X0KKZDT7KX9JVER25ABZV7", + "kind": "memory", + "score": 0.7441006898880005, + "summary": "project:fact - [2026-09-07] [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2020.2721999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1274, + "mcp_result_bytes": 1355, + "wire_bytes": 1391, + "reported_used_tokens": 1355, + "working_set_bytes": 941338624, + "peak_working_set_bytes": 942280704 + }, + { + "query": "tests must not climb to a parent git repository when resolving project paths", + "ranked": [ + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FD0AKH41YXD7612WYVVX", + "id": "01M1X0KNYGBHA7BTVHXY2048AK", + "kind": "memory", + "score": 0.9996840953826904, + "summary": "project:fact - [2026-09-07] [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1654.2703, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 794, + "mcp_result_bytes": 875, + "wire_bytes": 911, + "reported_used_tokens": 875, + "working_set_bytes": 942510080, + "peak_working_set_bytes": 943419392 + }, + { + "query": "how do I test request signing deterministically when timestamps change every run?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1545.8033, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 942518272, + "peak_working_set_bytes": 943431680 + }, + { + "query": "adding a new variant to the host target enum - which places will I forget to update?", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FCRJX7EACVPMGWA07XWD", + "id": "01M1X0KS2WYTBSGG4BCHZG6HE8", + "kind": "memory", + "score": 0.885578989982605, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1814.446, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1058, + "mcp_result_bytes": 1139, + "wire_bytes": 1175, + "reported_used_tokens": 1139, + "working_set_bytes": 942526464, + "peak_working_set_bytes": 943439872 + }, + { + "query": "how do I enable GPU acceleration for kimetsu embedding inference", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 1291.2859, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 942526464, + "peak_working_set_bytes": 943439872 + }, + { + "query": "how do I throttle kimetsu API spend per month", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 1734.7660999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 942645248, + "peak_working_set_bytes": 943562752 + }, + { + "query": "can the kimetsu brain database be stored in S3 instead of on disk", + "ranked": [ + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FGPV1QN2NJJ22691MFGC", + "id": "01M1X0KXVRA3FY6XFD2XEGH06P", + "kind": "memory", + "score": 0.6605784893035889, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 1275.4397, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 874, + "mcp_result_bytes": 955, + "wire_bytes": 991, + "reported_used_tokens": 955, + "working_set_bytes": 942649344, + "peak_working_set_bytes": 943566848 + }, + { + "query": "how do I plug a custom tokenizer into the FTS index", + "ranked": [ + "sqlite-fts5-tokenizer" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FDGPR1KJ3A5AG0PP2BSN", + "id": "01M1X0KZ1Y7J4WQHME5ETDTHA4", + "kind": "memory", + "score": 0.8707897067070007, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 1750.2586999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 671, + "mcp_result_bytes": 756, + "wire_bytes": 792, + "reported_used_tokens": 756, + "working_set_bytes": 942653440, + "peak_working_set_bytes": 943566848 + }, + { + "query": "what should I check when kimetsu behaves differently on Windows than on Linux?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1276.8636, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 942669824, + "peak_working_set_bytes": 943583232 + }, + { + "query": "what are the moving parts of the kimetsu remote deployment story?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1283.2202, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 942669824, + "peak_working_set_bytes": 943587328 + }, + { + "query": "which lessons cover guarding behavior behind environment variables?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1334.5662, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 942673920, + "peak_working_set_bytes": 943587328 + }, + { + "query": "SQLite BUSY error under concurrent writes", + "ranked": [ + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FDC458S72EPQTSVEKVE2", + "id": "01M1X0M4JKQCPWN30983RZF40F", + "kind": "memory", + "score": 0.8938739895820618, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1214.2073, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 898, + "mcp_result_bytes": 979, + "wire_bytes": 1016, + "reported_used_tokens": 979, + "working_set_bytes": 942682112, + "peak_working_set_bytes": 943587328 + }, + { + "query": "SQLite WAL mode breaks when the database is on a network share", + "ranked": [ + "sqlite-wal-network-drive", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FDFGF20XSWT46H55Y00V", + "id": "01M1X0M5RX9KGECBQ7GH1W5Q26", + "kind": "memory", + "score": 0.999137282371521, + "summary": "project:fact - [2026-09-07] [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + }, + { + "expansion_handle": "memory:01M1X0FDC458S72EPQTSVEKVE2", + "id": "01M1X0M5RX056HDWWEH03KXVD2", + "kind": "memory", + "score": 0.9803613424301147, + "summary": "project:fact - [2026-09-07] [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1346.3215, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1448, + "mcp_result_bytes": 1547, + "wire_bytes": 1584, + "reported_used_tokens": 1547, + "working_set_bytes": 942682112, + "peak_working_set_bytes": 943595520 + }, + { + "query": "my SQLite WAL database causes SQLITE_IOERR_LOCK on a mapped drive", + "ranked": [ + "sqlite-wal-network-drive", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FDFGF20XSWT46H55Y00V", + "id": "01M1X0M73132B03VBXECA0GPRD", + "kind": "memory", + "score": 0.999721109867096, + "summary": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + }, + { + "expansion_handle": "memory:01M1X0FDC458S72EPQTSVEKVE2", + "id": "01M1X0M731YS9HJCS3MYYZ84ZJ", + "kind": "memory", + "score": 0.6342206001281738, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1359.363, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1421, + "mcp_result_bytes": 1520, + "wire_bytes": 1557, + "reported_used_tokens": 1520, + "working_set_bytes": 942686208, + "peak_working_set_bytes": 943595520 + }, + { + "query": "FTS5 tokenizer configuration for Rust identifiers with underscores", + "ranked": [ + "sqlite-fts5-tokenizer" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FDGPR1KJ3A5AG0PP2BSN", + "id": "01M1X0M8D08GBFEFWEG0776S6W", + "kind": "memory", + "score": 0.9998878240585328, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1267.1315000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 671, + "mcp_result_bytes": 756, + "wire_bytes": 793, + "reported_used_tokens": 756, + "working_set_bytes": 942682112, + "peak_working_set_bytes": 943595520 + }, + { + "query": "I switched the FTS5 tokenizer but search stopped returning results", + "ranked": [ + "sqlite-fts5-tokenizer" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FDGPR1KJ3A5AG0PP2BSN", + "id": "01M1X0M9MD2764XJKDX2HP7DHH", + "kind": "memory", + "score": 0.943705141544342, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1298.7415, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 669, + "mcp_result_bytes": 754, + "wire_bytes": 791, + "reported_used_tokens": 754, + "working_set_bytes": 942682112, + "peak_working_set_bytes": 943595520 + }, + { + "query": "optimal SQLite page size for storing embedding vectors", + "ranked": [ + "sqlite-page-size" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FDHXGYJ0A4HSTNQKXSED", + "id": "01M1X0MAX0A8FPD2WG12WCS77J", + "kind": "memory", + "score": 0.9990623593330384, + "summary": "project:fact - [tags: sqlite page_size performance rusqlite] SQLite's default page_size is 4096 bytes. For a write-heavy brain database with large BLOB payloads (embedding vectors), raising page_size to 16384 reduces fragmentation and improves sequential scan throughput. `PRAGMA page_size = 16384;` must be set BEFORE the first table is created \u2014 changing it on an existing database requires a VACUUM afterward to rebuild all pages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1218.5037, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 809, + "mcp_result_bytes": 890, + "wire_bytes": 927, + "reported_used_tokens": 890, + "working_set_bytes": 942800896, + "peak_working_set_bytes": 943706112 + }, + { + "query": "ON DELETE CASCADE in SQLite does nothing \u2014 foreign keys not enforced", + "ranked": [ + "sqlite-foreign-keys-default-off" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FDK0D98P2Z72TEQ9V71S", + "id": "01M1X0MC3Z5N89DSZYFFQQTZXQ", + "kind": "memory", + "score": 0.9999727010726928, + "summary": "project:fact - [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting \u2014 every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1353.8987, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 736, + "mcp_result_bytes": 817, + "wire_bytes": 854, + "reported_used_tokens": 817, + "working_set_bytes": 942956544, + "peak_working_set_bytes": 943865856 + }, + { + "query": "indexing a JSON metadata column in SQLite without a schema migration", + "ranked": [ + "sqlite-json1-extract" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FDM26FQVTKMFA8RWV5WR", + "id": "01M1X0MDDHFW4P52N3NZPZ1PRM", + "kind": "memory", + "score": 0.9998306035995485, + "summary": "project:fact - [tags: sqlite json1 json_extract rusqlite] SQLite's json1 extension (built in since 3.38.0) lets you index and query JSONB columns with `json_extract(col, '$.field')`. To create a partial index over a JSON field: `CREATE INDEX idx ON memories (json_extract(metadata, '$.scope')) WHERE json_extract(metadata, '$.scope') IS NOT NULL;`. Use `json_each` for array fields." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1275.9993, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 757, + "mcp_result_bytes": 838, + "wire_bytes": 875, + "reported_used_tokens": 838, + "working_set_bytes": 942960640, + "peak_working_set_bytes": 943869952 + }, + { + "query": "prepare() vs prepare_cached() in rusqlite hot insert loop", + "ranked": [ + "sqlite-prepared-stmt-cache" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FDNAWNA21GRY8RE3E4Q6", + "id": "01M1X0MENBBBDAYT5VKAXBJJ4E", + "kind": "memory", + "score": 0.9999525547027588, + "summary": "project:fact - [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1236.8113, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 689, + "mcp_result_bytes": 770, + "wire_bytes": 807, + "reported_used_tokens": 770, + "working_set_bytes": 942968832, + "peak_working_set_bytes": 943874048 + }, + { + "query": "speed up bulk memory ingest by caching SQL statements", + "ranked": [ + "sqlite-prepared-stmt-cache" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FDNAWNA21GRY8RE3E4Q6", + "id": "01M1X0MFW7XYEVRAE6CPAXS9YV", + "kind": "memory", + "score": 0.6780275106430054, + "summary": "project:fact - [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1582.6038, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 688, + "mcp_result_bytes": 769, + "wire_bytes": 806, + "reported_used_tokens": 769, + "working_set_bytes": 942981120, + "peak_working_set_bytes": 943886336 + }, + { + "query": "partial index on deleted_at IS NULL for faster active memory queries", + "ranked": [ + "sqlite-partial-index", + "sqlite-json1-extract" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FDPFC7QHX797BCST6JFF", + "id": "01M1X0MHDSZ3J3HES90RETCYPM", + "kind": "memory", + "score": 0.999954104423523, + "summary": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query \u2014 the planner uses the partial index only when the WHERE clause matches." + }, + { + "expansion_handle": "memory:01M1X0FDM26FQVTKMFA8RWV5WR", + "id": "01M1X0MHDSK1X2RQC6XG2M88N1", + "kind": "memory", + "score": 0.5821903347969055, + "summary": "project:fact - [tags: sqlite json1 json_extract rusqlite] SQLite's json1 extension (built in since 3.38.0) lets you index and query JSONB columns with `json_extract(col, '$.field')`. To create a partial index over a JSON field: `CREATE INDEX idx ON memories (json_extract(metadata, '$.scope')) WHERE json_extract(metadata, '$.scope') IS NOT NULL;`. Use `json_each` for array fields." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1293.1873, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1323, + "mcp_result_bytes": 1422, + "wire_bytes": 1459, + "reported_used_tokens": 1422, + "working_set_bytes": 942981120, + "peak_working_set_bytes": 943886336 + }, + { + "query": "the brain query is slow because it scans all rows including soft-deleted ones", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1340.7405999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 942981120, + "peak_working_set_bytes": 943898624 + }, + { + "query": "Cargo.lock changed unexpectedly after adding a new workspace crate", + "ranked": [ + "cargo-lockfile-drift", + "cargo-feature-unification-embeddings" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FDQJFQGF4MJJFV5Q6913", + "id": "01M1X0MM0DYNKT4KYK0JXDFNTD", + "kind": "memory", + "score": 0.9998825788497924, + "summary": "project:fact - [2026-09-07] [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this \u2014 it errors on any lockfile diff." + }, + { + "expansion_handle": "memory:01M1X0FCMGF9PH24HGZV9KENFV", + "id": "01M1X0MM0DHCEKPA5X7MWJ8TW4", + "kind": "memory", + "score": 0.9923800230026244, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1361.6088, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1698, + "mcp_result_bytes": 1801, + "wire_bytes": 1838, + "reported_used_tokens": 1801, + "working_set_bytes": 942989312, + "peak_working_set_bytes": 943906816 + }, + { + "query": "how do I prevent CI from accepting a modified lockfile silently?", + "ranked": [ + "cargo-lockfile-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FDQJFQGF4MJJFV5Q6913", + "id": "01M1X0MNAJQV7PCV4925RG3YN9", + "kind": "memory", + "score": 0.770147979259491, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this \u2014 it errors on any lockfile diff." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1299.6933, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 764, + "mcp_result_bytes": 845, + "wire_bytes": 882, + "reported_used_tokens": 845, + "working_set_bytes": 942993408, + "peak_working_set_bytes": 943910912 + }, + { + "query": "build.rs reruns on every incremental build even when nothing changed", + "ranked": [ + "cargo-build-script-rerun" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FDRP7GJWX7CWTS6FHHD8", + "id": "01M1X0MPKHVC4VS7X1Z7EDRWBG", + "kind": "memory", + "score": 0.9999223947525024, + "summary": "project:fact - [2026-09-07] [tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1596.6911000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 698, + "mcp_result_bytes": 779, + "wire_bytes": 816, + "reported_used_tokens": 779, + "working_set_bytes": 943005696, + "peak_working_set_bytes": 943923200 + }, + { + "query": "incremental cargo build is slow because build script runs every time", + "ranked": [ + "cargo-build-script-rerun" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FDRP7GJWX7CWTS6FHHD8", + "id": "01M1X0MR5G1M3CYCQ6Q39EY9MC", + "kind": "memory", + "score": 0.9998290538787842, + "summary": "project:fact - [tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1432.2945, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 685, + "mcp_result_bytes": 766, + "wire_bytes": 803, + "reported_used_tokens": 766, + "working_set_bytes": 943005696, + "peak_working_set_bytes": 943923200 + }, + { + "query": "a dev-dependency is activating an embeddings feature in my production build", + "ranked": [ + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FDSYBHHE1BWJEBE0P4AZ", + "id": "01M1X0MSJP1Z9Q7K5M5NDH1TWZ", + "kind": "memory", + "score": 0.999002993106842, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1498.2572, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 930, + "mcp_result_bytes": 1011, + "wire_bytes": 1048, + "reported_used_tokens": 1011, + "working_set_bytes": 943005696, + "peak_working_set_bytes": 943923200 + }, + { + "query": "how do I prevent a test-only feature from bleeding into the non-test compilation?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1621.3146000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 943276032, + "peak_working_set_bytes": 944189440 + }, + { + "query": "linker errors in target/ caused by antivirus holding the exe file", + "ranked": [ + "windows-file-locking-av" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FEKZ2E193N9WCMNK608Q", + "id": "01M1X0MWM3EFPY7E2E241V019J", + "kind": "memory", + "score": 0.9991186261177064, + "summary": "project:fact - [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1361.8756, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 756, + "mcp_result_bytes": 837, + "wire_bytes": 874, + "reported_used_tokens": 837, + "working_set_bytes": 943529984, + "peak_working_set_bytes": 944439296 + }, + { + "query": "Access is denied (os error 5) when linking on Windows \u2014 how do I fix this?", + "ranked": [ + "windows-file-locking-av" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FEKZ2E193N9WCMNK608Q", + "id": "01M1X0MY2KY5ZS4DX3K5ZC3WJ9", + "kind": "memory", + "score": 0.9984531402587892, + "summary": "project:fact - [2026-09-07] [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1519.0272, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 769, + "mcp_result_bytes": 850, + "wire_bytes": 887, + "reported_used_tokens": 850, + "working_set_bytes": 943529984, + "peak_working_set_bytes": 944439296 + }, + { + "query": "incremental build broke with a type mismatch after switching branches", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FDWAJJJEQ386Q52CJW2T", + "id": "01M1X0MZECGPEBFKNCB1DMM6QG", + "kind": "memory", + "score": 0.7982672452926636, + "summary": "project:fact - [2026-09-07] [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1356.9539, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 890, + "mcp_result_bytes": 971, + "wire_bytes": 1008, + "reported_used_tokens": 971, + "working_set_bytes": 943529984, + "peak_working_set_bytes": 944443392 + }, + { + "query": "cargo reports a type error that references a type not in the codebase", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FDWAJJJEQ386Q52CJW2T", + "id": "01M1X0N0R7C3R7ED1DJZ4SSD29", + "kind": "memory", + "score": 0.7982914447784424, + "summary": "project:fact - [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1504.4463, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 877, + "mcp_result_bytes": 958, + "wire_bytes": 995, + "reported_used_tokens": 958, + "working_set_bytes": 943792128, + "peak_working_set_bytes": 944713728 + }, + { + "query": "compile fastembed at O2 in debug builds to avoid slow embedding inference", + "ranked": [ + "cargo-profile-override", + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FDXBKT432V6HFK7JTA3K", + "id": "01M1X0N27470N4X7ZDEBJ51MVG", + "kind": "memory", + "score": 0.9999233484268188, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1X0FGCEM52ZVFVATQ07Y6WA", + "id": "01M1X0N2747QNMQN0M127T06K1", + "kind": "memory", + "score": 0.7583951950073242, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1489.7835, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1323, + "mcp_result_bytes": 1422, + "wire_bytes": 1459, + "reported_used_tokens": 1422, + "working_set_bytes": 943792128, + "peak_working_set_bytes": 944713728 + }, + { + "query": "override compilation profile for a single crate in a Cargo workspace", + "ranked": [ + "cargo-profile-override", + "cargo-patch-section", + "cargo-target-dir-sharing", + "cargo-lockfile-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FDXBKT432V6HFK7JTA3K", + "id": "01M1X0N3NA5RVB6WDS8SDVASY7", + "kind": "memory", + "score": 0.9999361038208008, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1X0FDYHZHA5GZSND58F0KDX", + "id": "01M1X0N3NACCRY8G5R70J85H3A", + "kind": "memory", + "score": 0.9970531463623048, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace \u2014 including transitive deps \u2014 that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1X0FDV6PJ5BJVNBKJJ9EW2Y", + "id": "01M1X0N3NA386S4BP26M9F2Q8E", + "kind": "memory", + "score": 0.9418804049491882, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps \u2014 use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + }, + { + "expansion_handle": "memory:01M1X0FDQJFQGF4MJJFV5Q6913", + "id": "01M1X0N3NAJKEJN3NAN2THRM14", + "kind": "memory", + "score": 0.6213672161102295, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this \u2014 it errors on any lockfile diff." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1490.0815, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2516, + "mcp_result_bytes": 2655, + "wire_bytes": 2692, + "reported_used_tokens": 2655, + "working_set_bytes": 943792128, + "peak_working_set_bytes": 944713728 + }, + { + "query": "[patch.crates-io] workspace dependency override", + "ranked": [ + "cargo-patch-section", + "cargo-dev-dep-leak", + "cargo-profile-override" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FDYHZHA5GZSND58F0KDX", + "id": "01M1X0N54BJZQPAE7ZY6JAZPYN", + "kind": "memory", + "score": 0.9999796152114868, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace \u2014 including transitive deps \u2014 that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1X0FDSYBHHE1BWJEBE0P4AZ", + "id": "01M1X0N54B0YKXGEJHB4CPHEMF", + "kind": "memory", + "score": 0.7515549063682556, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + }, + { + "expansion_handle": "memory:01M1X0FDXBKT432V6HFK7JTA3K", + "id": "01M1X0N54B90Y0JAYG7CDQVGW9", + "kind": "memory", + "score": 0.6568455696105957, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1251.9222000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1917, + "mcp_result_bytes": 2038, + "wire_bytes": 2075, + "reported_used_tokens": 2038, + "working_set_bytes": 943792128, + "peak_working_set_bytes": 944713728 + }, + { + "query": "pin minimum supported Rust version in Cargo.toml", + "ranked": [ + "cargo-msrv" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FDZNZN1Z8JQHAH93YFTH", + "id": "01M1X0N6B3TBG69NNQG46SYZW2", + "kind": "memory", + "score": 0.9998986721038818, + "summary": "project:fact - [tags: cargo rust msrv edition compatibility] Set `rust-version` in each `Cargo.toml` to declare the minimum supported Rust version (MSRV). Cargo enforces this with `--check`: `cargo check` fails if the toolchain is older than `rust-version`. Keep MSRV as old as your oldest supported deployment target." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1184.5364, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 693, + "mcp_result_bytes": 774, + "wire_bytes": 811, + "reported_used_tokens": 774, + "working_set_bytes": 943800320, + "peak_working_set_bytes": 944717824 + }, + { + "query": "Windows path over 260 characters causes OS error 3 during Cargo build", + "ranked": [ + "windows-long-paths", + "windows-file-locking-av" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FEJS1Y2NTQ15BWGKCE08", + "id": "01M1X0N7GNRZXVZSSWYYJ64ZGK", + "kind": "memory", + "score": 0.9998657703399658, + "summary": "project:fact - [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe." + }, + { + "expansion_handle": "memory:01M1X0FEKZ2E193N9WCMNK608Q", + "id": "01M1X0N7GNM0K5HK6WGFW5S8G6", + "kind": "memory", + "score": 0.6231384873390198, + "summary": "project:fact - [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1181.8439, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1297, + "mcp_result_bytes": 1406, + "wire_bytes": 1443, + "reported_used_tokens": 1406, + "working_set_bytes": 943800320, + "peak_working_set_bytes": 944717824 + }, + { + "query": "how do I enable long file paths for Cargo on Windows?", + "ranked": [ + "windows-long-paths", + "windows-registry-rust" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FEJS1Y2NTQ15BWGKCE08", + "id": "01M1X0N8N7GQZTF0T1SEWSCT6J", + "kind": "memory", + "score": 0.9999781847000122, + "summary": "project:fact - [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe." + }, + { + "expansion_handle": "memory:01M1X0FETC7TY24EA6QCQZGJHD", + "id": "01M1X0N8N72VGZEERERG0NBAKM", + "kind": "memory", + "score": 0.7387577891349792, + "summary": "project:fact - [tags: windows registry rust winreg read write] Reading and writing the Windows registry from Rust requires the `winreg` crate. Open a key with `RegKey::predef(HKEY_LOCAL_MACHINE).open_subkey_with_flags(path, KEY_READ)` \u2014 use `KEY_READ` for reads and `KEY_READ | KEY_WRITE` for writes (NOT `KEY_ALL_ACCESS`, which requires admin). To set a DWORD value: `key.set_value(\"LongPathsEnabled\", &1u32)`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1634.9224, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1332, + "mcp_result_bytes": 1445, + "wire_bytes": 1482, + "reported_used_tokens": 1445, + "working_set_bytes": 943800320, + "peak_working_set_bytes": 944717824 + }, + { + "query": "intermittent sharing violation errors when Rust linker writes the exe on Windows", + "ranked": [ + "windows-file-locking-av" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FEKZ2E193N9WCMNK608Q", + "id": "01M1X0NA8JW7Z43TF50TG6QJ6W", + "kind": "memory", + "score": 0.9999388456344604, + "summary": "project:fact - [2026-09-07] [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1224.9582, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 769, + "mcp_result_bytes": 850, + "wire_bytes": 887, + "reported_used_tokens": 850, + "working_set_bytes": 943804416, + "peak_working_set_bytes": 944717824 + }, + { + "query": "Rust walkdir follows junctions differently from symlinks on Windows", + "ranked": [ + "windows-junctions-vs-symlinks" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FEQQ8QFJF38RW2F5QSM0", + "id": "01M1X0NBF4Z56XMXRJ48Y7CTZW", + "kind": "memory", + "score": 0.9999210834503174, + "summary": "project:fact - [tags: windows junctions symlinks rust std::fs] On Windows, directory junctions (NTFS reparse points) behave like symlinks for directory traversal but `std::fs::symlink_metadata` returns `FileType::is_symlink() = false` for junctions (only true for regular symlinks). Use `std::fs::read_link` \u2014 it succeeds for both junction and symlink. `walkdir` crate's `follow_links` follows both, but its `is_symlink()` method correctly reports only actual symlinks." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1293.1524, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 845, + "mcp_result_bytes": 926, + "wire_bytes": 963, + "reported_used_tokens": 926, + "working_set_bytes": 943808512, + "peak_working_set_bytes": 944721920 + }, + { + "query": "UNC path canonicalize returns verbatim prefix \u2014 how do I strip it?", + "ranked": [ + "windows-unc-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FEN2P1P40YE2F77AFTCQ", + "id": "01M1X0NCQ4QYKAX3JFN2MR9GC5", + "kind": "memory", + "score": 0.999847412109375, + "summary": "project:fact - [tags: windows unc-paths rust std::fs] Windows UNC paths (`\\\\server\\share\\...`) are not supported by most Rust `std::fs` operations unless passed through the extended-length prefix `\\\\?\\UNC\\server\\share\\...`. `std::path::Path::new(\"\\\\\\\\server\\\\share\")` works for basic operations but breaks with `canonicalize()` which returns the verbatim prefix form. When walking directory trees that may start on UNC paths, use the `dunce` crate to strip the verbatim prefix before comparing or displaying paths." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1623.7713, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 907, + "mcp_result_bytes": 1024, + "wire_bytes": 1061, + "reported_used_tokens": 1024, + "working_set_bytes": 943808512, + "peak_working_set_bytes": 944734208 + }, + { + "query": "UTF-8 memory text prints as mojibake in the Windows console", + "ranked": [ + "windows-console-encoding" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FEPDVFP6JW3GC3CEJJA2", + "id": "01M1X0NE9Q1FK804PF7G96Y6GN", + "kind": "memory", + "score": 0.9999754428863524, + "summary": "project:fact - [tags: windows console encoding utf8 rust] Windows console code page defaults to the system ANSI code page (usually CP1252 or CP932), not UTF-8. Rust's `println!` writes UTF-8 bytes which display as mojibake in a non-UTF-8 console. Fix at process startup: call `SetConsoleOutputCP(65001)` via `winapi` or `windows-sys`, or set `PYTHONUTF8=1`/`RUST_LOG` before launch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1237.5366, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 757, + "mcp_result_bytes": 838, + "wire_bytes": 875, + "reported_used_tokens": 838, + "working_set_bytes": 943828992, + "peak_working_set_bytes": 944746496 + }, + { + "query": "process exit code is 4294967295 instead of -1 on Windows", + "ranked": [ + "windows-exit-codes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FES2B7AHT9Z53K9DFQPX", + "id": "01M1X0NFGVS0S6C56WEYB3HS72", + "kind": "memory", + "score": 0.9999797344207764, + "summary": "project:fact - [tags: windows exit-codes rust process child] On Windows, process exit codes are 32-bit unsigned integers (DWORD). Rust's `ExitStatus::code()` returns `Option` \u2014 it's `None` if the process was killed by a signal (which Windows doesn't use; instead, TerminateProcess with a code). Conventional codes: 0=success, 1=generic error, 0xC0000005=access violation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1395.1342, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 753, + "mcp_result_bytes": 834, + "wire_bytes": 871, + "reported_used_tokens": 834, + "working_set_bytes": 944082944, + "peak_working_set_bytes": 944996352 + }, + { + "query": "tokenizer.json must match the ONNX model \u2014 what breaks if it doesn't?", + "ranked": [ + "onnx-tokenizer-mismatch" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FEVQ6FT1PJT4D1ZTY1A0", + "id": "01M1X0NGWPZC4RPZNVFQVEJ1RR", + "kind": "memory", + "score": 0.9999275207519532, + "summary": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly \u2014 specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings \u2014 cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1358.9210999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 959, + "mcp_result_bytes": 1040, + "wire_bytes": 1077, + "reported_used_tokens": 1040, + "working_set_bytes": 944197632, + "peak_working_set_bytes": 945111040 + }, + { + "query": "embedding quality degraded after I swapped in the INT8 quantized model", + "ranked": [ + "onnx-quantization-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FEX1YECY93K1TVT2HPRW", + "id": "01M1X0NJ76HAABWTT2TGCX4SAJ", + "kind": "memory", + "score": 0.9944571256637572, + "summary": "project:fact - [2026-09-07] [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals \u2014 cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1335.4071000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 991, + "mcp_result_bytes": 1072, + "wire_bytes": 1109, + "reported_used_tokens": 1072, + "working_set_bytes": 944197632, + "peak_working_set_bytes": 945115136 + }, + { + "query": "missing attention mask causes low-norm embeddings in batch inference", + "ranked": [ + "onnx-batch-padding" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FEY6KZAQ6VXVFCQHDMB8", + "id": "01M1X0NKGS14GZ2TPTZSX1TQEK", + "kind": "memory", + "score": 0.9998724460601808, + "summary": "project:fact - [tags: onnx batch padding attention-mask embeddings] When running batch inference with an ONNX model, all inputs in the batch must be padded to the same sequence length. The `attention_mask` tensor marks which tokens are real (1) and which are padding (0). Failing to pass `attention_mask` causes the model to average-pool over padding tokens, producing systematically lower-norm embeddings." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1303.4008999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 781, + "mcp_result_bytes": 862, + "wire_bytes": 899, + "reported_used_tokens": 862, + "working_set_bytes": 944197632, + "peak_working_set_bytes": 945115136 + }, + { + "query": "ONNX model download fails in a Docker container with no home directory", + "ranked": [ + "onnx-model-cache-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FEZF6J7G38R12F6WKY7T", + "id": "01M1X0NMS4877M6EC0YQ0DQC2H", + "kind": "memory", + "score": 0.9924855828285216, + "summary": "project:fact - [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1312.8033, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 755, + "mcp_result_bytes": 838, + "wire_bytes": 875, + "reported_used_tokens": 838, + "working_set_bytes": 944205824, + "peak_working_set_bytes": 945115136 + }, + { + "query": "fastembed cache path environment variable for CI", + "ranked": [ + "onnx-model-cache-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FEZF6J7G38R12F6WKY7T", + "id": "01M1X0NP2D4RB8C9XYKBG8STAX", + "kind": "memory", + "score": 0.9999436140060424, + "summary": "project:fact - [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1261.6488000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 756, + "mcp_result_bytes": 839, + "wire_bytes": 876, + "reported_used_tokens": 839, + "working_set_bytes": 944189440, + "peak_working_set_bytes": 945115136 + }, + { + "query": "cosine similarity vs dot product for L2-normalized embedding vectors", + "ranked": [ + "onnx-cosine-vs-dot" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FF0K20746M62DSGEMWH1", + "id": "01M1X0NQA67SKXFCDFG40D56M9", + "kind": "memory", + "score": 0.9999769926071168, + "summary": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing \u2014 double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1325.8163, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 765, + "mcp_result_bytes": 846, + "wire_bytes": 883, + "reported_used_tokens": 846, + "working_set_bytes": 944189440, + "peak_working_set_bytes": 945115136 + }, + { + "query": "stored vectors have wrong dimension after switching embedding models", + "ranked": [ + "onnx-dim-mismatch", + "onnx-cosine-vs-dot" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FF1TSSPK6CTCD80P6Z9V", + "id": "01M1X0NRKKX1B1WQCTNTFK8VM4", + "kind": "memory", + "score": 0.9999632835388184, + "summary": "project:fact - [2026-09-07] [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results \u2014 the ANN index shape mismatch isn't always caught at runtime." + }, + { + "expansion_handle": "memory:01M1X0FF0K20746M62DSGEMWH1", + "id": "01M1X0NRKK1G7DS0J23S6GFR6F", + "kind": "memory", + "score": 0.8715931177139282, + "summary": "project:fact - [2026-09-07] [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing \u2014 double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1172.8694, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1305, + "mcp_result_bytes": 1404, + "wire_bytes": 1441, + "reported_used_tokens": 1404, + "working_set_bytes": 944189440, + "peak_working_set_bytes": 945115136 + }, + { + "query": "E5 and Instructor models need a query prefix \u2014 what happens without it?", + "ranked": [ + "onnx-prefix-instructions", + "onnx-cosine-vs-dot" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FF5Q589N58C2M1T8R601", + "id": "01M1X0NSQVX98RTWAQM413B5NP", + "kind": "memory", + "score": 0.9999685287475586, + "summary": "project:fact - [tags: onnx embeddings prefix instruction e5 query passage] E5 and Instructor family models require a text prefix on BOTH query and passage sides to produce meaningful similarities: query prefix `\"query: \"`, passage prefix `\"passage: \"`. Omitting the prefix can drop MRR by 10-15 percentage points on out-of-domain datasets. Check the model's README for the exact prefix string \u2014 it varies by model family." + }, + { + "expansion_handle": "memory:01M1X0FF0K20746M62DSGEMWH1", + "id": "01M1X0NSQVB51AQGPN55WNFFWK", + "kind": "memory", + "score": 0.5567834973335266, + "summary": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing \u2014 double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1555.2664, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1339, + "mcp_result_bytes": 1446, + "wire_bytes": 1483, + "reported_used_tokens": 1446, + "working_set_bytes": 944553984, + "peak_working_set_bytes": 945467392 + }, + { + "query": "ORT thread pool contention when running multiple bench processes in parallel", + "ranked": [ + "onnx-ort-threading" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FF6ZQCCMQCC7N9WCWPFV", + "id": "01M1X0NV8EYZDABHM5S17HG6T2", + "kind": "memory", + "score": 0.9999712705612184, + "summary": "project:fact - [2026-09-07] [tags: onnx ort thread-pool parallelism cpu] ORT (ONNX Runtime) creates its own inter-op and intra-op thread pools. In a multi-process bench setup, each child inherits these pools and they compete for CPU cores. Set `SessionOptionsBuilder::with_intra_threads(1).with_inter_threads(1)` if you're running many parallel bench processes \u2014 this sacrifices per-inference throughput for lower contention." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1292.838, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 802, + "mcp_result_bytes": 883, + "wire_bytes": 920, + "reported_used_tokens": 883, + "working_set_bytes": 944611328, + "peak_working_set_bytes": 945524736 + }, + { + "query": "git worktrees share the .kimetsu brain \u2014 how do I isolate test runs?", + "ranked": [ + "git-worktree-brain-isolation", + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FF865VP00ZZP7GC92453", + "id": "01M1X0NWH4T160PTA3P3EDH6BW", + "kind": "memory", + "score": 0.9999784231185912, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root \u2014 if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + }, + { + "expansion_handle": "memory:01M1X0FD0AKH41YXD7612WYVVX", + "id": "01M1X0NWH47AC9WVMSMF0TNTWC", + "kind": "memory", + "score": 0.9857950210571288, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1277.7091999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1435, + "mcp_result_bytes": 1534, + "wire_bytes": 1571, + "reported_used_tokens": 1534, + "working_set_bytes": 944758784, + "peak_working_set_bytes": 945676288 + }, + { + "query": "when is it safe to use --no-verify on git commit?", + "ranked": [ + "git-hooks-bypass" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FF9DDBWKSDA3QE8DRAZX", + "id": "01M1X0NXRXEJRRNN9DN7SZM09A", + "kind": "memory", + "score": 0.99863463640213, + "summary": "project:fact - [2026-09-07] [tags: git hooks bypass pre-commit skip] `git commit --no-verify` skips ALL hooks (pre-commit and commit-msg). Never use this in shared team repos where hooks enforce quality gates (lint, tests, memory harvest). Instead, fix the failing hook." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1323.2373, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 645, + "mcp_result_bytes": 726, + "wire_bytes": 763, + "reported_used_tokens": 726, + "working_set_bytes": 944771072, + "peak_working_set_bytes": 945684480 + }, + { + "query": "reduce clone size and bandwidth for server-side repo ingest", + "ranked": [ + "git-sparse-checkout", + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FFAJQ9WXZWCPRAXYDE0J", + "id": "01M1X0NZ271WJKB6DKRMW0WBKJ", + "kind": "memory", + "score": 0.9952669143676758, + "summary": "project:fact - [tags: git sparse-checkout partial-clone bandwidth] `git sparse-checkout init --cone` combined with `git clone --filter=blob:none` (partial clone) fetches only the commit graph and tree objects, not blobs. Individual blobs are fetched on demand when accessed. This cuts clone time for large repos from minutes to seconds." + }, + { + "expansion_handle": "memory:01M1X0FCFT22SRB88D2ZQV8BRK", + "id": "01M1X0NZ272ZXAH02CYGXTPJHP", + "kind": "memory", + "score": 0.5591859817504883, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1708.7052, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1744, + "mcp_result_bytes": 1843, + "wire_bytes": 1880, + "reported_used_tokens": 1843, + "working_set_bytes": 944787456, + "peak_working_set_bytes": 945696768 + }, + { + "query": "spurious diffs from Windows CRLF line ending conversion in git", + "ranked": [ + "git-line-endings-windows" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FFBVED4S8NP7GTF6GNHY", + "id": "01M1X0P0QQGC4ZY2A031TFQJNW", + "kind": "memory", + "score": 0.9999781847000122, + "summary": "project:fact - [tags: git line-endings windows crlf autocrlf] On Windows, `core.autocrlf=true` (git's default for Windows installs) converts LF to CRLF on checkout and CRLF to LF on commit. This causes spurious diffs when files are edited on Windows then committed \u2014 the content is identical but the line endings differ in the index vs the working tree. Fix: set `core.autocrlf=false` and `.gitattributes` with `* text=auto eol=lf` for the repo." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1635.0937000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 940, + "reported_used_tokens": 903, + "working_set_bytes": 944795648, + "peak_working_set_bytes": 945700864 + }, + { + "query": "git submodule always gets the wrong commit in CI", + "ranked": [ + "git-submodule-pinning", + "git-hooks-bypass" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FFD1SDFGRNM9A10G981G", + "id": "01M1X0P2AYBKYFSJAA27ZSM6CK", + "kind": "memory", + "score": 0.9990686774253844, + "summary": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip \u2014 this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version." + }, + { + "expansion_handle": "memory:01M1X0FF9DDBWKSDA3QE8DRAZX", + "id": "01M1X0P2AY43HRA9B6QZR5SAGV", + "kind": "memory", + "score": 0.7485930919647217, + "summary": "project:fact - [tags: git hooks bypass pre-commit skip] `git commit --no-verify` skips ALL hooks (pre-commit and commit-msg). Never use this in shared team repos where hooks enforce quality gates (lint, tests, memory harvest). Instead, fix the failing hook." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1277.2985999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1157, + "mcp_result_bytes": 1256, + "wire_bytes": 1293, + "reported_used_tokens": 1256, + "working_set_bytes": 944803840, + "peak_working_set_bytes": 945717248 + }, + { + "query": "accidentally ran git reset --hard and lost commits \u2014 can I recover?", + "ranked": [ + "git-reflog-rescue" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FFE9765XZEN4SP9VXASC", + "id": "01M1X0P3K71YZKZ836R3RK2K4Y", + "kind": "memory", + "score": 0.9999775886535645, + "summary": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone \u2014 they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only \u2014 remote reflog is not accessible via normal git commands." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1660.4432, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 762, + "mcp_result_bytes": 843, + "wire_bytes": 880, + "reported_used_tokens": 843, + "working_set_bytes": 944807936, + "peak_working_set_bytes": 945717248 + }, + { + "query": "blocking SQLite call from an async tokio handler causes latency spikes", + "ranked": [ + "tokio-blocking-in-async", + "tokio-runtime-in-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FFFE7JSJHPASKCZ506M2", + "id": "01M1X0P56K6C17ZPMQ0AZZ299N", + "kind": "memory", + "score": 0.9999537467956544, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + }, + { + "expansion_handle": "memory:01M1X0FFGR17NXBNS2X6A04RAC", + "id": "01M1X0P56KCYHT29E1WCMG5RGY", + "kind": "memory", + "score": 0.7837615609169006, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1270.8875, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1370, + "mcp_result_bytes": 1477, + "wire_bytes": 1514, + "reported_used_tokens": 1477, + "working_set_bytes": 944807936, + "peak_working_set_bytes": 945717248 + }, + { + "query": "Cannot start a runtime from within a runtime in a tokio test", + "ranked": [ + "tokio-runtime-in-tests", + "tokio-blocking-in-async" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FFGR17NXBNS2X6A04RAC", + "id": "01M1X0P6E63C6FRG6XHQ75ZT8V", + "kind": "memory", + "score": 0.9999808073043824, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + }, + { + "expansion_handle": "memory:01M1X0FFFE7JSJHPASKCZ506M2", + "id": "01M1X0P6E6F7DS7JV40VJFSWNJ", + "kind": "memory", + "score": 0.7143720388412476, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1497.655, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1370, + "mcp_result_bytes": 1477, + "wire_bytes": 1514, + "reported_used_tokens": 1477, + "working_set_bytes": 944807936, + "peak_working_set_bytes": 945721344 + }, + { + "query": "tokio select cancels the other branch and loses the value in the channel", + "ranked": [ + "tokio-select-cancellation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FFJ7R8DVQM39SBJGQGT6", + "id": "01M1X0P7X6HTQKSPYHDK52NWB2", + "kind": "memory", + "score": 0.9996563196182252, + "summary": "project:fact - [tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1290.9843999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 751, + "mcp_result_bytes": 832, + "wire_bytes": 869, + "reported_used_tokens": 832, + "working_set_bytes": 944807936, + "peak_working_set_bytes": 945725440 + }, + { + "query": "mpsc channel backpressure causing senders to stall", + "ranked": [ + "tokio-channel-backpressure" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FFKGJ6JCY3DN5QKSZ5RY", + "id": "01M1X0P95KA0AJND7FZPXTE9TJ", + "kind": "memory", + "score": 0.999940037727356, + "summary": "project:fact - [tags: tokio mpsc channel backpressure async rust] `tokio::sync::mpsc::channel(N)` with a bounded buffer provides backpressure: senders block when the buffer is full. This prevents unbounded memory growth but can cause sender tasks to stall. Choosing N: too small causes frequent backpressure (throughput drops); too large defeats the purpose." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1375.9653, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 732, + "mcp_result_bytes": 813, + "wire_bytes": 850, + "reported_used_tokens": 813, + "working_set_bytes": 944824320, + "peak_working_set_bytes": 945729536 + }, + { + "query": "overhead from calling spawn_blocking on every single query request", + "ranked": [ + "tokio-spawn-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FFMQF838NAMWG1GHHFKT", + "id": "01M1X0PAGQ6YDB79WBN107E3YX", + "kind": "memory", + "score": 0.9911772012710572, + "summary": "project:fact - [tags: tokio spawn_blocking thread-pool rust blocking] `tokio::task::spawn_blocking` places work on a dedicated blocking thread pool (default up to 512 threads, configurable via `Builder::max_blocking_threads`). Each call creates or reuses a thread \u2014 there's no true pooling, threads may be created on demand. For many short-duration blocking calls (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1294.3661, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 746, + "mcp_result_bytes": 827, + "wire_bytes": 864, + "reported_used_tokens": 827, + "working_set_bytes": 944824320, + "peak_working_set_bytes": 945733632 + }, + { + "query": "axum server panics during shutdown because the DB pool is already closed", + "ranked": [ + "tokio-shutdown-ordering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FFRDD2BYGMKJ5P79H1B9", + "id": "01M1X0PBS6AQVZ6K51DT0E4ETQ", + "kind": "memory", + "score": 0.9920267462730408, + "summary": "project:fact - [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries \u2014 the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1824.417, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 933, + "mcp_result_bytes": 1014, + "wire_bytes": 1051, + "reported_used_tokens": 1014, + "working_set_bytes": 944865280, + "peak_working_set_bytes": 945782784 + }, + { + "query": "reqwest Client created per-request defeats connection pooling", + "ranked": [ + "http-connection-pooling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FFSMFZ03ZQX4Y38B8FEA", + "id": "01M1X0PDJ79EA1XD91NVHVR4B8", + "kind": "memory", + "score": 0.9999794960021972, + "summary": "project:fact - [tags: http reqwest connection-pool keep-alive rust] reqwest's `Client` holds a connection pool; always create ONE `Client` instance and clone it for each handler \u2014 cloning is cheap (Arc under the hood). Creating a `Client::new()` per request defeats connection pooling and causes TCP connection exhaustion under load. The default pool settings: max_idle_per_host=usize::MAX (unbounded), idle_timeout=90s." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1237.9750000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 797, + "mcp_result_bytes": 878, + "wire_bytes": 915, + "reported_used_tokens": 878, + "working_set_bytes": 944865280, + "peak_working_set_bytes": 945782784 + }, + { + "query": "LLM request times out during streaming \u2014 which timeout setting applies?", + "ranked": [ + "http-timeout-layering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FFTX4RE26HKRK680RP5P", + "id": "01M1X0PERWH3KVB65B1YC8WEW4", + "kind": "memory", + "score": 0.999026656150818, + "summary": "project:fact - [tags: http reqwest timeout connect read total rust] reqwest has three distinct timeout knobs: `connect_timeout`, `read_timeout`, and `timeout` (total). They compose: if all three are set, the request fails at whichever fires first. For LLM API calls with streaming responses, `read_timeout` must be larger than the slowest expected token (often 30-60s) while `connect_timeout` can be tight (3-5s)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1385.7456, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 788, + "mcp_result_bytes": 869, + "wire_bytes": 906, + "reported_used_tokens": 869, + "working_set_bytes": 944865280, + "peak_working_set_bytes": 945782784 + }, + { + "query": "how do I safely retry a POST to the LLM API without creating duplicates?", + "ranked": [ + "http-retry-idempotency" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FFW578DXR9SCW9M3JVMA", + "id": "01M1X0PG4Q9PD2Q4FMS5BD450A", + "kind": "memory", + "score": 0.9997218251228333, + "summary": "project:fact - [tags: http retry idempotency post put reqwest] Only retry idempotent requests automatically. GET, HEAD, PUT, DELETE are idempotent. POST is NOT \u2014 retrying a POST may create duplicate resources." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1592.0398, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 585, + "mcp_result_bytes": 666, + "wire_bytes": 703, + "reported_used_tokens": 666, + "working_set_bytes": 944865280, + "peak_working_set_bytes": 945782784 + }, + { + "query": "custom enterprise root CA not trusted by rustls on Windows", + "ranked": [ + "http-tls-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FFXDBCM03W2SHT9GW61G", + "id": "01M1X0PHP47PDA3Z34QJ08MHD7", + "kind": "memory", + "score": 0.9999604225158693, + "summary": "project:fact - [tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle \u2014 the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1646.4207, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 780, + "mcp_result_bytes": 861, + "wire_bytes": 898, + "reported_used_tokens": 861, + "working_set_bytes": 944865280, + "peak_working_set_bytes": 945782784 + }, + { + "query": "parsing server-sent events when a single TCP chunk contains a partial SSE frame", + "ranked": [ + "http-streaming-bodies" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FFYQSRFZK4GH66S85GM2", + "id": "01M1X0PK984DC4AXKA9CS378FN", + "kind": "memory", + "score": 0.9942779541015624, + "summary": "project:fact - [2026-09-07] [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding \u2014 a chunk may split across frame boundaries." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1277.862, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 859, + "mcp_result_bytes": 940, + "wire_bytes": 977, + "reported_used_tokens": 940, + "working_set_bytes": 944865280, + "peak_working_set_bytes": 945782784 + }, + { + "query": "reqwest does not use the system proxy settings on Windows", + "ranked": [ + "http-proxy-env", + "http-tls-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FG03JFV5NMMDNA6KWJJS", + "id": "01M1X0PMH9W1D8VDXKA3JBA5XF", + "kind": "memory", + "score": 0.9999799728393556, + "summary": "project:fact - [tags: http proxy environment reqwest rust corporate] reqwest respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` environment variables by default (with `default-tls` or `rustls-tls`). In a corporate network, these may redirect traffic through an intercepting proxy that breaks mTLS or adds latency. To disable proxy usage entirely: `reqwest::ClientBuilder::no_proxy()`." + }, + { + "expansion_handle": "memory:01M1X0FFXDBCM03W2SHT9GW61G", + "id": "01M1X0PMH9GREQ5JXHD4KDT14C", + "kind": "memory", + "score": 0.9782498478889464, + "summary": "project:fact - [tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle \u2014 the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1321.126, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1310, + "mcp_result_bytes": 1409, + "wire_bytes": 1446, + "reported_used_tokens": 1409, + "working_set_bytes": 944865280, + "peak_working_set_bytes": 945782784 + }, + { + "query": "insta snapshot tests fail in CI because output includes a timestamp", + "ranked": [ + "testing-snapshot-churn" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FG1B1Y59JM7ZFMRCHXFN", + "id": "01M1X0PNV78F8R9JDV19V64XHV", + "kind": "memory", + "score": 0.9999759197235109, + "summary": "project:fact - [tags: testing snapshot insta assert churn rust] Snapshot tests (e.g. with the `insta` crate) fail whenever the output changes, even for intended changes. In CI, they fail loudly; locally, `cargo insta review` walks you through accepting or rejecting changes." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1400.999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 649, + "mcp_result_bytes": 730, + "wire_bytes": 767, + "reported_used_tokens": 730, + "working_set_bytes": 944984064, + "peak_working_set_bytes": 945897472 + }, + { + "query": "two test workers writing to the same temp directory path race each other", + "ranked": [ + "testing-temp-dirs-ci" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FG2M8GQXGWMSNVWE993Y", + "id": "01M1X0PQ6PSKWGYTVCFW4H3Z92", + "kind": "memory", + "score": 0.9582907557487488, + "summary": "project:fact - [tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1381.8448, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 755, + "mcp_result_bytes": 836, + "wire_bytes": 873, + "reported_used_tokens": 836, + "working_set_bytes": 944984064, + "peak_working_set_bytes": 945897472 + }, + { + "query": "test passes locally but fails on a slow CI runner due to a 100ms sleep", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1482.5511, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 944988160, + "peak_working_set_bytes": 945909760 + }, + { + "query": "proptest found a hash collision in text normalization that example tests missed", + "ranked": [ + "testing-property-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FG557BF0ARGJXRT0RFEG", + "id": "01M1X0PT0179MMVYZR5PW1Q68M", + "kind": "memory", + "score": 0.9999779462814332, + "summary": "project:fact - [tags: testing property-based proptest quickcheck rust] Property-based tests (proptest, quickcheck) find edge cases that example-based tests miss. For kimetsu's memory text normalization, proptest found that zero-width joiner characters and right-to-left marks caused hash collisions. Run proptest with `PROPTEST_CASES=10000` in CI for thorough coverage." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1473.057, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 744, + "mcp_result_bytes": 825, + "wire_bytes": 862, + "reported_used_tokens": 825, + "working_set_bytes": 944988160, + "peak_working_set_bytes": 945909760 + }, + { + "query": "set_var in tests races when cargo test runs them in parallel", + "ranked": [ + "testing-serial-vs-parallel" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FG6A0MZBPHAR0ZCH68DY", + "id": "01M1X0PVEBRKKRJH5NTP43BHX7", + "kind": "memory", + "score": 0.9995468258857728, + "summary": "project:fact - [2026-09-07] [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1303.2363, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 832, + "mcp_result_bytes": 913, + "wire_bytes": 950, + "reported_used_tokens": 913, + "working_set_bytes": 944988160, + "peak_working_set_bytes": 945909760 + }, + { + "query": "hardcoded JSON fixtures broke after a schema migration", + "ranked": [ + "testing-fixture-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FGA4RAT8W5S9SMQJJQP7", + "id": "01M1X0PWQVSSAEBVTDHC45YDQM", + "kind": "memory", + "score": 0.9999451637268066, + "summary": "project:fact - [2026-09-07] [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1313.7179999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 783, + "mcp_result_bytes": 864, + "wire_bytes": 901, + "reported_used_tokens": 864, + "working_set_bytes": 944988160, + "peak_working_set_bytes": 945909760 + }, + { + "query": "debug print in the MCP handler corrupts the JSON-Lines protocol stream", + "ranked": [ + "mcp-stdout-protocol" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FGB6CRFP501Z6A7S6SKX", + "id": "01M1X0PY09H6W4YMG8HBPN18W0", + "kind": "memory", + "score": 0.9999716281890868, + "summary": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1625.0010000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 705, + "mcp_result_bytes": 786, + "wire_bytes": 823, + "reported_used_tokens": 786, + "working_set_bytes": 945242112, + "peak_working_set_bytes": 946155520 + }, + { + "query": "kimetsu MCP tool call times out because embedding model is re-initialized every call", + "ranked": [ + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FGCEM52ZVFVATQ07Y6WA", + "id": "01M1X0PZK75XZYC33HFMWZ15YG", + "kind": "memory", + "score": 0.9981862902641296, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1259.9103, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 858, + "mcp_result_bytes": 939, + "wire_bytes": 976, + "reported_used_tokens": 939, + "working_set_bytes": 945242112, + "peak_working_set_bytes": 946155520 + }, + { + "query": "env var set after host launch is not visible to the MCP server process", + "ranked": [ + "mcp-env-propagation", + "kimetsu-daemon-lifecycle" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FGDNMF77E78V0ZWRASVD", + "id": "01M1X0Q0T496XV01THM84FVK69", + "kind": "memory", + "score": 0.9996464252471924, + "summary": "project:fact - [2026-09-07] [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment \u2014 changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate." + }, + { + "expansion_handle": "memory:01M1X0FH1B8EG42XEA3S29MF4X", + "id": "01M1X0Q0T4VY4R9CPSRJB7Z5TV", + "kind": "memory", + "score": 0.8873274922370911, + "summary": "project:fact - [2026-09-07] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1635.2199, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1267, + "mcp_result_bytes": 1366, + "wire_bytes": 1403, + "reported_used_tokens": 1366, + "working_set_bytes": 945242112, + "peak_working_set_bytes": 946163712 + }, + { + "query": "MCP tool call fails because a required field is missing from the JSON input", + "ranked": [ + "mcp-schema-validation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FGEZPZWHY4P3S38K40JB", + "id": "01M1X0Q2D5QG6ZEK8KZJETXTA4", + "kind": "memory", + "score": 0.9998551607131958, + "summary": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array \u2014 omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1429.5613999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 799, + "mcp_result_bytes": 880, + "wire_bytes": 917, + "reported_used_tokens": 880, + "working_set_bytes": 945242112, + "peak_working_set_bytes": 946163712 + }, + { + "query": "Claude Code rejects the tool name with a hyphen in it", + "ranked": [ + "mcp-tool-naming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FGG7B802AF0HEGPBJMGV", + "id": "01M1X0Q3T755ZNWQ8X9TYT2WTH", + "kind": "memory", + "score": 0.999729573726654, + "summary": "project:fact - [tags: mcp tool naming convention kimetsu] MCP tool names must be valid identifiers for all host agents. Claude Code restricts tool names to `[a-zA-Z0-9_-]` and max 64 chars. Use `snake_case` (kimetsu_brain_context, kimetsu_brain_record) \u2014 hyphen is technically allowed but some hosts reject it." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1551.5729999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 686, + "mcp_result_bytes": 767, + "wire_bytes": 804, + "reported_used_tokens": 767, + "working_set_bytes": 945242112, + "peak_working_set_bytes": 946163712 + }, + { + "query": "MCP response path uses backslashes and the host rejects it", + "ranked": [ + "mcp-transcript-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FGHH4WVX4F45X1MVNB7F", + "id": "01M1X0Q5AHSYRG0QWED0D4YW4F", + "kind": "memory", + "score": 0.9998076558113098, + "summary": "project:fact - [tags: mcp transcript paths kimetsu hooks runs] kimetsu writes run transcripts to `/.kimetsu/runs//`. The post-session hook reads the latest run's transcript to trigger memory harvest. On Windows, the path uses backslashes internally but the MCP JSON must use forward slashes or the host may reject path-type arguments." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1773.9849, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 724, + "mcp_result_bytes": 805, + "wire_bytes": 842, + "reported_used_tokens": 805, + "working_set_bytes": 945242112, + "peak_working_set_bytes": 946163712 + }, + { + "query": "AWS credentials not found \u2014 which env var does kimetsu read for Bedrock?", + "ranked": [ + "aws-credentials-chain", + "aws-region-resolution", + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FGJWSYM3MH0XM7B9KC50", + "id": "01M1X0Q724A32HCSA4AMTJKVMR", + "kind": "memory", + "score": 0.9999332427978516, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + }, + { + "expansion_handle": "memory:01M1X0FGMCP8YBCDTS3WYEGCXD", + "id": "01M1X0Q7247BWAVS58D8E8P4N0", + "kind": "memory", + "score": 0.999756395816803, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X0FCP7QJPKBMCYASHDFZC7", + "id": "01M1X0Q724SMB7JBWZZEAR7669", + "kind": "memory", + "score": 0.9983052015304564, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X0FCX344YDYSFM8JDDK4G1", + "id": "01M1X0Q724MPCT8DYQ23D2MD6J", + "kind": "memory", + "score": 0.7727437615394592, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1628.7489000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3454, + "mcp_result_bytes": 3617, + "wire_bytes": 3654, + "reported_used_tokens": 3617, + "working_set_bytes": 945254400, + "peak_working_set_bytes": 946167808 + }, + { + "query": "Bedrock InvokeModel fails because the region is not configured", + "ranked": [ + "aws-region-resolution", + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FGMCP8YBCDTS3WYEGCXD", + "id": "01M1X0Q8N3QAH6ZDEEB347AFQK", + "kind": "memory", + "score": 0.9998329877853394, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X0FCP7QJPKBMCYASHDFZC7", + "id": "01M1X0Q8N3NFNK4ZJ7K3HW6CSB", + "kind": "memory", + "score": 0.871902346611023, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X0FCX344YDYSFM8JDDK4G1", + "id": "01M1X0Q8N33H053QCDHJAEB0XF", + "kind": "memory", + "score": 0.7683040499687195, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1628.3620999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2786, + "mcp_result_bytes": 2931, + "wire_bytes": 2968, + "reported_used_tokens": 2931, + "working_set_bytes": 945246208, + "peak_working_set_bytes": 946167808 + }, + { + "query": "how do I handle ThrottlingException from Bedrock with exponential backoff?", + "ranked": [ + "aws-retry-throttling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FGNK72S7ATWPNR442PEP", + "id": "01M1X0QA8BSWY4VABFYZGEAJ5G", + "kind": "memory", + "score": 0.9984448552131652, + "summary": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with \u00b125% jitter." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1676.3102000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 771, + "mcp_result_bytes": 868, + "wire_bytes": 905, + "reported_used_tokens": 868, + "working_set_bytes": 945238016, + "peak_working_set_bytes": 946167808 + }, + { + "query": "generating a presigned S3 URL for brain export without exposing credentials", + "ranked": [ + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FGPV1QN2NJJ22691MFGC", + "id": "01M1X0QBW5H32Q3FFNA1WY76FY", + "kind": "memory", + "score": 0.9999775886535645, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1635.1236, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 875, + "mcp_result_bytes": 956, + "wire_bytes": 993, + "reported_used_tokens": 956, + "working_set_bytes": 945242112, + "peak_working_set_bytes": 946167808 + }, + { + "query": "IMDSv2 token required for instance metadata \u2014 PUT before GET", + "ranked": [ + "aws-instance-metadata" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FGR2NPKK4N8WF88YPWQS", + "id": "01M1X0QDF9Q068E1CMVXMRMWPR", + "kind": "memory", + "score": 0.999979853630066, + "summary": "project:fact - [2026-09-07] [tags: aws imds instance-metadata ec2 token] The AWS Instance Metadata Service v2 (IMDSv2) requires a session token: PUT `http://169.254.169.254/latest/api/token` with `X-aws-ec2-metadata-token-ttl-seconds: 21600` to get a token, then GET metadata with `X-aws-ec2-metadata-token: `. IMDSv1 (no token) is disabled on hardened instances. The metadata endpoint is only reachable from within EC2 \u2014 a connection timeout means you're not on EC2." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1457.5318, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 851, + "mcp_result_bytes": 932, + "wire_bytes": 969, + "reported_used_tokens": 932, + "working_set_bytes": 945250304, + "peak_working_set_bytes": 946167808 + }, + { + "query": "Cargo cache key strategy for GitHub Actions to avoid toolchain version collisions", + "ranked": [ + "ci-cache-keys" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FGVGDB8CCW5JTA2MEN29", + "id": "01M1X0QEWT6XZ6C1PEG07TEJB4", + "kind": "memory", + "score": 0.9999439716339112, + "summary": "project:fact - [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key \u2014 macOS and Windows have incompatible artifact formats." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1295.734, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 788, + "mcp_result_bytes": 869, + "wire_bytes": 906, + "reported_used_tokens": 869, + "working_set_bytes": 945250304, + "peak_working_set_bytes": 946167808 + }, + { + "query": "CI matrix has 18 jobs and costs too much \u2014 how do I reduce it?", + "ranked": [ + "ci-matrix-explosion" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FGWK1C3VTTKQFT04YY5G", + "id": "01M1X0QG5GXEQBP86TDQEZD7M9", + "kind": "memory", + "score": 0.99962317943573, + "summary": "project:fact - [tags: ci github-actions matrix jobs resources] A CI matrix combining OS (3) x Rust toolchain (3) x features (2) = 18 jobs. Each spawns a runner; at $0.008/min for Ubuntu and $0.016/min for Windows, a 10-minute build costs $2.40 per push. Reduce: test the full matrix only on PRs to main; on feature branches, test only Linux+stable." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1329.9605, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 721, + "mcp_result_bytes": 802, + "wire_bytes": 839, + "reported_used_tokens": 802, + "working_set_bytes": 945246208, + "peak_working_set_bytes": 946167808 + }, + { + "query": "GitHub Actions secret accidentally printed in build logs", + "ranked": [ + "ci-secrets-masking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FGXP7GSA2220GFB58ZHM", + "id": "01M1X0QHF06B14DNP1YYCZDH07", + "kind": "memory", + "score": 0.9951270818710328, + "summary": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output \u2014 but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1580.178, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 711, + "mcp_result_bytes": 792, + "wire_bytes": 829, + "reported_used_tokens": 792, + "working_set_bytes": 945246208, + "peak_working_set_bytes": 946167808 + }, + { + "query": "how long do GitHub Actions artifacts persist and what's the storage limit?", + "ranked": [ + "ci-artifact-retention" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FGYYD4FR7T9A604YDQ5X", + "id": "01M1X0QK1WCZMVQ47AMQAPE0SN", + "kind": "memory", + "score": 0.9989684820175172, + "summary": "project:fact - [tags: ci github-actions artifacts retention benchmark] GitHub Actions artifacts are retained for 90 days (default). For benchmark results, use `actions/upload-artifact` with `retention-days: 365` for long-term tracking. The free tier has 500MB storage \u2014 per-combo JSON files from kimetsu bench (each ~60KB) add up fast if you upload them on every push." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1482.1363999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 744, + "mcp_result_bytes": 825, + "wire_bytes": 862, + "reported_used_tokens": 825, + "working_set_bytes": 945242112, + "peak_working_set_bytes": 946167808 + }, + { + "query": "timing-based test flake in CI \u2014 quarantine or fix?", + "ranked": [ + "ci-flaky-quarantine", + "testing-time-dependent-flakes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FH01PKV444BK3JFDFGP2", + "id": "01M1X0QMEXVNGD5XQH96HSM15B", + "kind": "memory", + "score": 0.9940990209579468, + "summary": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal \u2014 a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output." + }, + { + "expansion_handle": "memory:01M1X0FG40THR6GHM4YYCSGNRV", + "id": "01M1X0QMEX371HN85HW14YB2W4", + "kind": "memory", + "score": 0.7894570231437683, + "summary": "project:fact - [tags: testing time flaky clock mock rust] Tests that depend on wall-clock time are inherently flaky under load (slow CI runners, GC pauses). Abstract time behind a trait (`Clock: Fn() -> SystemTime`) injected at construction, and supply a fake in tests. For tests checking that something happened \"within N seconds\", use a generous multiple of the expected duration (10x is not unreasonable for CI)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1250.1386, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1340, + "mcp_result_bytes": 1443, + "wire_bytes": 1480, + "reported_used_tokens": 1443, + "working_set_bytes": 945242112, + "peak_working_set_bytes": 946167808 + }, + { + "query": "kimetsu doctor says the MCP server is running \u2014 how do I stop it before an update?", + "ranked": [ + "kimetsu-daemon-lifecycle", + "mcp-env-propagation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FH1B8EG42XEA3S29MF4X", + "id": "01M1X0QNNMXND8NXEFN9ZDHTJ2", + "kind": "memory", + "score": 0.9999032020568848, + "summary": "project:fact - [2026-09-07] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1X0FGDNMF77E78V0ZWRASVD", + "id": "01M1X0QNNMVGJEWZ8C9B2V9FX1", + "kind": "memory", + "score": 0.9228461980819702, + "summary": "project:fact - [2026-09-07] [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment \u2014 changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1654.7275, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1266, + "mcp_result_bytes": 1365, + "wire_bytes": 1402, + "reported_used_tokens": 1365, + "working_set_bytes": 945242112, + "peak_working_set_bytes": 946167808 + }, + { + "query": "noise capsules consuming token budget without contributing retrieval signal", + "ranked": [ + "kimetsu-capsule-budgets" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FH2G7888PNCFY3293VFX", + "id": "01M1X0QQ9PKE9ADXK3MARKM49G", + "kind": "memory", + "score": 0.9999643564224244, + "summary": "project:fact - [tags: kimetsu capsule tokens budget retrieval] kimetsu retrieval enforces a token budget per capsule type: memory capsules are capped at 6000 tokens total (across all retrieved memories), file capsules at 3000 tokens. When a memory is large and would exceed the budget, it is truncated at a sentence boundary. The budget is enforced AFTER reranking \u2014 reranking may reorder results so that a truncated high-ranked memory displaces a full lower-ranked one." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1565.6799, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 847, + "mcp_result_bytes": 928, + "wire_bytes": 965, + "reported_used_tokens": 928, + "working_set_bytes": 945242112, + "peak_working_set_bytes": 946167808 + }, + { + "query": "kimetsu_brain_record writes to the wrong brain location \u2014 user vs project scope", + "ranked": [ + "kimetsu-memory-scopes", + "kimetsu-write-tools-gate", + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FH3M8E6WW7KKMJCPESDR", + "id": "01M1X0QRV5T24SMNJ1B0MNXTMH", + "kind": "memory", + "score": 0.9998672008514404, + "summary": "project:fact - [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available \u2014 if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope." + }, + { + "expansion_handle": "memory:01M1X0FH76GS665H39MXQKH81R", + "id": "01M1X0QRV502D6XS62JH0GE7W8", + "kind": "memory", + "score": 0.9389453530311584, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level \u2014 disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1X0FD0AKH41YXD7612WYVVX", + "id": "01M1X0QRV54YGWEA85PJRGXGPT", + "kind": "memory", + "score": 0.9388486742973328, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1320.6583, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2097, + "mcp_result_bytes": 2214, + "wire_bytes": 2251, + "reported_used_tokens": 2214, + "working_set_bytes": 945238016, + "peak_working_set_bytes": 946167808 + }, + { + "query": "how do I configure kimetsu to use Claude Haiku for harvesting but Opus for the agent?", + "ranked": [ + "kimetsu-distiller-config", + "aws-region-resolution", + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FH4T1903DTT5G8SF70RJ", + "id": "01M1X0QT3J6VHQZE6HZ8T4P7SX", + "kind": "memory", + "score": 0.9999725818634032, + "summary": "project:fact - [tags: kimetsu distiller harvest config provider] The kimetsu distiller (auto-harvester) uses a SEPARATE provider configuration from the main agent: `distiller.provider`, `distiller.model`, `distiller.api_key`. This allows running the agent on an expensive model (Claude Opus) while harvesting with a cheap model (Claude Haiku). If `distiller.provider` is not set, it inherits `provider`." + }, + { + "expansion_handle": "memory:01M1X0FGMCP8YBCDTS3WYEGCXD", + "id": "01M1X0QT3KJ0C2S6T8PBAMX619", + "kind": "memory", + "score": 0.8705393075942993, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X0FCP7QJPKBMCYASHDFZC7", + "id": "01M1X0QT3KYYYXM6RY8WDMJA2T", + "kind": "memory", + "score": 0.8454174399375916, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1582.9997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2348, + "mcp_result_bytes": 2473, + "wire_bytes": 2510, + "reported_used_tokens": 2473, + "working_set_bytes": 945242112, + "peak_working_set_bytes": 946167808 + }, + { + "query": "first agent turn is slow because kimetsu proactive hook runs embedding inference", + "ranked": [ + "kimetsu-proactive-hooks", + "kimetsu-distiller-config", + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FH61A8CTR8N2AAE3XT5Y", + "id": "01M1X0QVMW3XH3153N4RA99JNQ", + "kind": "memory", + "score": 0.999129831790924, + "summary": "project:fact - [2026-09-07] [tags: kimetsu proactive hooks context injection] kimetsu's proactive context injection runs before each agent turn (pre-turn hook) and injects relevant memories into the system prompt prefix. The hook invocation adds latency to the first token: embedding inference + vector search + reranking + context formatting. On a cold start, this can be 1-3 seconds." + }, + { + "expansion_handle": "memory:01M1X0FH4T1903DTT5G8SF70RJ", + "id": "01M1X0QVMWY15AF9N14BZMZSPQ", + "kind": "memory", + "score": 0.8709061145782471, + "summary": "project:fact - [2026-09-07] [tags: kimetsu distiller harvest config provider] The kimetsu distiller (auto-harvester) uses a SEPARATE provider configuration from the main agent: `distiller.provider`, `distiller.model`, `distiller.api_key`. This allows running the agent on an expensive model (Claude Opus) while harvesting with a cheap model (Claude Haiku). If `distiller.provider` is not set, it inherits `provider`." + }, + { + "expansion_handle": "memory:01M1X0FGCEM52ZVFVATQ07Y6WA", + "id": "01M1X0QVMW9F14VZF832Q07HK0", + "kind": "memory", + "score": 0.6799831390380859, + "summary": "project:fact - [2026-09-07] [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1796.3226, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1967, + "mcp_result_bytes": 2084, + "wire_bytes": 2121, + "reported_used_tokens": 2084, + "working_set_bytes": 945496064, + "peak_working_set_bytes": 946409472 + }, + { + "query": "make the kimetsu brain read-only for certain repos on a shared remote server", + "ranked": [ + "kimetsu-write-tools-gate", + "remote-ingest-split-roots", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FH76GS665H39MXQKH81R", + "id": "01M1X0QXDJ521EFM25EK91P685", + "kind": "memory", + "score": 0.9999514818191528, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level \u2014 disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1X0FCFT22SRB88D2ZQV8BRK", + "id": "01M1X0QXDJRQ6R5SRXJ4SZNZYC", + "kind": "memory", + "score": 0.9976721405982972, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1X0FCJ72EZEB2W227G8E2PV", + "id": "01M1X0QXDJKQA22TW2SWBF3MAD", + "kind": "memory", + "score": 0.915355622768402, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1637.8292000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2725, + "mcp_result_bytes": 2890, + "wire_bytes": 2927, + "reported_used_tokens": 2890, + "working_set_bytes": 945500160, + "peak_working_set_bytes": 946413568 + }, + { + "query": "kimetsu FTS search misses 'deadlocking' when memory says 'deadlock'", + "ranked": [ + "kimetsu-query-stemming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FHB81R7H0QDZCBR6FZ3P", + "id": "01M1X0QZ0YP3WQFTFQ01WE95PH", + "kind": "memory", + "score": 0.989694595336914, + "summary": "project:fact - [2026-09-07] [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1268.8189, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 781, + "mcp_result_bytes": 878, + "wire_bytes": 915, + "reported_used_tokens": 878, + "working_set_bytes": 945500160, + "peak_working_set_bytes": 946413568 + }, + { + "query": "how does pool size affect retrieval recall and latency in the bench?", + "ranked": [ + "kimetsu-rerank-pool" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FHCG2KV70P84XPQ9BV72", + "id": "01M1X0R08922Y59B81582PDEQ5", + "kind": "memory", + "score": 0.999588668346405, + "summary": "project:fact - [tags: kimetsu reranker pool size ann retrieval] kimetsu's retrieval pipeline: ANN (approximate nearest neighbor) retrieves a pool of candidates, then the reranker reorders them, then the top-K are returned. The pool size (default 6 for production, 12 in bench) controls the recall-latency tradeoff: larger pool = higher recall = more reranker calls = more latency. For the jina-tiny reranker, pool 12 adds ~80ms vs pool 6." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1282.0056, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 812, + "mcp_result_bytes": 893, + "wire_bytes": 930, + "reported_used_tokens": 893, + "working_set_bytes": 945758208, + "peak_working_set_bytes": 946675712 + }, + { + "query": "second embedder in a remote bench run gets worse results than the first", + "ranked": [ + "kimetsu-bench-remote-embedder-singleton" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FHDM9E41829DQ14VK8XT", + "id": "01M1X0R1GM6WSZV9570BHQCASG", + "kind": "memory", + "score": 0.8715754747390747, + "summary": "project:fact - [2026-09-07] [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1411.7925, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 895, + "mcp_result_bytes": 976, + "wire_bytes": 1013, + "reported_used_tokens": 976, + "working_set_bytes": 945762304, + "peak_working_set_bytes": 946679808 + }, + { + "query": "what is the expected JSON schema for kimetsu brain bench dataset files?", + "ranked": [ + "kimetsu-eval-fixture-shape", + "mcp-schema-validation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FHEV9EKMD5P2WC166BTZ", + "id": "01M1X0R2WQV5P19GTFMNJXZ54G", + "kind": "memory", + "score": 0.9999712705612184, + "summary": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` \u2014 a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases)." + }, + { + "expansion_handle": "memory:01M1X0FGEZPZWHY4P3S38K40JB", + "id": "01M1X0R2WRNC5PX19CM893P4J3", + "kind": "memory", + "score": 0.7343910336494446, + "summary": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array \u2014 omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1346.3529, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1390, + "mcp_result_bytes": 1533, + "wire_bytes": 1570, + "reported_used_tokens": 1533, + "working_set_bytes": 945766400, + "peak_working_set_bytes": 946679808 + }, + { + "query": "what does MRR mean and how do I interpret a 0.01 difference between combos?", + "ranked": [ + "kimetsu-mrr-metric" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FHG8PTGR96B477P44RGJ", + "id": "01M1X0R473QRPV5PRS8YTHM57Y", + "kind": "memory", + "score": 0.930732488632202, + "summary": "project:fact - [tags: kimetsu bench mrr recall metrics evaluation] kimetsu bench reports MRR (Mean Reciprocal Rank) and Recall@K. MRR is 1/rank_of_first_relevant_result, averaged across cases; it penalizes models that rank the correct answer 2nd or 3rd. Recall@K is the fraction of cases where at least one relevant answer appears in the top K." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1344.9251, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 719, + "mcp_result_bytes": 800, + "wire_bytes": 837, + "reported_used_tokens": 800, + "working_set_bytes": 945897472, + "peak_working_set_bytes": 946810880 + }, + { + "query": "SQLITE_BUSY keeps appearing even with WAL mode enabled", + "ranked": [ + "sqlite-busy-timeout-wal", + "sqlite-wal-network-drive" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FDC458S72EPQTSVEKVE2", + "id": "01M1X0R5GGNB4M4KM15W61YNQ9", + "kind": "memory", + "score": 0.9940937161445618, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + }, + { + "expansion_handle": "memory:01M1X0FDFGF20XSWT46H55Y00V", + "id": "01M1X0R5GG79EWQ05GR6MS5PPM", + "kind": "memory", + "score": 0.7747130393981934, + "summary": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1674.1131999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1423, + "mcp_result_bytes": 1522, + "wire_bytes": 1559, + "reported_used_tokens": 1522, + "working_set_bytes": 945901568, + "peak_working_set_bytes": 946814976 + }, + { + "query": "my brain file got huge again right after I compacted it", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1423.8235000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 945909760, + "peak_working_set_bytes": 946814976 + }, + { + "query": "all my FTS queries stopped returning results after I changed the tokenizer config", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1448.0154, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 945909760, + "peak_working_set_bytes": 946823168 + }, + { + "query": "something is preventing the kimetsu binary from being replaced during update", + "ranked": [ + "kimetsu-daemon-lifecycle" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FH1B8EG42XEA3S29MF4X", + "id": "01M1X0R9YPREZKRARR9DWTX3SX", + "kind": "memory", + "score": 0.9216884970664978, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + } + ], + "positive_recall_at_4": 0.3333333333333333, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1256.0892999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 752, + "mcp_result_bytes": 833, + "wire_bytes": 870, + "reported_used_tokens": 833, + "working_set_bytes": 945909760, + "peak_working_set_bytes": 946823168 + }, + { + "query": "tool call results not appearing in the context \u2014 is the semantic floor too high?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1306.8749, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 228, + "mcp_result_bytes": 291, + "wire_bytes": 328, + "reported_used_tokens": 291, + "working_set_bytes": 945909760, + "peak_working_set_bytes": 946831360 + }, + { + "query": "CARGO_INCREMENTAL=0 in CI prevents a class of spurious compilation errors", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FDWAJJJEQ386Q52CJW2T", + "id": "01M1X0RCETPVJMYZHEEARPR5KX", + "kind": "memory", + "score": 0.7999841570854187, + "summary": "project:fact - [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1246.0889, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 877, + "mcp_result_bytes": 958, + "wire_bytes": 995, + "reported_used_tokens": 958, + "working_set_bytes": 945909760, + "peak_working_set_bytes": 946831360 + }, + { + "query": "how do I check whether my Cargo workspace respects the MSRV constraint?", + "ranked": [ + "cargo-msrv", + "cargo-patch-section", + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FDZNZN1Z8JQHAH93YFTH", + "id": "01M1X0RDP4EBS69VV962RP71PT", + "kind": "memory", + "score": 0.9985359907150269, + "summary": "project:fact - [tags: cargo rust msrv edition compatibility] Set `rust-version` in each `Cargo.toml` to declare the minimum supported Rust version (MSRV). Cargo enforces this with `--check`: `cargo check` fails if the toolchain is older than `rust-version`. Keep MSRV as old as your oldest supported deployment target." + }, + { + "expansion_handle": "memory:01M1X0FDYHZHA5GZSND58F0KDX", + "id": "01M1X0RDP4M3KQ54YSNQS3E7XH", + "kind": "memory", + "score": 0.620707631111145, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace \u2014 including transitive deps \u2014 that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1X0FDSYBHHE1BWJEBE0P4AZ", + "id": "01M1X0RDP42W1556D97GA6BPR7", + "kind": "memory", + "score": 0.6190821528434753, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1297.2096, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1915, + "mcp_result_bytes": 2036, + "wire_bytes": 2073, + "reported_used_tokens": 2036, + "working_set_bytes": 945909760, + "peak_working_set_bytes": 946831360 + }, + { + "query": "rusqlite connection opened but ON DELETE CASCADE cascade never fires", + "ranked": [ + "sqlite-foreign-keys-default-off" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FDK0D98P2Z72TEQ9V71S", + "id": "01M1X0REYFGD5PZ8ETG8TR87WP", + "kind": "memory", + "score": 0.9797621369361876, + "summary": "project:fact - [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting \u2014 every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1744.2279, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 735, + "mcp_result_bytes": 816, + "wire_bytes": 853, + "reported_used_tokens": 816, + "working_set_bytes": 945909760, + "peak_working_set_bytes": 946831360 + }, + { + "query": "I cannot connect to kimetsu-remote \u2014 something about TLS cert validation failed", + "ranked": [ + "http-tls-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FFXDBCM03W2SHT9GW61G", + "id": "01M1X0RGP8AX91GP5R57M1AFHF", + "kind": "memory", + "score": 0.5578561425209045, + "summary": "project:fact - [tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle \u2014 the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1342.0934, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 779, + "mcp_result_bytes": 860, + "wire_bytes": 897, + "reported_used_tokens": 860, + "working_set_bytes": 1000046592, + "peak_working_set_bytes": 1000964096 + }, + { + "query": "graceful shutdown fails because in-flight SQLite queries are still running when pool closes", + "ranked": [ + "tokio-shutdown-ordering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FFRDD2BYGMKJ5P79H1B9", + "id": "01M1X0RHZBM9BP01EPAM1GM722", + "kind": "memory", + "score": 0.9991455078125, + "summary": "project:fact - [2026-09-07] [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries \u2014 the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1319.047, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 944, + "mcp_result_bytes": 1025, + "wire_bytes": 1062, + "reported_used_tokens": 1025, + "working_set_bytes": 1000046592, + "peak_working_set_bytes": 1000964096 + }, + { + "query": "kimetsu-remote response takes 8 seconds \u2014 which stage is slow?", + "ranked": [ + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FGCEM52ZVFVATQ07Y6WA", + "id": "01M1X0RK8AESY1WBBJRZDHTFH1", + "kind": "memory", + "score": 0.980535328388214, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1452.3334000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 858, + "mcp_result_bytes": 939, + "wire_bytes": 976, + "reported_used_tokens": 939, + "working_set_bytes": 1000046592, + "peak_working_set_bytes": 1000964096 + }, + { + "query": "git reflog to rescue accidentally deleted branch", + "ranked": [ + "git-reflog-rescue" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FFE9765XZEN4SP9VXASC", + "id": "01M1X0RMNDTY1H6XBC81MB8Y46", + "kind": "memory", + "score": 0.9931837916374208, + "summary": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone \u2014 they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only \u2014 remote reflog is not accessible via normal git commands." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1343.5231999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 762, + "mcp_result_bytes": 843, + "wire_bytes": 880, + "reported_used_tokens": 843, + "working_set_bytes": 1000046592, + "peak_working_set_bytes": 1000964096 + }, + { + "query": "git submodule --remote advances the pinned SHA unexpectedly", + "ranked": [ + "git-submodule-pinning" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FFD1SDFGRNM9A10G981G", + "id": "01M1X0RNZK0WFNB008KZG3T678", + "kind": "memory", + "score": 0.9999607801437378, + "summary": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip \u2014 this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1582.1574, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 752, + "mcp_result_bytes": 833, + "wire_bytes": 870, + "reported_used_tokens": 833, + "working_set_bytes": 1000239104, + "peak_working_set_bytes": 1001144320 + }, + { + "query": "axum SSE streaming drops the last event when client disconnects", + "ranked": [ + "http-streaming-bodies" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FFYQSRFZK4GH66S85GM2", + "id": "01M1X0RQHF1PCNMX86Q5ECBBCJ", + "kind": "memory", + "score": 0.7960996031761169, + "summary": "project:fact - [2026-09-07] [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding \u2014 a chunk may split across frame boundaries." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1805.7984999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 859, + "mcp_result_bytes": 940, + "wire_bytes": 977, + "reported_used_tokens": 940, + "working_set_bytes": 1000239104, + "peak_working_set_bytes": 1001152512 + }, + { + "query": "how do I detect that I am running inside a git worktree vs the main checkout?", + "ranked": [ + "git-worktree-brain-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FF865VP00ZZP7GC92453", + "id": "01M1X0RS9QC56S0C77EVPSCZPE", + "kind": "memory", + "score": 0.9114787578582764, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root \u2014 if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1307.8798, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 881, + "mcp_result_bytes": 962, + "wire_bytes": 999, + "reported_used_tokens": 962, + "working_set_bytes": 1000239104, + "peak_working_set_bytes": 1001156608 + }, + { + "query": "ONNX Runtime intra-op threads causing CPU contention during parallel bench", + "ranked": [ + "onnx-ort-threading" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0FF6ZQCCMQCC7N9WCWPFV", + "id": "01M1X0RTJKB0DGF5Q1HR4WPYAP", + "kind": "memory", + "score": 0.9999747276306152, + "summary": "project:fact - [tags: onnx ort thread-pool parallelism cpu] ORT (ONNX Runtime) creates its own inter-op and intra-op thread pools. In a multi-process bench setup, each child inherits these pools and they compete for CPU cores. Set `SessionOptionsBuilder::with_intra_threads(1).with_inter_threads(1)` if you're running many parallel bench processes \u2014 this sacrifices per-inference throughput for lower contention." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1228.2841, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 789, + "mcp_result_bytes": 870, + "wire_bytes": 907, + "reported_used_tokens": 870, + "working_set_bytes": 1000243200, + "peak_working_set_bytes": 1001156608 + }, + { + "query": "what is the right way to supply AWS session token alongside access key and secret?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1538.3572000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 1000243200, + "peak_working_set_bytes": 1001156608 + } + ], + "id": "existing-development-100", + "dimension": "retrieval", + "tier": "hard", + "score": 0.8293650793650794, + "skipped": false, + "detail": "positive-recall@4=0.84 mrr=0.86 stale-hit=n/a resolution=n/a false-injection=0.385 (n=13) positive-n=197 negative-n=13 (210 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 0.8293650793650794, + 1 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 0.8293650793650794, + "n": 1, + "ci95": null + } + }, + "overall_index": 0.8293650793650794, + "scenario_weighted_index": 0.8293650793650794 +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-retrieval/development/3-baseline.json b/docs/audits/2026-09-07-retrieval/development/3-baseline.json new file mode 100644 index 0000000..71b4e56 --- /dev/null +++ b/docs/audits/2026-09-07-retrieval/development/3-baseline.json @@ -0,0 +1,6811 @@ +{ + "generated_at": "2026-09-07T04:13:59.9719006Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\tmp-tests\\brainbench-development-100.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "test_env_lock inside with_user_brain_disabled deadlock", + "ranked": [ + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZEHVJGD07YGEDZ14AVMV", + "id": "01M1X0ZKMTE27TGPC2XRV55CGN", + "kind": "memory", + "score": 0.9999488592147828, + "summary": "project:fact - [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure \u2014 `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1188.9243999999999, + "first_query": true, + "server_startup_ms": 72.7226, + "model_text_bytes": 796, + "mcp_result_bytes": 877, + "wire_bytes": 912, + "reported_used_tokens": 877, + "working_set_bytes": 227213312, + "peak_working_set_bytes": 248188928 + }, + { + "query": "why does my test hang after calling with_user_brain_disabled when I also lock test_env_lock?", + "ranked": [ + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZEHVJGD07YGEDZ14AVMV", + "id": "01M1X0ZMFR8QCQEKX1J0GTPY84", + "kind": "memory", + "score": 0.9990190267562866, + "summary": "project:fact - [2026-09-07] [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure \u2014 `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 804.0882, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 808, + "mcp_result_bytes": 889, + "wire_bytes": 924, + "reported_used_tokens": 889, + "working_set_bytes": 229257216, + "peak_working_set_bytes": 248188928 + }, + { + "query": "ingest_repo_at_root brain_root files_root kimetsu remote", + "ranked": [ + "remote-ingest-split-roots", + "kimetsu-write-tools-gate", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZEJZP4HRFMXFVX53ZJ2M", + "id": "01M1X0ZN9E6F92KV95VXNQEEQF", + "kind": "memory", + "score": 0.999886393547058, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1X0ZJB78MBB69JA7ZTYBJNG", + "id": "01M1X0ZN9FKG1JG2Q1DBAWCAEV", + "kind": "memory", + "score": 0.8439717888832092, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level \u2014 disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1X0ZEN209JTV53G9N189X79", + "id": "01M1X0ZN9EGYFRP75P6Q7MKXAB", + "kind": "memory", + "score": 0.8363722562789917, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 925.6616, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2726, + "mcp_result_bytes": 2891, + "wire_bytes": 2926, + "reported_used_tokens": 2891, + "working_set_bytes": 252280832, + "peak_working_set_bytes": 253190144 + }, + { + "query": "why does the remote server index the wrong directory when I run kimetsu brain ingest?", + "ranked": [ + "remote-ingest-split-roots", + "onnx-dim-mismatch" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZEJZP4HRFMXFVX53ZJ2M", + "id": "01M1X0ZP5Z9Z6QK65H2654CM95", + "kind": "memory", + "score": 0.9836117625236512, + "summary": "project:fact - [2026-09-07] [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1X0ZGBQ2VZT14120VKW0MYX", + "id": "01M1X0ZP5ZYA5SVAVJYQ70TC02", + "kind": "memory", + "score": 0.3657674789428711, + "summary": "project:fact - [2026-09-07] [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results \u2014 the ANN index shape mismatch isn't always caught at runtime." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 907.3981, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1800, + "mcp_result_bytes": 1899, + "wire_bytes": 1934, + "reported_used_tokens": 1899, + "working_set_bytes": 257802240, + "peak_working_set_bytes": 258723840 + }, + { + "query": "kimetsu plugin install --remote mcp.json authorization bearer token", + "ranked": [ + "remote-mcp-host-wiring", + "mcp-stdout-protocol" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZEN209JTV53G9N189X79", + "id": "01M1X0ZQ27MAZC1TH83J2VNK8S", + "kind": "memory", + "score": 0.999605119228363, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + }, + { + "expansion_handle": "memory:01M1X0ZHK46P88A6EV49JZ72Z3", + "id": "01M1X0ZQ28W8HM61SSJFPV386E", + "kind": "memory", + "score": 0.3375842869281769, + "summary": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 840.1763, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1472, + "mcp_result_bytes": 1619, + "wire_bytes": 1654, + "reported_used_tokens": 1619, + "working_set_bytes": 258404352, + "peak_working_set_bytes": 259330048 + }, + { + "query": "how do I wire a remote kimetsu brain into Claude Code without storing the token in the config file?", + "ranked": [ + "remote-mcp-host-wiring", + "mcp-tool-naming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZEN209JTV53G9N189X79", + "id": "01M1X0ZQWHH3MAFN828CSSPV88", + "kind": "memory", + "score": 0.9963359832763672, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + }, + { + "expansion_handle": "memory:01M1X0ZHQ906QPDZRG8VM07EPH", + "id": "01M1X0ZQWHJVW9T6R8E99P2N0Z", + "kind": "memory", + "score": 0.831425666809082, + "summary": "project:fact - [tags: mcp tool naming convention kimetsu] MCP tool names must be valid identifiers for all host agents. Claude Code restricts tool names to `[a-zA-Z0-9_-]` and max 64 chars. Use `snake_case` (kimetsu_brain_context, kimetsu_brain_record) \u2014 hyphen is technically allowed but some hosts reject it." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 838.6754, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1454, + "mcp_result_bytes": 1601, + "wire_bytes": 1636, + "reported_used_tokens": 1601, + "working_set_bytes": 258547712, + "peak_working_set_bytes": 259469312 + }, + { + "query": "cargo feature unification kimetsu-brain embeddings fastembed test failure", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-profile-override", + "clap-version-build-flavor" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZEQ4Z80DHHAR2MNQZ1A7", + "id": "01M1X0ZRPPQEBS6CS5HN4EW9W1", + "kind": "memory", + "score": 0.9996790885925292, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X0ZFTEX7E4JYA35AJM4JBQ", + "id": "01M1X0ZRPP9T27GGWZMWDJKGC6", + "kind": "memory", + "score": 0.9923595786094666, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1X0ZF2DEQAXNVK748P8ZXKS", + "id": "01M1X0ZRPPYHYD8TQWRFSTPVDJ", + "kind": "memory", + "score": 0.585203230381012, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 824.5675, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2387, + "mcp_result_bytes": 2524, + "wire_bytes": 2559, + "reported_used_tokens": 2524, + "working_set_bytes": 259891200, + "peak_working_set_bytes": 260812800 + }, + { + "query": "my integration tests pass in isolation but break when I run cargo test --workspace \u2014 embedder changed?", + "ranked": [ + "cargo-feature-unification-embeddings", + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZEQ4Z80DHHAR2MNQZ1A7", + "id": "01M1X0ZSGNSARP7V3BAMPZZHC4", + "kind": "memory", + "score": 0.9943140745162964, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X0ZF1GHG8GMEWTPEF0B3GJ", + "id": "01M1X0ZSGN9Y9FRATQ7VKHTY5E", + "kind": "memory", + "score": 0.31398114562034607, + "summary": "project:fact - [2026-09-07] [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 884.2512, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1714, + "mcp_result_bytes": 1817, + "wire_bytes": 1852, + "reported_used_tokens": 1817, + "working_set_bytes": 260665344, + "peak_working_set_bytes": 261586944 + }, + { + "query": "build_anthropic_body bedrock-2023-05-31 InvokeModel blocking reqwest", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZERMERMXX9NA2KTGPZDG", + "id": "01M1X0ZTC6CD9M9QC1Q3HY5BDT", + "kind": "memory", + "score": 0.9973788261413574, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X0ZEYPBW1N1TY0KVBGW2GN", + "id": "01M1X0ZTC7E8WRB0M7F9DMRQKD", + "kind": "memory", + "score": 0.6916899085044861, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 694.3123, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2193, + "mcp_result_bytes": 2320, + "wire_bytes": 2356, + "reported_used_tokens": 2320, + "working_set_bytes": 260911104, + "peak_working_set_bytes": 261824512 + }, + { + "query": "how do I add AWS Bedrock as a model provider in Kimetsu without pulling in the aws-sdk?", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-region-resolution", + "aws-credentials-chain", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZERMERMXX9NA2KTGPZDG", + "id": "01M1X0ZV2WKZSZR15JA520TCEQ", + "kind": "memory", + "score": 0.9998898506164552, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X0ZHTNDF0DYAMM67R34CKC", + "id": "01M1X0ZV2WNKP13RP9S6AKEJVX", + "kind": "memory", + "score": 0.995676338672638, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X0ZHSCPRWJGM9PSEB6ZM8G", + "id": "01M1X0ZV2WK867Z62MBS7G8CF4", + "kind": "memory", + "score": 0.987064242362976, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + }, + { + "expansion_handle": "memory:01M1X0ZEYPBW1N1TY0KVBGW2GN", + "id": "01M1X0ZV2W9MSSD4QSF30D19XD", + "kind": "memory", + "score": 0.9493880867958068, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 873.0033999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3455, + "mcp_result_bytes": 3618, + "wire_bytes": 3654, + "reported_used_tokens": 3618, + "working_set_bytes": 269160448, + "peak_working_set_bytes": 270077952 + }, + { + "query": "BridgeTarget enum seams plugin_install_inner plugin_status_inner resolve_setup_hosts", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZETMGXDNJHWBWG9ZZ2Y5", + "id": "01M1X0ZVY69SY6Z7FAV65GWXBS", + "kind": "memory", + "score": 0.9997583031654358, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 773.0568, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1060, + "mcp_result_bytes": 1141, + "wire_bytes": 1177, + "reported_used_tokens": 1141, + "working_set_bytes": 279146496, + "peak_working_set_bytes": 280059904 + }, + { + "query": "I added a new host to the bridge enum but cargo gives me compile errors in five different match arms \u2014 what did I miss?", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZETMGXDNJHWBWG9ZZ2Y5", + "id": "01M1X0ZWQS6PX588QNK1XQ55AQ", + "kind": "memory", + "score": 0.9977060556411744, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 992.059, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1059, + "mcp_result_bytes": 1140, + "wire_bytes": 1176, + "reported_used_tokens": 1140, + "working_set_bytes": 279965696, + "peak_working_set_bytes": 280879104 + }, + { + "query": "Pi extension factory defineExtension agent_end session_shutdown kimetsu.ts", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZEVTX6BZPYY73M2KS091", + "id": "01M1X0ZXNKX6C7EKAYRBZ1BAKH", + "kind": "memory", + "score": 0.9990354776382446, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 996.638, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 804, + "mcp_result_bytes": 893, + "wire_bytes": 929, + "reported_used_tokens": 893, + "working_set_bytes": 280354816, + "peak_working_set_bytes": 281268224 + }, + { + "query": "how does Pi (earendil-works/pi) load plugins and what lifecycle hooks does it expose?", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZEVTX6BZPYY73M2KS091", + "id": "01M1X0ZYN53H7ZTW4XTD8S20WV", + "kind": "memory", + "score": 0.9934834837913512, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 981.6379, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 803, + "mcp_result_bytes": 892, + "wire_bytes": 928, + "reported_used_tokens": 892, + "working_set_bytes": 281120768, + "peak_working_set_bytes": 282030080 + }, + { + "query": "aws-sigv4 SigningParams apply_to_request_http1x reqwest sign-http", + "ranked": [ + "aws-sigv4-bedrock-blocking", + "aws-presigned-urls", + "bedrock-kimetsu-provider", + "aws-credentials-chain" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZEYPBW1N1TY0KVBGW2GN", + "id": "01M1X0ZZJNXB33MBGMAWDND4RA", + "kind": "memory", + "score": 0.9995608925819396, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1X0ZHWS1G2E6ER6NHTRVEWK", + "id": "01M1X0ZZJNAVK8V2G20TRJWCD1", + "kind": "memory", + "score": 0.984916627407074, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + }, + { + "expansion_handle": "memory:01M1X0ZERMERMXX9NA2KTGPZDG", + "id": "01M1X0ZZJNCDH7CP7KHRW1H6YK", + "kind": "memory", + "score": 0.983895778656006, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X0ZHSCPRWJGM9PSEB6ZM8G", + "id": "01M1X0ZZJNKK8JQ876C7DBVKC1", + "kind": "memory", + "score": 0.8592692017555237, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 710.6721, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3507, + "mcp_result_bytes": 3670, + "wire_bytes": 3706, + "reported_used_tokens": 3670, + "working_set_bytes": 281337856, + "peak_working_set_bytes": 282238976 + }, + { + "query": "how do I sign a Bedrock InvokeModel request with aws-sigv4 in blocking Rust?", + "ranked": [ + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider", + "aws-region-resolution", + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZEYPBW1N1TY0KVBGW2GN", + "id": "01M1X1008NWN020YRS3TSWTHAA", + "kind": "memory", + "score": 0.9998323917388916, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1X0ZERMERMXX9NA2KTGPZDG", + "id": "01M1X1008N7DAFDJ1QH1C7D4WR", + "kind": "memory", + "score": 0.9970844388008118, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X0ZHTNDF0DYAMM67R34CKC", + "id": "01M1X1008NEB0Q4HTJP2A3CD2K", + "kind": "memory", + "score": 0.9468621611595154, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X0ZHWS1G2E6ER6NHTRVEWK", + "id": "01M1X1008N11THXJB5W0WCCPS2", + "kind": "memory", + "score": 0.9210098385810852, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 846.6475999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3434, + "mcp_result_bytes": 3597, + "wire_bytes": 3633, + "reported_used_tokens": 3597, + "working_set_bytes": 281526272, + "peak_working_set_bytes": 282443776 + }, + { + "query": "KIMETSU_RUNS_GC env opt-out TraceWriter create gc_old_runs caller", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZF0MADM4ZVAS7Z7SYTQJ", + "id": "01M1X10134HVMS59H63HPQE5R8", + "kind": "memory", + "score": 0.999936580657959, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 802.7986999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 761, + "mcp_result_bytes": 842, + "wire_bytes": 878, + "reported_used_tokens": 842, + "working_set_bytes": 281825280, + "peak_working_set_bytes": 282738688 + }, + { + "query": "where should I put the KIMETSU_RUNS_GC=0 guard \u2014 inside the GC function or at the call site?", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZF0MADM4ZVAS7Z7SYTQJ", + "id": "01M1X101W8WM09NC05P9JP9J6M", + "kind": "memory", + "score": 0.9971211552619934, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 904.0203, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 762, + "mcp_result_bytes": 843, + "wire_bytes": 879, + "reported_used_tokens": 843, + "working_set_bytes": 282345472, + "peak_working_set_bytes": 283267072 + }, + { + "query": "git_init_boundary ProjectPaths::discover temp dir user brain isolation", + "ranked": [ + "init-project-git-boundary", + "git-worktree-brain-isolation", + "testing-temp-dirs-ci", + "kimetsu-memory-scopes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZF1GHG8GMEWTPEF0B3GJ", + "id": "01M1X102SP39A9WA8V4033WV2C", + "kind": "memory", + "score": 0.9997712969779968, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + }, + { + "expansion_handle": "memory:01M1X0ZGGKZ262G85SY6B3T5B1", + "id": "01M1X102SPT5WCZP8QTCJC0S0V", + "kind": "memory", + "score": 0.9962491393089294, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root \u2014 if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + }, + { + "expansion_handle": "memory:01M1X0ZHB5SS7Y7DPND7PN1DX1", + "id": "01M1X102SP5H19FS2QC6S52R6E", + "kind": "memory", + "score": 0.9682154655456544, + "summary": "project:fact - [tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure." + }, + { + "expansion_handle": "memory:01M1X0ZJ838F1BR8VTV7ERMJB7", + "id": "01M1X102SP0QKRAJBVVE3A4DKK", + "kind": "memory", + "score": 0.3057229816913605, + "summary": "project:fact - [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available \u2014 if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 828.2343, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2580, + "mcp_result_bytes": 2715, + "wire_bytes": 2751, + "reported_used_tokens": 2715, + "working_set_bytes": 282402816, + "peak_working_set_bytes": 283316224 + }, + { + "query": "my test calls init_project but it writes to the real ~/.kimetsu instead of the temp folder \u2014 why?", + "ranked": [ + "init-project-git-boundary", + "cargo-feature-unification-embeddings", + "testing-fixture-drift", + "tokio-runtime-in-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZF1GHG8GMEWTPEF0B3GJ", + "id": "01M1X103JJMKN9A426MT6ERZQ2", + "kind": "memory", + "score": 0.9995088577270508, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + }, + { + "expansion_handle": "memory:01M1X0ZEQ4Z80DHHAR2MNQZ1A7", + "id": "01M1X103JJHF1XJ3C75VRW1N68", + "kind": "memory", + "score": 0.7287850975990295, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X0ZHJ7V8BRWKTYR18X6ACN", + "id": "01M1X103JJ08F1RPJXH7RM1BBC", + "kind": "memory", + "score": 0.6596062183380127, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + }, + { + "expansion_handle": "memory:01M1X0ZGQ908QPA9R8BQ4WTCEK", + "id": "01M1X103JJBX889PMWSRBDEKES", + "kind": "memory", + "score": 0.3297702968120575, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 950.3475, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2833, + "mcp_result_bytes": 2980, + "wire_bytes": 3016, + "reported_used_tokens": 2980, + "working_set_bytes": 282550272, + "peak_working_set_bytes": 283463680 + }, + { + "query": "clap command version KIMETSU_VERSION_DISPLAY cfg feature embeddings", + "ranked": [ + "clap-version-build-flavor", + "cargo-feature-unification-embeddings" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZF2DEQAXNVK748P8ZXKS", + "id": "01M1X104H00DWA6BAA7W2E3TFW", + "kind": "memory", + "score": 0.9996613264083862, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + }, + { + "expansion_handle": "memory:01M1X0ZEQ4Z80DHHAR2MNQZ1A7", + "id": "01M1X104H0CQHDCZZ8GF91G5T6", + "kind": "memory", + "score": 0.3973360061645508, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 747.5482, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1922, + "mcp_result_bytes": 2041, + "wire_bytes": 2077, + "reported_used_tokens": 2041, + "working_set_bytes": 282882048, + "peak_working_set_bytes": 283787264 + }, + { + "query": "how do I show the build flavor (lean vs embeddings) in the kimetsu --version output?", + "ranked": [ + "clap-version-build-flavor", + "cargo-feature-unification-embeddings", + "onnx-quantization-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZF2DEQAXNVK748P8ZXKS", + "id": "01M1X10597VP43PGV6CD0THSWK", + "kind": "memory", + "score": 0.9978312849998474, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + }, + { + "expansion_handle": "memory:01M1X0ZEQ4Z80DHHAR2MNQZ1A7", + "id": "01M1X105972P1T58CV820JTQKH", + "kind": "memory", + "score": 0.8926984667778015, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X0ZG7WCFGH0CD0BM3MAQWA", + "id": "01M1X10597WREZTRJTW493TH4Y", + "kind": "memory", + "score": 0.8877003192901611, + "summary": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals \u2014 cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1010.0047000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2672, + "mcp_result_bytes": 2809, + "wire_bytes": 2845, + "reported_used_tokens": 2809, + "working_set_bytes": 283205632, + "peak_working_set_bytes": 284123136 + }, + { + "query": "Harbor pyiceberg os.getcwd stale WSL2 DrvFs worker-result subprocess re-exec", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZF3QZ2VSDH104Y837HV1", + "id": "01M1X1068B4R4W16HRNPRB8103", + "kind": "memory", + "score": 0.9998155236244202, + "summary": "project:fact - [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1026.2003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1026, + "mcp_result_bytes": 1107, + "wire_bytes": 1143, + "reported_used_tokens": 1107, + "working_set_bytes": 283303936, + "peak_working_set_bytes": 284213248 + }, + { + "query": "why does my kbench sweep crash after the first trial with 'result.json missing' on WSL2?", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZF3QZ2VSDH104Y837HV1", + "id": "01M1X107908SQS4MV4TGNE35E5", + "kind": "memory", + "score": 0.998451828956604, + "summary": "project:fact - [2026-09-07] [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1107.2726, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1038, + "mcp_result_bytes": 1119, + "wire_bytes": 1155, + "reported_used_tokens": 1119, + "working_set_bytes": 283725824, + "peak_working_set_bytes": 284647424 + }, + { + "query": "rusqlite VACUUM transaction WAL checkpoint wal_checkpoint TRUNCATE", + "ranked": [ + "sqlite-vacuum-wal-checkpoint", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZF537M7YEPKETRN13BYG", + "id": "01M1X108ASBFWZKF6B40KY6T9J", + "kind": "memory", + "score": 0.9996871948242188, + "summary": "project:fact - [tags: rust sqlite vacuum rusqlite windows] When implementing SQLite VACUUM in rusqlite: VACUUM cannot run inside a transaction. rusqlite's Connection does not hold an implicit transaction, so `conn.execute_batch(\"VACUUM;\")` works directly. After VACUUM, run `PRAGMA wal_checkpoint(TRUNCATE);` before measuring file size \u2014 on Windows the WAL file can hold significant space that isn't reflected in the main db file until the checkpoint runs." + }, + { + "expansion_handle": "memory:01M1X0ZFCB0HTB40H3S45W1XS7", + "id": "01M1X108AS1NCVWY1VMF5SBW8Y", + "kind": "memory", + "score": 0.5274003744125366, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 772.6567, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1507, + "mcp_result_bytes": 1610, + "wire_bytes": 1646, + "reported_used_tokens": 1610, + "working_set_bytes": 283725824, + "peak_working_set_bytes": 284647424 + }, + { + "query": "my SQLite VACUUM reports the file shrank but the disk usage stayed the same \u2014 Windows WAL?", + "ranked": [ + "sqlite-vacuum-wal-checkpoint", + "sqlite-wal-network-drive" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZF537M7YEPKETRN13BYG", + "id": "01M1X10936KCR31JSZ090YX9ZJ", + "kind": "memory", + "score": 0.9155893921852112, + "summary": "project:fact - [tags: rust sqlite vacuum rusqlite windows] When implementing SQLite VACUUM in rusqlite: VACUUM cannot run inside a transaction. rusqlite's Connection does not hold an implicit transaction, so `conn.execute_batch(\"VACUUM;\")` works directly. After VACUUM, run `PRAGMA wal_checkpoint(TRUNCATE);` before measuring file size \u2014 on Windows the WAL file can hold significant space that isn't reflected in the main db file until the checkpoint runs." + }, + { + "expansion_handle": "memory:01M1X0ZFF44WCC49W67QG1BAQR", + "id": "01M1X109363G08KR9HSQC8J1DX", + "kind": "memory", + "score": 0.902395486831665, + "summary": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 997.7131999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1357, + "mcp_result_bytes": 1460, + "wire_bytes": 1496, + "reported_used_tokens": 1460, + "working_set_bytes": 283742208, + "peak_working_set_bytes": 284659712 + }, + { + "query": "add_memory import dedup seen_ids snapshot pre-existing active memory IDs", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZF5XE4S8M5NK3DAHN6PE", + "id": "01M1X10AGK6Y2TRWTYX84X57EP", + "kind": "memory", + "score": 0.9999133348464966, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount \u2014 both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1311.3305, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 966, + "mcp_result_bytes": 1047, + "wire_bytes": 1083, + "reported_used_tokens": 1047, + "working_set_bytes": 283832320, + "peak_working_set_bytes": 284741632 + }, + { + "query": "brain import re-imports the same JSON file but the deduplication counter is wrong \u2014 why?", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZF5XE4S8M5NK3DAHN6PE", + "id": "01M1X10BB2N5NAQWBEG15DHP32", + "kind": "memory", + "score": 0.9254016876220704, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount \u2014 both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 879.3879999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 965, + "mcp_result_bytes": 1046, + "wire_bytes": 1082, + "reported_used_tokens": 1046, + "working_set_bytes": 284065792, + "peak_working_set_bytes": 284983296 + }, + { + "query": "toml::from_str Value parse document unexpected content str.parse", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZF72C210NDRKM00YJ4W2", + "id": "01M1X10C5TBBCF988ZASEQEA6S", + "kind": "memory", + "score": 0.9991866946220398, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 750.1446000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 734, + "mcp_result_bytes": 815, + "wire_bytes": 851, + "reported_used_tokens": 815, + "working_set_bytes": 284106752, + "peak_working_set_bytes": 285020160 + }, + { + "query": "how do I parse a TOML configuration file into a toml::Value in toml 0.9?", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZF72C210NDRKM00YJ4W2", + "id": "01M1X10CXV5RAWY1D1W5VZGK22", + "kind": "memory", + "score": 0.9992641806602478, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 894.7981000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 733, + "mcp_result_bytes": 814, + "wire_bytes": 850, + "reported_used_tokens": 814, + "working_set_bytes": 284377088, + "peak_working_set_bytes": 285290496 + }, + { + "query": "CIM CreationDate DMTF WMI ps etimes started_at assess_mcp_skew", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZF8E3HTSE36QFFFJ0YBT", + "id": "01M1X10DSC0CZDAV3VF96K0FG3", + "kind": "memory", + "score": 0.9957948923110962, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 703.1642, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 924, + "mcp_result_bytes": 1013, + "wire_bytes": 1049, + "reported_used_tokens": 1013, + "working_set_bytes": 284377088, + "peak_working_set_bytes": 285290496 + }, + { + "query": "how do I read a process start time on both Windows and Linux in pure Rust?", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZF8E3HTSE36QFFFJ0YBT", + "id": "01M1X10EFXPS0DG9ZGM00SFKT1", + "kind": "memory", + "score": 0.99687659740448, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 908.6351000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 921, + "mcp_result_bytes": 1010, + "wire_bytes": 1046, + "reported_used_tokens": 1010, + "working_set_bytes": 284430336, + "peak_working_set_bytes": 285356032 + }, + { + "query": "processes_locking_target decide_preflight_action BufRead Write update.rs", + "ranked": [ + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZFA17MW7EDQGRKH93EW9", + "id": "01M1X10FC0VH9VF87BF7WH9W4S", + "kind": "memory", + "score": 0.9995336532592772, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 761.0909, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1133, + "mcp_result_bytes": 1214, + "wire_bytes": 1250, + "reported_used_tokens": 1214, + "working_set_bytes": 284434432, + "peak_working_set_bytes": 285356032 + }, + { + "query": "how should I reuse the existing process enumerator in the update preflight check to avoid a second PowerShell query?", + "ranked": [ + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZFA17MW7EDQGRKH93EW9", + "id": "01M1X10G3Y847V5Q5DSN1396XX", + "kind": "memory", + "score": 0.9973384737968444, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 948.2203000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1132, + "mcp_result_bytes": 1213, + "wire_bytes": 1249, + "reported_used_tokens": 1213, + "working_set_bytes": 284483584, + "peak_working_set_bytes": 285401088 + }, + { + "query": "cfg_attr windows allow dead_code parse_unix_ps cross-platform tests", + "ranked": [ + "cfg-cross-platform-dead-code", + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZFBCQ5Q2DBF8XZ334WTW", + "id": "01M1X10H1CTYSMXH4BQ3GA5788", + "kind": "memory", + "score": 0.9999476671218872, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + }, + { + "expansion_handle": "memory:01M1X0ZF8E3HTSE36QFFFJ0YBT", + "id": "01M1X10H1CZB8BC3RVFKHH4478", + "kind": "memory", + "score": 0.9764312505722046, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 726.2875, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1518, + "mcp_result_bytes": 1625, + "wire_bytes": 1661, + "reported_used_tokens": 1625, + "working_set_bytes": 284606464, + "peak_working_set_bytes": 285519872 + }, + { + "query": "how do I keep a function that is only called on Unix from triggering dead_code warnings on Windows?", + "ranked": [ + "cfg-cross-platform-dead-code" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZFBCQ5Q2DBF8XZ334WTW", + "id": "01M1X10HQVHWAABXNXGKAHANC0", + "kind": "memory", + "score": 0.9988092184066772, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 881.264, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 939, + "reported_used_tokens": 903, + "working_set_bytes": 284794880, + "peak_working_set_bytes": 285708288 + }, + { + "query": "deadlocking a Rust mutex in integration tests", + "ranked": [ + "mutex-deadlock-user-brain-disabled", + "testing-serial-vs-parallel", + "kimetsu-query-stemming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZEHVJGD07YGEDZ14AVMV", + "id": "01M1X10JM3XNF4ZFKDAQ67JYX4", + "kind": "memory", + "score": 0.9997490048408508, + "summary": "project:fact - [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure \u2014 `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + }, + { + "expansion_handle": "memory:01M1X0ZHEBS8TQMPSTT182J1MD", + "id": "01M1X10JM3VCTY847Q39VRE3B6", + "kind": "memory", + "score": 0.9057517647743224, + "summary": "project:fact - [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`)." + }, + { + "expansion_handle": "memory:01M1X0ZJF4CV4GMMY9RCT11XHB", + "id": "01M1X10JM3RC32F0DFAD8076C0", + "kind": "memory", + "score": 0.4889622032642365, + "summary": "project:fact - [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 875.3152, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1930, + "mcp_result_bytes": 2063, + "wire_bytes": 2099, + "reported_used_tokens": 2063, + "working_set_bytes": 284880896, + "peak_working_set_bytes": 285794304 + }, + { + "query": "benchmarking retrieval quality across embedders", + "ranked": [ + "kimetsu-bench-remote-embedder-singleton", + "onnx-quantization-drift", + "cargo-feature-unification-embeddings" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZJH6EYMQ5H2CB23ZRSY2", + "id": "01M1X10KFH9MWJ447KBJPZ7EYK", + "kind": "memory", + "score": 0.988014280796051, + "summary": "project:fact - [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval." + }, + { + "expansion_handle": "memory:01M1X0ZG7WCFGH0CD0BM3MAQWA", + "id": "01M1X10KFHC2DTWMY51CWBRJQ0", + "kind": "memory", + "score": 0.985597550868988, + "summary": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals \u2014 cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + }, + { + "expansion_handle": "memory:01M1X0ZEQ4Z80DHHAR2MNQZ1A7", + "id": "01M1X10KFHD9QQ4J81PQ71M7W0", + "kind": "memory", + "score": 0.5341982841491699, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 761.8729, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2537, + "mcp_result_bytes": 2658, + "wire_bytes": 2694, + "reported_used_tokens": 2658, + "working_set_bytes": 284889088, + "peak_working_set_bytes": 285798400 + }, + { + "query": "process memory working set RSS peak measurement Windows", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 919.4333, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 284999680, + "peak_working_set_bytes": 285892608 + }, + { + "query": "cloning a git repository server-side into a managed checkout", + "ranked": [ + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZEJZP4HRFMXFVX53ZJ2M", + "id": "01M1X10N3K1X3SFQPDTNT19P6Y", + "kind": "memory", + "score": 0.9466677904129028, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 763.6494, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1261, + "mcp_result_bytes": 1342, + "wire_bytes": 1378, + "reported_used_tokens": 1342, + "working_set_bytes": 285007872, + "peak_working_set_bytes": 285913088 + }, + { + "query": "SigV4 signing HTTP requests in Rust", + "ranked": [ + "aws-presigned-urls", + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZHWS1G2E6ER6NHTRVEWK", + "id": "01M1X10NVMKETMKRH4EHT163TG", + "kind": "memory", + "score": 0.9992632269859314, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + }, + { + "expansion_handle": "memory:01M1X0ZEYPBW1N1TY0KVBGW2GN", + "id": "01M1X10NVMWF3K5VBGJ85PT4GM", + "kind": "memory", + "score": 0.9991399049758912, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1X0ZERMERMXX9NA2KTGPZDG", + "id": "01M1X10NVMSS7W63M7FERQGRM4", + "kind": "memory", + "score": 0.9803794622421264, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 0.5, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 857.0997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2840, + "mcp_result_bytes": 2985, + "wire_bytes": 3021, + "reported_used_tokens": 2985, + "working_set_bytes": 285016064, + "peak_working_set_bytes": 285913088 + }, + { + "query": "cargo test --workspace feature flag changes broke my unit tests", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-dev-dep-leak", + "ci-flaky-quarantine" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZEQ4Z80DHHAR2MNQZ1A7", + "id": "01M1X10PQ08KS5TCSBWGVESQRN", + "kind": "memory", + "score": 0.997899889945984, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X0ZFQJ2YCP0MRA63WFP9S4", + "id": "01M1X10PQ09WVW0C2FHBRPCBGD", + "kind": "memory", + "score": 0.9901249408721924, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + }, + { + "expansion_handle": "memory:01M1X0ZJ55N7K4YMPJ81M5YN0V", + "id": "01M1X10PQ0DSEB3XYVPAHHJR4Y", + "kind": "memory", + "score": 0.835382342338562, + "summary": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal \u2014 a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 782.2111000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2383, + "mcp_result_bytes": 2504, + "wire_bytes": 2540, + "reported_used_tokens": 2504, + "working_set_bytes": 285077504, + "peak_working_set_bytes": 285990912 + }, + { + "query": "how do I make pasta carbonara?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 819.5407, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 285450240, + "peak_working_set_bytes": 286363648 + }, + { + "query": "what is the offside rule in football?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 975.1385, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 285843456, + "peak_working_set_bytes": 286752768 + }, + { + "query": "best way to train for a half marathon", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 978.6643, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 285921280, + "peak_working_set_bytes": 286842880 + }, + { + "query": "my test passes when I run it alone but fails under cargo test --workspace", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZEQ4Z80DHHAR2MNQZ1A7", + "id": "01M1X10T5QNF3YP64A9PNGCB3S", + "kind": "memory", + "score": 0.9907942414283752, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X0ZFQJ2YCP0MRA63WFP9S4", + "id": "01M1X10T5QSXAAYFAFKX0ACW17", + "kind": "memory", + "score": 0.986136794090271, + "summary": "project:fact - [2026-09-07] [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 938.2085999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1863, + "mcp_result_bytes": 1966, + "wire_bytes": 2002, + "reported_used_tokens": 1966, + "working_set_bytes": 286380032, + "peak_working_set_bytes": 287297536 + }, + { + "query": "all the project tests started hanging forever after I added my new test", + "ranked": [ + "cargo-feature-unification-embeddings", + "tokio-runtime-in-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZEQ4Z80DHHAR2MNQZ1A7", + "id": "01M1X10V38WXJDGDNDXAQK9N79", + "kind": "memory", + "score": 0.774284839630127, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X0ZGQ908QPA9R8BQ4WTCEK", + "id": "01M1X10V38R5JG83ECK7R8PK9P", + "kind": "memory", + "score": 0.33030807971954346, + "summary": "project:fact - [2026-09-07] [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 891.1031, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1763, + "mcp_result_bytes": 1874, + "wire_bytes": 1910, + "reported_used_tokens": 1874, + "working_set_bytes": 286388224, + "peak_working_set_bytes": 287297536 + }, + { + "query": "my integration test silently wrote memories into my real home brain instead of the temp workspace", + "ranked": [ + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZF1GHG8GMEWTPEF0B3GJ", + "id": "01M1X10VZP3P2Q7QWW3456HYDM", + "kind": "memory", + "score": 0.9922831654548644, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 915.9888000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 780, + "mcp_result_bytes": 861, + "wire_bytes": 897, + "reported_used_tokens": 861, + "working_set_bytes": 286388224, + "peak_working_set_bytes": 287305728 + }, + { + "query": "where should the env-var opt-out check live for a cleanup feature triggered from a hot code path", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZF0MADM4ZVAS7Z7SYTQJ", + "id": "01M1X10WWR839C1VABPYFEJ9F6", + "kind": "memory", + "score": 0.9952055215835572, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1001.0201999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 761, + "mcp_result_bytes": 842, + "wire_bytes": 878, + "reported_used_tokens": 842, + "working_set_bytes": 286388224, + "peak_working_set_bytes": 287305728 + }, + { + "query": "the brain database file stays huge on Windows even after deleting most rows", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 953.3857999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 286388224, + "peak_working_set_bytes": 287309824 + }, + { + "query": "re-importing the same exported memories file counts them as new instead of deduplicated", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZF5XE4S8M5NK3DAHN6PE", + "id": "01M1X10YS14GEJGYB30XGPSBQE", + "kind": "memory", + "score": 0.9878425598144532, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount \u2014 both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 957.4993000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 965, + "mcp_result_bytes": 1046, + "wire_bytes": 1082, + "reported_used_tokens": 1046, + "working_set_bytes": 286396416, + "peak_working_set_bytes": 287313920 + }, + { + "query": "a helper function only called on Unix at runtime fails the dead-code lint on the Windows build", + "ranked": [ + "cfg-cross-platform-dead-code", + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZFBCQ5Q2DBF8XZ334WTW", + "id": "01M1X10ZQ67C0NH29X23GB8R7B", + "kind": "memory", + "score": 0.9971064925193788, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + }, + { + "expansion_handle": "memory:01M1X0ZFA17MW7EDQGRKH93EW9", + "id": "01M1X10ZQ6P4777Q1CVZVC06V5", + "kind": "memory", + "score": 0.427912950515747, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 910.5727, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1728, + "mcp_result_bytes": 1827, + "wire_bytes": 1863, + "reported_used_tokens": 1827, + "working_set_bytes": 286547968, + "peak_working_set_bytes": 287469568 + }, + { + "query": "the second Terminal-Bench trial always crashes even though the first one passes", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZF3QZ2VSDH104Y837HV1", + "id": "01M1X110K5W345078Z12A1A3VE", + "kind": "memory", + "score": 0.9963042736053468, + "summary": "project:fact - [2026-09-07] [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 924.9399000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1038, + "mcp_result_bytes": 1119, + "wire_bytes": 1155, + "reported_used_tokens": 1119, + "working_set_bytes": 286744576, + "peak_working_set_bytes": 287657984 + }, + { + "query": "how does doctor tell a running MCP server process is older than the kimetsu binary on disk", + "ranked": [ + "kimetsu-daemon-lifecycle", + "process-start-time-cross-platform", + "mcp-env-propagation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZJ67E5EZD48V1ZXX3EDF", + "id": "01M1X111GZF2JWDENG4VX6MAXP", + "kind": "memory", + "score": 0.9985345602035522, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1X0ZF8E3HTSE36QFFFJ0YBT", + "id": "01M1X111GZ8NMQ6GWH6D24654E", + "kind": "memory", + "score": 0.9438157677650452, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + }, + { + "expansion_handle": "memory:01M1X0ZHN8D90ZGFZWMSQQJ0A4", + "id": "01M1X111GZMQWZNF3FBE1Y8F04", + "kind": "memory", + "score": 0.33611738681793213, + "summary": "project:fact - [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment \u2014 changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 0.5, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 965.1074, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1936, + "mcp_result_bytes": 2061, + "wire_bytes": 2097, + "reported_used_tokens": 2061, + "working_set_bytes": 287043584, + "peak_working_set_bytes": 287961088 + }, + { + "query": "the self-update preflight needs the list of running kimetsu processes without re-running the OS query", + "ranked": [ + "windows-update-process-locking", + "kimetsu-daemon-lifecycle" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZFA17MW7EDQGRKH93EW9", + "id": "01M1X112E6Z4WH1X91H7ZBEQ3T", + "kind": "memory", + "score": 0.9972410202026368, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + }, + { + "expansion_handle": "memory:01M1X0ZJ67E5EZD48V1ZXX3EDF", + "id": "01M1X112E6RWWKKSF6MP2KE3P5", + "kind": "memory", + "score": 0.8902595043182373, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 877.0586999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1658, + "mcp_result_bytes": 1757, + "wire_bytes": 1793, + "reported_used_tokens": 1757, + "working_set_bytes": 287047680, + "peak_working_set_bytes": 287961088 + }, + { + "query": "parsing the WMI DMTF CreationDate timestamp into epoch seconds without extra crates", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZF8E3HTSE36QFFFJ0YBT", + "id": "01M1X1139G36R6YMNRDJ4NTC2Z", + "kind": "memory", + "score": 0.9258026480674744, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 924.9769, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 924, + "mcp_result_bytes": 1013, + "wire_bytes": 1049, + "reported_used_tokens": 1013, + "working_set_bytes": 287498240, + "peak_working_set_bytes": 288411648 + }, + { + "query": "calling Bedrock InvokeModel from blocking reqwest without the aws sdk", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking", + "aws-region-resolution", + "aws-retry-throttling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZERMERMXX9NA2KTGPZDG", + "id": "01M1X1146KXN2KZNRY98QDZW4D", + "kind": "memory", + "score": 0.9991798996925354, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X0ZEYPBW1N1TY0KVBGW2GN", + "id": "01M1X1146KGAYPVMGA4JZGVV2W", + "kind": "memory", + "score": 0.999082326889038, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1X0ZHTNDF0DYAMM67R34CKC", + "id": "01M1X1146K2BA2QBP3RZZNWS6C", + "kind": "memory", + "score": 0.8391201496124268, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X0ZHVNBH39VH86ETTXRK4M", + "id": "01M1X1146K0C9QYYT3NGHKTZ5X", + "kind": "memory", + "score": 0.4906356632709503, + "summary": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with \u00b125% jitter." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 932.7461000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3330, + "mcp_result_bytes": 3509, + "wire_bytes": 3545, + "reported_used_tokens": 3509, + "working_set_bytes": 287518720, + "peak_working_set_bytes": 288436224 + }, + { + "query": "how do I rotate the encryption key protecting the kimetsu brain database", + "ranked": [ + "kimetsu-eval-fixture-shape" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZJJ71P374MV9AEP4YS28", + "id": "01M1X1153R6PQSRYV2K8SX7DV6", + "kind": "memory", + "score": 0.8046634197235107, + "summary": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` \u2014 a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases)." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 912.4278, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 817, + "mcp_result_bytes": 942, + "wire_bytes": 978, + "reported_used_tokens": 942, + "working_set_bytes": 287518720, + "peak_working_set_bytes": 288436224 + }, + { + "query": "which tokio runtime worker-thread settings does the kimetsu MCP server use", + "ranked": [ + "tokio-blocking-in-async", + "tokio-runtime-in-tests", + "mcp-stdout-protocol" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZGPA0V0JRMAC9T4Z7Y8Y", + "id": "01M1X116072PXCY6RKE8TFPWJK", + "kind": "memory", + "score": 0.9973159432411194, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + }, + { + "expansion_handle": "memory:01M1X0ZGQ908QPA9R8BQ4WTCEK", + "id": "01M1X11608KCN14YHVADGYPAAW", + "kind": "memory", + "score": 0.8583173155784607, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + }, + { + "expansion_handle": "memory:01M1X0ZHK46P88A6EV49JZ72Z3", + "id": "01M1X11608KTNY2MG4E77VA4RR", + "kind": "memory", + "score": 0.8141786456108093, + "summary": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 927.2064, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1847, + "mcp_result_bytes": 1972, + "wire_bytes": 2008, + "reported_used_tokens": 1972, + "working_set_bytes": 287522816, + "peak_working_set_bytes": 288436224 + }, + { + "query": "how does kimetsu sync memories between two machines over the network", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 851.4422999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 287596544, + "peak_working_set_bytes": 288509952 + }, + { + "query": "recovering a corrupted usearch ANN index after a power loss", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 817.9463999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 287674368, + "peak_working_set_bytes": 288587776 + }, + { + "query": "what postgres schema should I use to store kimetsu memories", + "ranked": [ + "kimetsu-memory-scopes", + "testing-fixture-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZJ838F1BR8VTV7ERMJB7", + "id": "01M1X118HQ8SB9RK5SMMN9K96Q", + "kind": "memory", + "score": 0.9890244603157043, + "summary": "project:fact - [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available \u2014 if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope." + }, + { + "expansion_handle": "memory:01M1X0ZHJ7V8BRWKTYR18X6ACN", + "id": "01M1X118HQNVK8VNKQYWFK54EX", + "kind": "memory", + "score": 0.8922504782676697, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 917.8317999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1389, + "mcp_result_bytes": 1488, + "wire_bytes": 1524, + "reported_used_tokens": 1488, + "working_set_bytes": 287678464, + "peak_working_set_bytes": 288587776 + }, + { + "query": "the whole CI job just froze forever with no failure output after my latest test PR", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 895.8597000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 287703040, + "peak_working_set_bytes": 288620544 + }, + { + "query": "running the test suite left junk state in my home directory", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 957.8245, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 287784960, + "peak_working_set_bytes": 288702464 + }, + { + "query": "I deleted a bunch of old rows but the file on disk is still the same size", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 931.8535999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 287789056, + "peak_working_set_bytes": 288706560 + }, + { + "query": "adding one new crate quietly changed how the whole workspace builds", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-lockfile-drift", + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZEQ4Z80DHHAR2MNQZ1A7", + "id": "01M1X11C59ZTB70BMJNA5H19N5", + "kind": "memory", + "score": 0.9941080808639526, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X0ZFNME7BP5VJE18QNBMZC", + "id": "01M1X11C593TP2QMP35T49XSRH", + "kind": "memory", + "score": 0.9717232584953308, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this \u2014 it errors on any lockfile diff." + }, + { + "expansion_handle": "memory:01M1X0ZFQJ2YCP0MRA63WFP9S4", + "id": "01M1X11C59N5FB0M7YXVGGVP77", + "kind": "memory", + "score": 0.9183088541030884, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 930.6911, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2374, + "mcp_result_bytes": 2495, + "wire_bytes": 2531, + "reported_used_tokens": 2495, + "working_set_bytes": 287789056, + "peak_working_set_bytes": 288710656 + }, + { + "query": "we cannot pull an async runtime into the agent just to talk to AWS", + "ranked": [ + "tokio-blocking-in-async", + "tokio-runtime-in-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZGPA0V0JRMAC9T4Z7Y8Y", + "id": "01M1X11D2QA22S4WMWCEMFJBAR", + "kind": "memory", + "score": 0.7520647644996643, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + }, + { + "expansion_handle": "memory:01M1X0ZGQ908QPA9R8BQ4WTCEK", + "id": "01M1X11D2QZ6H6FAN9AA7KHQH2", + "kind": "memory", + "score": 0.7233642935752869, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 939.882, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1369, + "mcp_result_bytes": 1476, + "wire_bytes": 1512, + "reported_used_tokens": 1476, + "working_set_bytes": 287780864, + "peak_working_set_bytes": 288743424 + }, + { + "query": "users should be able to tell which build variant they installed from the version output", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 907.8859, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288157696, + "peak_working_set_bytes": 289075200 + }, + { + "query": "what gotchas should I expect writing process-inspection code that works on both Windows and Unix?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 860.4214, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288215040, + "peak_working_set_bytes": 289136640 + }, + { + "query": "why might tests behave differently on my machine than in the full CI run?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 857.4788, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288206848, + "peak_working_set_bytes": 289136640 + }, + { + "query": "what do I need to know before wiring kimetsu into a brand new host agent?", + "ranked": [ + "bridge-target-enum-seams", + "kimetsu-daemon-lifecycle", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZETMGXDNJHWBWG9ZZ2Y5", + "id": "01M1X11GHNCN40VNDXJE56T0J9", + "kind": "memory", + "score": 0.9741999506950378, + "summary": "project:fact - [2026-09-07] [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + }, + { + "expansion_handle": "memory:01M1X0ZJ67E5EZD48V1ZXX3EDF", + "id": "01M1X11GHNBPTRQW0NS7CCDYAF", + "kind": "memory", + "score": 0.9637662768363952, + "summary": "project:fact - [2026-09-07] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1X0ZEN209JTV53G9N189X79", + "id": "01M1X11GHNKWWF83SJFKQX22K2", + "kind": "memory", + "score": 0.4149944484233856, + "summary": "project:fact - [2026-09-07] [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 0.6666666666666666, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 926.2321, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2390, + "mcp_result_bytes": 2555, + "wire_bytes": 2591, + "reported_used_tokens": 2555, + "working_set_bytes": 288215040, + "peak_working_set_bytes": 289136640 + }, + { + "query": "tell me everything relevant to running kimetsu against AWS", + "ranked": [ + "kimetsu-mrr-metric", + "aws-credentials-chain", + "cargo-feature-unification-embeddings", + "kimetsu-eval-fixture-shape" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZJKCP6EMVFNJJTBAXD3M", + "id": "01M1X11HERESDM99SZ60Y3B94X", + "kind": "memory", + "score": 0.984548270702362, + "summary": "project:fact - [tags: kimetsu bench mrr recall metrics evaluation] kimetsu bench reports MRR (Mean Reciprocal Rank) and Recall@K. MRR is 1/rank_of_first_relevant_result, averaged across cases; it penalizes models that rank the correct answer 2nd or 3rd. Recall@K is the fraction of cases where at least one relevant answer appears in the top K." + }, + { + "expansion_handle": "memory:01M1X0ZHSCPRWJGM9PSEB6ZM8G", + "id": "01M1X11HERDY6VZRHEG0YMSRZD", + "kind": "memory", + "score": 0.9737622141838074, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + }, + { + "expansion_handle": "memory:01M1X0ZEQ4Z80DHHAR2MNQZ1A7", + "id": "01M1X11HERE5Z4VDDGZZP3X000", + "kind": "memory", + "score": 0.9726329445838928, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X0ZJJ71P374MV9AEP4YS28", + "id": "01M1X11HER9NJGB7HRTBG1S138", + "kind": "memory", + "score": 0.9641559720039368, + "summary": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` \u2014 a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases)." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 926.8692, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2883, + "mcp_result_bytes": 3066, + "wire_bytes": 3102, + "reported_used_tokens": 3066, + "working_set_bytes": 288288768, + "peak_working_set_bytes": 289198080 + }, + { + "query": "ingesting a cloned repo when the brain lives under a different root", + "ranked": [ + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZEJZP4HRFMXFVX53ZJ2M", + "id": "01M1X11JBQ4GBNEHGEAYKP1PBK", + "kind": "memory", + "score": 0.9995300769805908, + "summary": "project:fact - [2026-09-07] [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 860.3688999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1274, + "mcp_result_bytes": 1355, + "wire_bytes": 1391, + "reported_used_tokens": 1355, + "working_set_bytes": 288288768, + "peak_working_set_bytes": 289198080 + }, + { + "query": "streamable-http transport entry for openclaw.json with a bearer token", + "ranked": [ + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZEN209JTV53G9N189X79", + "id": "01M1X11K6JDPXSNHMDRCF8MYZR", + "kind": "memory", + "score": 0.9921918511390686, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 904.0138, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 996, + "mcp_result_bytes": 1125, + "wire_bytes": 1161, + "reported_used_tokens": 1125, + "working_set_bytes": 288366592, + "peak_working_set_bytes": 289284096 + }, + { + "query": "serializing ingests with a tokio mutex to avoid checkout races", + "ranked": [ + "remote-ingest-split-roots", + "testing-serial-vs-parallel", + "tokio-select-cancellation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZEJZP4HRFMXFVX53ZJ2M", + "id": "01M1X11M3D1CT53T6C17RQJSBZ", + "kind": "memory", + "score": 0.9795480966567992, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1X0ZHEBS8TQMPSTT182J1MD", + "id": "01M1X11M3DJ6SF42XS02RG851B", + "kind": "memory", + "score": 0.9425267577171326, + "summary": "project:fact - [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`)." + }, + { + "expansion_handle": "memory:01M1X0ZGRCAN29FYJAR5E4QAHR", + "id": "01M1X11M3DF1DXDHT78VS0JP00", + "kind": "memory", + "score": 0.5619664192199707, + "summary": "project:fact - [tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1012.7630999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2376, + "mcp_result_bytes": 2493, + "wire_bytes": 2529, + "reported_used_tokens": 2493, + "working_set_bytes": 288411648, + "peak_working_set_bytes": 289333248 + }, + { + "query": "percent-encoding the colon in the bedrock model id for the invoke URL", + "ranked": [ + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZERMERMXX9NA2KTGPZDG", + "id": "01M1X11N3PT3BX3G32T5RA9819", + "kind": "memory", + "score": 0.8341025710105896, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 971.3312, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1204, + "mcp_result_bytes": 1293, + "wire_bytes": 1329, + "reported_used_tokens": 1293, + "working_set_bytes": 288432128, + "peak_working_set_bytes": 289345536 + }, + { + "query": "deduplicating re-imported memories against pre-existing ids", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZF5XE4S8M5NK3DAHN6PE", + "id": "01M1X11P1T9A1NEW17V0NX7WEV", + "kind": "memory", + "score": 0.9991393089294434, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount \u2014 both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1000.2307, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 966, + "mcp_result_bytes": 1047, + "wire_bytes": 1083, + "reported_used_tokens": 1047, + "working_set_bytes": 288452608, + "peak_working_set_bytes": 289349632 + }, + { + "query": "parsing DMTF datetimes", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZF8E3HTSE36QFFFJ0YBT", + "id": "01M1X11Q05BG304D38P2XH7G1P", + "kind": "memory", + "score": 0.9934942126274108, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 699.3133, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 924, + "mcp_result_bytes": 1013, + "wire_bytes": 1049, + "reported_used_tokens": 1013, + "working_set_bytes": 288452608, + "peak_working_set_bytes": 289349632 + }, + { + "query": "how should install derive a stable identifier from the git remote URL?", + "ranked": [ + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZEN209JTV53G9N189X79", + "id": "01M1X11QP66AKRZ30MHKPZKMRG", + "kind": "memory", + "score": 0.98285174369812, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 900.205, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 995, + "mcp_result_bytes": 1124, + "wire_bytes": 1160, + "reported_used_tokens": 1124, + "working_set_bytes": 288452608, + "peak_working_set_bytes": 289366016 + }, + { + "query": "the secret token must not end up written into the host config file", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 965.1904999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 228, + "mcp_result_bytes": 291, + "wire_bytes": 327, + "reported_used_tokens": 291, + "working_set_bytes": 288518144, + "peak_working_set_bytes": 289435648 + }, + { + "query": "keep the cleanup logic unit-testable without touching environment variables", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 910.447, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288526336, + "peak_working_set_bytes": 289435648 + }, + { + "query": "how do we stop the server from cloning arbitrary repos clients request?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 904.1233, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 228, + "mcp_result_bytes": 291, + "wire_bytes": 327, + "reported_used_tokens": 291, + "working_set_bytes": 288526336, + "peak_working_set_bytes": 289443840 + }, + { + "query": "make sure a wrong guess about a host plugin API never breaks that host", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZEVTX6BZPYY73M2KS091", + "id": "01M1X11V9SRXX87XZ9EW75YP8C", + "kind": "memory", + "score": 0.928434193134308, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 794.0213, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 803, + "mcp_result_bytes": 892, + "wire_bytes": 928, + "reported_used_tokens": 892, + "working_set_bytes": 288526336, + "peak_working_set_bytes": 289447936 + }, + { + "query": "which wire-format trick lets us reuse the existing Anthropic request builder for AWS?", + "ranked": [ + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZERMERMXX9NA2KTGPZDG", + "id": "01M1X11W2Q7BPE5TAJVRS1VA3M", + "kind": "memory", + "score": 0.9748817682266236, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 978.0948000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1203, + "mcp_result_bytes": 1292, + "wire_bytes": 1328, + "reported_used_tokens": 1292, + "working_set_bytes": 288534528, + "peak_working_set_bytes": 289452032 + }, + { + "query": "the self-update froze because something was still holding the executable", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 945.4806, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288571392, + "peak_working_set_bytes": 289480704 + }, + { + "query": "our notes about the extension API turned out wrong once we read the actual repo", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 912.3574, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288579584, + "peak_working_set_bytes": 289488896 + }, + { + "query": "half the benchmark trials die right after the first one finishes", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 873.4009000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288608256, + "peak_working_set_bytes": 289525760 + }, + { + "query": "I need this parser visible to tests on every OS even though only one OS calls it", + "ranked": [ + "cfg-cross-platform-dead-code" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZFBCQ5Q2DBF8XZ334WTW", + "id": "01M1X11ZP5CGH68F0C5V2C3JMK", + "kind": "memory", + "score": 0.36490198969841, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 902.4725, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 939, + "reported_used_tokens": 903, + "working_set_bytes": 288681984, + "peak_working_set_bytes": 289587200 + }, + { + "query": "the config file content refuses to parse even though the TOML looks valid", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZF72C210NDRKM00YJ4W2", + "id": "01M1X120K6GMG9T0FSNW3R5GP6", + "kind": "memory", + "score": 0.6614054441452026, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 969.7701, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 733, + "mcp_result_bytes": 814, + "wire_bytes": 850, + "reported_used_tokens": 814, + "working_set_bytes": 288931840, + "peak_working_set_bytes": 289849344 + }, + { + "query": "the remote server must refresh its checkout before answering file queries", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 949.1822, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 228, + "mcp_result_bytes": 291, + "wire_bytes": 327, + "reported_used_tokens": 291, + "working_set_bytes": 289087488, + "peak_working_set_bytes": 290004992 + }, + { + "query": "tests must not climb to a parent git repository when resolving project paths", + "ranked": [ + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZF1GHG8GMEWTPEF0B3GJ", + "id": "01M1X122EZR46X725QQVWTPBR7", + "kind": "memory", + "score": 0.9839988350868224, + "summary": "project:fact - [2026-09-07] [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 953.4899999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 794, + "mcp_result_bytes": 875, + "wire_bytes": 911, + "reported_used_tokens": 875, + "working_set_bytes": 289112064, + "peak_working_set_bytes": 290021376 + }, + { + "query": "how do I test request signing deterministically when timestamps change every run?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 950.403, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 289218560, + "peak_working_set_bytes": 290131968 + }, + { + "query": "adding a new variant to the host target enum - which places will I forget to update?", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZETMGXDNJHWBWG9ZZ2Y5", + "id": "01M1X1249GFNDEJ329ERD38VEA", + "kind": "memory", + "score": 0.885076105594635, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 957.6899, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1058, + "mcp_result_bytes": 1139, + "wire_bytes": 1175, + "reported_used_tokens": 1139, + "working_set_bytes": 289275904, + "peak_working_set_bytes": 290189312 + }, + { + "query": "how do I enable GPU acceleration for kimetsu embedding inference", + "ranked": [ + "mcp-tool-timeouts", + "kimetsu-proactive-hooks" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZHM6HQ7BES1FK6GH1PFD", + "id": "01M1X1258PZY7W6P2TMR4BF3GY", + "kind": "memory", + "score": 0.9826309084892272, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + }, + { + "expansion_handle": "memory:01M1X0ZJA7VJPYZXDWE76RW90E", + "id": "01M1X1258PXV4AQECVYRKD2X5P", + "kind": "memory", + "score": 0.8807981610298157, + "summary": "project:fact - [tags: kimetsu proactive hooks context injection] kimetsu's proactive context injection runs before each agent turn (pre-turn hook) and injects relevant memories into the system prompt prefix. The hook invocation adds latency to the first token: embedding inference + vector search + reranking + context formatting. On a cold start, this can be 1-3 seconds." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 978.1902, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1377, + "mcp_result_bytes": 1476, + "wire_bytes": 1512, + "reported_used_tokens": 1476, + "working_set_bytes": 289345536, + "peak_working_set_bytes": 290238464 + }, + { + "query": "how do I throttle kimetsu API spend per month", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 865.4775999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 289357824, + "peak_working_set_bytes": 290275328 + }, + { + "query": "can the kimetsu brain database be stored in S3 instead of on disk", + "ranked": [ + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZHWS1G2E6ER6NHTRVEWK", + "id": "01M1X12711PEAJSHK1CYDWHGK1", + "kind": "memory", + "score": 0.38596054911613464, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 812.9572000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 875, + "mcp_result_bytes": 956, + "wire_bytes": 992, + "reported_used_tokens": 956, + "working_set_bytes": 289361920, + "peak_working_set_bytes": 290279424 + }, + { + "query": "how do I plug a custom tokenizer into the FTS index", + "ranked": [ + "sqlite-fts5-tokenizer" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZFG36H1KATE1SN5VW1B6", + "id": "01M1X127TGK0518MTV2YGX61MW", + "kind": "memory", + "score": 0.9691632390022278, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 885.3853, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 671, + "mcp_result_bytes": 756, + "wire_bytes": 792, + "reported_used_tokens": 756, + "working_set_bytes": 289398784, + "peak_working_set_bytes": 290304000 + }, + { + "query": "what should I check when kimetsu behaves differently on Windows than on Linux?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 871.6525, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 289509376, + "peak_working_set_bytes": 290422784 + }, + { + "query": "what are the moving parts of the kimetsu remote deployment story?", + "ranked": [ + "kimetsu-write-tools-gate", + "ci-secrets-masking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZJB78MBB69JA7ZTYBJNG", + "id": "01M1X129HJ11GTDY2XM376881H", + "kind": "memory", + "score": 0.9729357361793518, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level \u2014 disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1X0ZJ36VASSAJBJPDTPC7ZQ", + "id": "01M1X129HJMZ2TPZ14QRKGBBCQ", + "kind": "memory", + "score": 0.8412115573883057, + "summary": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output \u2014 but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 870.0437, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1410, + "mcp_result_bytes": 1509, + "wire_bytes": 1546, + "reported_used_tokens": 1509, + "working_set_bytes": 289517568, + "peak_working_set_bytes": 290435072 + }, + { + "query": "which lessons cover guarding behavior behind environment variables?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 796.6651999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 289570816, + "peak_working_set_bytes": 290476032 + }, + { + "query": "SQLite BUSY error under concurrent writes", + "ranked": [ + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZFCB0HTB40H3S45W1XS7", + "id": "01M1X12B6E490PXMKV03SHNHXG", + "kind": "memory", + "score": 0.9978362917900084, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 798.016, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 898, + "mcp_result_bytes": 979, + "wire_bytes": 1016, + "reported_used_tokens": 979, + "working_set_bytes": 289603584, + "peak_working_set_bytes": 290496512 + }, + { + "query": "SQLite WAL mode breaks when the database is on a network share", + "ranked": [ + "sqlite-wal-network-drive", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZFF44WCC49W67QG1BAQR", + "id": "01M1X12BZTBSQH6YHCDDTW1TW2", + "kind": "memory", + "score": 0.999302864074707, + "summary": "project:fact - [2026-09-07] [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + }, + { + "expansion_handle": "memory:01M1X0ZFCB0HTB40H3S45W1XS7", + "id": "01M1X12BZTXSFYK2E3Z0V8AMJC", + "kind": "memory", + "score": 0.9966553449630736, + "summary": "project:fact - [2026-09-07] [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 955.3247, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1448, + "mcp_result_bytes": 1547, + "wire_bytes": 1584, + "reported_used_tokens": 1547, + "working_set_bytes": 289734656, + "peak_working_set_bytes": 290648064 + }, + { + "query": "my SQLite WAL database causes SQLITE_IOERR_LOCK on a mapped drive", + "ranked": [ + "sqlite-wal-network-drive" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZFF44WCC49W67QG1BAQR", + "id": "01M1X12CWN6Y185MXT8VARKZV6", + "kind": "memory", + "score": 0.99892657995224, + "summary": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 956.1070000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 748, + "mcp_result_bytes": 829, + "wire_bytes": 866, + "reported_used_tokens": 829, + "working_set_bytes": 289902592, + "peak_working_set_bytes": 290811904 + }, + { + "query": "FTS5 tokenizer configuration for Rust identifiers with underscores", + "ranked": [ + "sqlite-fts5-tokenizer", + "kimetsu-query-stemming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZFG36H1KATE1SN5VW1B6", + "id": "01M1X12DVDQ227P5MSRX1GH3TT", + "kind": "memory", + "score": 0.998104453086853, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + }, + { + "expansion_handle": "memory:01M1X0ZJF4CV4GMMY9RCT11XHB", + "id": "01M1X12DVD7CT9JYYP2TZJE8A1", + "kind": "memory", + "score": 0.7023860812187195, + "summary": "project:fact - [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 901.8438, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1212, + "mcp_result_bytes": 1331, + "wire_bytes": 1368, + "reported_used_tokens": 1331, + "working_set_bytes": 289980416, + "peak_working_set_bytes": 290889728 + }, + { + "query": "I switched the FTS5 tokenizer but search stopped returning results", + "ranked": [ + "sqlite-fts5-tokenizer" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZFG36H1KATE1SN5VW1B6", + "id": "01M1X12EPHCFYB7TQV4GWMW3GB", + "kind": "memory", + "score": 0.8194089531898499, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 894.0798, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 670, + "mcp_result_bytes": 755, + "wire_bytes": 792, + "reported_used_tokens": 755, + "working_set_bytes": 290271232, + "peak_working_set_bytes": 291180544 + }, + { + "query": "optimal SQLite page size for storing embedding vectors", + "ranked": [ + "sqlite-page-size", + "onnx-dim-mismatch", + "onnx-cosine-vs-dot" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZFH2XQ9X4M5K2S5HGA03", + "id": "01M1X12FJDN8KWMVAABNVHKNB4", + "kind": "memory", + "score": 0.9990121126174928, + "summary": "project:fact - [tags: sqlite page_size performance rusqlite] SQLite's default page_size is 4096 bytes. For a write-heavy brain database with large BLOB payloads (embedding vectors), raising page_size to 16384 reduces fragmentation and improves sequential scan throughput. `PRAGMA page_size = 16384;` must be set BEFORE the first table is created \u2014 changing it on an existing database requires a VACUUM afterward to rebuild all pages." + }, + { + "expansion_handle": "memory:01M1X0ZGBQ2VZT14120VKW0MYX", + "id": "01M1X12FJDV7KRT6PMFN7VTS0V", + "kind": "memory", + "score": 0.9881643056869508, + "summary": "project:fact - [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results \u2014 the ANN index shape mismatch isn't always caught at runtime." + }, + { + "expansion_handle": "memory:01M1X0ZGANK91NVW3PQ4XQ3Y2Z", + "id": "01M1X12FJD44Q987DG15VNMXR2", + "kind": "memory", + "score": 0.9425415992736816, + "summary": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing \u2014 double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 816.9286, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1860, + "mcp_result_bytes": 1977, + "wire_bytes": 2014, + "reported_used_tokens": 1977, + "working_set_bytes": 290328576, + "peak_working_set_bytes": 291233792 + }, + { + "query": "ON DELETE CASCADE in SQLite does nothing \u2014 foreign keys not enforced", + "ranked": [ + "sqlite-foreign-keys-default-off" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZFHZS4JQ8R8W6MPR6CZ9", + "id": "01M1X12GC2F6ATE064JFPFW59K", + "kind": "memory", + "score": 0.9996858835220336, + "summary": "project:fact - [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting \u2014 every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 914.7467, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 736, + "mcp_result_bytes": 817, + "wire_bytes": 854, + "reported_used_tokens": 817, + "working_set_bytes": 290349056, + "peak_working_set_bytes": 291258368 + }, + { + "query": "indexing a JSON metadata column in SQLite without a schema migration", + "ranked": [ + "sqlite-json1-extract", + "testing-fixture-drift", + "onnx-dim-mismatch", + "sqlite-partial-index" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZFJTMDVRM1V181QGCV04", + "id": "01M1X12H8RME745XYBV5YNPANS", + "kind": "memory", + "score": 0.9955366849899292, + "summary": "project:fact - [tags: sqlite json1 json_extract rusqlite] SQLite's json1 extension (built in since 3.38.0) lets you index and query JSONB columns with `json_extract(col, '$.field')`. To create a partial index over a JSON field: `CREATE INDEX idx ON memories (json_extract(metadata, '$.scope')) WHERE json_extract(metadata, '$.scope') IS NOT NULL;`. Use `json_each` for array fields." + }, + { + "expansion_handle": "memory:01M1X0ZHJ7V8BRWKTYR18X6ACN", + "id": "01M1X12H8R7T9E0KXB2S94RJR0", + "kind": "memory", + "score": 0.8227390646934509, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + }, + { + "expansion_handle": "memory:01M1X0ZGBQ2VZT14120VKW0MYX", + "id": "01M1X12H8RDDFTMPEA33FBB3KS", + "kind": "memory", + "score": 0.38374292850494385, + "summary": "project:fact - [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results \u2014 the ANN index shape mismatch isn't always caught at runtime." + }, + { + "expansion_handle": "memory:01M1X0ZFMP6S7YZWR2SJWA7FBM", + "id": "01M1X12H8RH5RZGZ5B5YXG3JAB", + "kind": "memory", + "score": 0.3276048004627228, + "summary": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query \u2014 the planner uses the partial index only when the WHERE clause matches." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 869.1461, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2381, + "mcp_result_bytes": 2516, + "wire_bytes": 2553, + "reported_used_tokens": 2516, + "working_set_bytes": 290406400, + "peak_working_set_bytes": 291315712 + }, + { + "query": "prepare() vs prepare_cached() in rusqlite hot insert loop", + "ranked": [ + "sqlite-prepared-stmt-cache" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZFKT29ANP128R99ZS3HZ", + "id": "01M1X12J3SVG8E0AJ0GJXSWX5B", + "kind": "memory", + "score": 0.9993672966957092, + "summary": "project:fact - [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 861.3597, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 689, + "mcp_result_bytes": 770, + "wire_bytes": 807, + "reported_used_tokens": 770, + "working_set_bytes": 290557952, + "peak_working_set_bytes": 291463168 + }, + { + "query": "speed up bulk memory ingest by caching SQL statements", + "ranked": [ + "sqlite-prepared-stmt-cache" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZFKT29ANP128R99ZS3HZ", + "id": "01M1X12JZDW504M60RMGTTHG62", + "kind": "memory", + "score": 0.9823396801948548, + "summary": "project:fact - [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 902.7596, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 688, + "mcp_result_bytes": 769, + "wire_bytes": 806, + "reported_used_tokens": 769, + "working_set_bytes": 290570240, + "peak_working_set_bytes": 291475456 + }, + { + "query": "partial index on deleted_at IS NULL for faster active memory queries", + "ranked": [ + "sqlite-partial-index" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZFMP6S7YZWR2SJWA7FBM", + "id": "01M1X12KV5BADB2X1CY02RC71D", + "kind": "memory", + "score": 0.9988954067230223, + "summary": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query \u2014 the planner uses the partial index only when the WHERE clause matches." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 883.9239, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 794, + "mcp_result_bytes": 875, + "wire_bytes": 912, + "reported_used_tokens": 875, + "working_set_bytes": 290570240, + "peak_working_set_bytes": 291475456 + }, + { + "query": "the brain query is slow because it scans all rows including soft-deleted ones", + "ranked": [ + "sqlite-partial-index" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZFMP6S7YZWR2SJWA7FBM", + "id": "01M1X12MPX3NGQSQ12VTCK0BX2", + "kind": "memory", + "score": 0.5760471224784851, + "summary": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query \u2014 the planner uses the partial index only when the WHERE clause matches." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 907.42, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 793, + "mcp_result_bytes": 874, + "wire_bytes": 911, + "reported_used_tokens": 874, + "working_set_bytes": 290582528, + "peak_working_set_bytes": 291500032 + }, + { + "query": "Cargo.lock changed unexpectedly after adding a new workspace crate", + "ranked": [ + "cargo-lockfile-drift", + "cargo-feature-unification-embeddings", + "cargo-target-dir-sharing", + "cargo-patch-section" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZFNME7BP5VJE18QNBMZC", + "id": "01M1X12NKD6PHG6PWCW4JXTPFM", + "kind": "memory", + "score": 0.9991374015808104, + "summary": "project:fact - [2026-09-07] [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this \u2014 it errors on any lockfile diff." + }, + { + "expansion_handle": "memory:01M1X0ZEQ4Z80DHHAR2MNQZ1A7", + "id": "01M1X12NKD99ZDSEPPMTGN1CF4", + "kind": "memory", + "score": 0.9968542456626892, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X0ZFRM3VQ2YK91809T077K", + "id": "01M1X12NKDGX5G1C2WJH04JFCA", + "kind": "memory", + "score": 0.9829630851745604, + "summary": "project:fact - [2026-09-07] [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps \u2014 use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + }, + { + "expansion_handle": "memory:01M1X0ZFVEAGHAWDDF12PQAM8P", + "id": "01M1X12NKDTBE7E0RPY4PM0ESH", + "kind": "memory", + "score": 0.9262890815734864, + "summary": "project:fact - [2026-09-07] [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace \u2014 including transitive deps \u2014 that depend on `my-crate`. Remove the patch before publishing." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 781.3524, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3010, + "mcp_result_bytes": 3153, + "wire_bytes": 3190, + "reported_used_tokens": 3153, + "working_set_bytes": 290586624, + "peak_working_set_bytes": 291504128 + }, + { + "query": "how do I prevent CI from accepting a modified lockfile silently?", + "ranked": [ + "cargo-lockfile-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZFNME7BP5VJE18QNBMZC", + "id": "01M1X12PBC8BA820PBF888QFV0", + "kind": "memory", + "score": 0.9125379323959352, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this \u2014 it errors on any lockfile diff." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 890.8764, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 765, + "mcp_result_bytes": 846, + "wire_bytes": 883, + "reported_used_tokens": 846, + "working_set_bytes": 290598912, + "peak_working_set_bytes": 291516416 + }, + { + "query": "build.rs reruns on every incremental build even when nothing changed", + "ranked": [ + "cargo-build-script-rerun" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZFPHGMQ35E3J9NBA5G31", + "id": "01M1X12Q792RY5NR0P5BEZWCP5", + "kind": "memory", + "score": 0.9996689558029176, + "summary": "project:fact - [2026-09-07] [tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 909.1976000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 698, + "mcp_result_bytes": 779, + "wire_bytes": 816, + "reported_used_tokens": 779, + "working_set_bytes": 290615296, + "peak_working_set_bytes": 291532800 + }, + { + "query": "incremental cargo build is slow because build script runs every time", + "ranked": [ + "cargo-build-script-rerun" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZFPHGMQ35E3J9NBA5G31", + "id": "01M1X12R3QTFW6BAZVS97W2E6G", + "kind": "memory", + "score": 0.9978280663490297, + "summary": "project:fact - [tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 879.0059, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 685, + "mcp_result_bytes": 766, + "wire_bytes": 803, + "reported_used_tokens": 766, + "working_set_bytes": 290680832, + "peak_working_set_bytes": 291594240 + }, + { + "query": "a dev-dependency is activating an embeddings feature in my production build", + "ranked": [ + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZFQJ2YCP0MRA63WFP9S4", + "id": "01M1X12RZFKWRT0C9EJ9N0BSP7", + "kind": "memory", + "score": 0.9944193959236144, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 891.5917, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 931, + "mcp_result_bytes": 1012, + "wire_bytes": 1049, + "reported_used_tokens": 1012, + "working_set_bytes": 290684928, + "peak_working_set_bytes": 291598336 + }, + { + "query": "how do I prevent a test-only feature from bleeding into the non-test compilation?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 839.4689, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 290684928, + "peak_working_set_bytes": 291598336 + }, + { + "query": "linker errors in target/ caused by antivirus holding the exe file", + "ranked": [ + "windows-file-locking-av", + "cargo-target-dir-sharing" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZG0MQERHDPPT38CPWMW6", + "id": "01M1X12TNC41XJMKHEVNCKFA7W", + "kind": "memory", + "score": 0.9997633099555968, + "summary": "project:fact - [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + }, + { + "expansion_handle": "memory:01M1X0ZFRM3VQ2YK91809T077K", + "id": "01M1X12TNCW87N7R86HWNHF8E5", + "kind": "memory", + "score": 0.7463976740837097, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps \u2014 use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 874.2428, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1523, + "mcp_result_bytes": 1622, + "wire_bytes": 1659, + "reported_used_tokens": 1622, + "working_set_bytes": 290684928, + "peak_working_set_bytes": 291598336 + }, + { + "query": "Access is denied (os error 5) when linking on Windows \u2014 how do I fix this?", + "ranked": [ + "windows-file-locking-av" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZG0MQERHDPPT38CPWMW6", + "id": "01M1X12VGY4JA0FBDW8PJ2KWR3", + "kind": "memory", + "score": 0.9977193474769592, + "summary": "project:fact - [2026-09-07] [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 886.1293000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 769, + "mcp_result_bytes": 850, + "wire_bytes": 887, + "reported_used_tokens": 850, + "working_set_bytes": 290721792, + "peak_working_set_bytes": 291631104 + }, + { + "query": "incremental build broke with a type mismatch after switching branches", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZFSK3Z1FRY1DKJNKHT4Y", + "id": "01M1X12WCKZX009450BW089D62", + "kind": "memory", + "score": 0.7971777319908142, + "summary": "project:fact - [2026-09-07] [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 885.9780000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 890, + "mcp_result_bytes": 971, + "wire_bytes": 1008, + "reported_used_tokens": 971, + "working_set_bytes": 290742272, + "peak_working_set_bytes": 291655680 + }, + { + "query": "cargo reports a type error that references a type not in the codebase", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZFSK3Z1FRY1DKJNKHT4Y", + "id": "01M1X12X8NT6G8SW5WX9SFN7EY", + "kind": "memory", + "score": 0.7925198078155518, + "summary": "project:fact - [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 850.8251, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 877, + "mcp_result_bytes": 958, + "wire_bytes": 995, + "reported_used_tokens": 958, + "working_set_bytes": 290746368, + "peak_working_set_bytes": 291667968 + }, + { + "query": "compile fastembed at O2 in debug builds to avoid slow embedding inference", + "ranked": [ + "cargo-profile-override", + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZFTEX7E4JYA35AJM4JBQ", + "id": "01M1X12Y2RHRGAC2Z6Z08W36S6", + "kind": "memory", + "score": 0.9932281374931335, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1X0ZHM6HQ7BES1FK6GH1PFD", + "id": "01M1X12Y2R7QRJVXRX4QQRXRC7", + "kind": "memory", + "score": 0.987656831741333, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 892.3594, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1322, + "mcp_result_bytes": 1421, + "wire_bytes": 1458, + "reported_used_tokens": 1421, + "working_set_bytes": 290762752, + "peak_working_set_bytes": 291680256 + }, + { + "query": "override compilation profile for a single crate in a Cargo workspace", + "ranked": [ + "cargo-patch-section", + "cargo-profile-override", + "cargo-target-dir-sharing", + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZFVEAGHAWDDF12PQAM8P", + "id": "01M1X12YYWECJM5E7VTSAYV659", + "kind": "memory", + "score": 0.9984123706817628, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace \u2014 including transitive deps \u2014 that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1X0ZFTEX7E4JYA35AJM4JBQ", + "id": "01M1X12YYW896KFG7SCNJC4E92", + "kind": "memory", + "score": 0.9979992508888244, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1X0ZFRM3VQ2YK91809T077K", + "id": "01M1X12YYWXBCM8N7T9XXXZNM8", + "kind": "memory", + "score": 0.9956549406051636, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps \u2014 use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + }, + { + "expansion_handle": "memory:01M1X0ZFQJ2YCP0MRA63WFP9S4", + "id": "01M1X12YYWJFNJ49AVF1FYVP3E", + "kind": "memory", + "score": 0.9820712208747864, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 0.5, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 916.9544, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2682, + "mcp_result_bytes": 2821, + "wire_bytes": 2858, + "reported_used_tokens": 2821, + "working_set_bytes": 290816000, + "peak_working_set_bytes": 291737600 + }, + { + "query": "[patch.crates-io] workspace dependency override", + "ranked": [ + "cargo-patch-section", + "cargo-lockfile-drift", + "cargo-dev-dep-leak", + "cargo-target-dir-sharing" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZFVEAGHAWDDF12PQAM8P", + "id": "01M1X12ZV9BXAVYDHFPXJX4Y54", + "kind": "memory", + "score": 0.9999405145645142, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace \u2014 including transitive deps \u2014 that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1X0ZFNME7BP5VJE18QNBMZC", + "id": "01M1X12ZVADP0EPAN31N7YRQBB", + "kind": "memory", + "score": 0.9975811243057252, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this \u2014 it errors on any lockfile diff." + }, + { + "expansion_handle": "memory:01M1X0ZFQJ2YCP0MRA63WFP9S4", + "id": "01M1X12ZVACZWG826KNX85FB4N", + "kind": "memory", + "score": 0.994149684906006, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + }, + { + "expansion_handle": "memory:01M1X0ZFRM3VQ2YK91809T077K", + "id": "01M1X12ZVAT6V5G01YNWHJBFFG", + "kind": "memory", + "score": 0.7471600770950317, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps \u2014 use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 691.1931, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2755, + "mcp_result_bytes": 2894, + "wire_bytes": 2931, + "reported_used_tokens": 2894, + "working_set_bytes": 290824192, + "peak_working_set_bytes": 291737600 + }, + { + "query": "pin minimum supported Rust version in Cargo.toml", + "ranked": [ + "cargo-msrv" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZFWD6FKV99DDXXHKC0MD", + "id": "01M1X130GVNVZ39DMMTFPSZPQA", + "kind": "memory", + "score": 0.999652862548828, + "summary": "project:fact - [tags: cargo rust msrv edition compatibility] Set `rust-version` in each `Cargo.toml` to declare the minimum supported Rust version (MSRV). Cargo enforces this with `--check`: `cargo check` fails if the toolchain is older than `rust-version`. Keep MSRV as old as your oldest supported deployment target." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 814.7516, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 693, + "mcp_result_bytes": 774, + "wire_bytes": 811, + "reported_used_tokens": 774, + "working_set_bytes": 290832384, + "peak_working_set_bytes": 291749888 + }, + { + "query": "Windows path over 260 characters causes OS error 3 during Cargo build", + "ranked": [ + "windows-long-paths", + "windows-file-locking-av" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZFZNEQWY15T4NBHQH2R8", + "id": "01M1X131B8XCHJH4EFFA93FGNX", + "kind": "memory", + "score": 0.9964189529418944, + "summary": "project:fact - [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe." + }, + { + "expansion_handle": "memory:01M1X0ZG0MQERHDPPT38CPWMW6", + "id": "01M1X131B8MAPCBE340J4DKNZX", + "kind": "memory", + "score": 0.9571694135665894, + "summary": "project:fact - [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 841.3218999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1297, + "mcp_result_bytes": 1406, + "wire_bytes": 1443, + "reported_used_tokens": 1406, + "working_set_bytes": 290832384, + "peak_working_set_bytes": 291749888 + }, + { + "query": "how do I enable long file paths for Cargo on Windows?", + "ranked": [ + "windows-long-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZFZNEQWY15T4NBHQH2R8", + "id": "01M1X132639H21D4RWBW2AHBAZ", + "kind": "memory", + "score": 0.9998334646224976, + "summary": "project:fact - [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 968.9833, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 769, + "mcp_result_bytes": 860, + "wire_bytes": 897, + "reported_used_tokens": 860, + "working_set_bytes": 290832384, + "peak_working_set_bytes": 291749888 + }, + { + "query": "intermittent sharing violation errors when Rust linker writes the exe on Windows", + "ranked": [ + "windows-file-locking-av", + "windows-long-paths", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZG0MQERHDPPT38CPWMW6", + "id": "01M1X1334RYK2X50V06MHG2FGY", + "kind": "memory", + "score": 0.999750316143036, + "summary": "project:fact - [2026-09-07] [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + }, + { + "expansion_handle": "memory:01M1X0ZFZNEQWY15T4NBHQH2R8", + "id": "01M1X1334RS0QNEQ2FVH8DTW3T", + "kind": "memory", + "score": 0.4757097661495209, + "summary": "project:fact - [2026-09-07] [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe." + }, + { + "expansion_handle": "memory:01M1X0ZFCB0HTB40H3S45W1XS7", + "id": "01M1X1334RF8KKZ4WX6RERB9MT", + "kind": "memory", + "score": 0.38107830286026, + "summary": "project:fact - [2026-09-07] [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 914.0797, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2006, + "mcp_result_bytes": 2133, + "wire_bytes": 2170, + "reported_used_tokens": 2133, + "working_set_bytes": 290832384, + "peak_working_set_bytes": 291749888 + }, + { + "query": "Rust walkdir follows junctions differently from symlinks on Windows", + "ranked": [ + "windows-junctions-vs-symlinks" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZG3P0PXNV8BQGJZVFPQM", + "id": "01M1X1341408AMD6SS0YQYXRY8", + "kind": "memory", + "score": 0.9996020197868348, + "summary": "project:fact - [tags: windows junctions symlinks rust std::fs] On Windows, directory junctions (NTFS reparse points) behave like symlinks for directory traversal but `std::fs::symlink_metadata` returns `FileType::is_symlink() = false` for junctions (only true for regular symlinks). Use `std::fs::read_link` \u2014 it succeeds for both junction and symlink. `walkdir` crate's `follow_links` follows both, but its `is_symlink()` method correctly reports only actual symlinks." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 908.1901, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 845, + "mcp_result_bytes": 926, + "wire_bytes": 963, + "reported_used_tokens": 926, + "working_set_bytes": 290832384, + "peak_working_set_bytes": 291749888 + }, + { + "query": "UNC path canonicalize returns verbatim prefix \u2014 how do I strip it?", + "ranked": [ + "windows-unc-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZG1HHY43FMRZC1V4J7YH", + "id": "01M1X134WPXSVABREM0KCG79K0", + "kind": "memory", + "score": 0.9988629817962646, + "summary": "project:fact - [tags: windows unc-paths rust std::fs] Windows UNC paths (`\\\\server\\share\\...`) are not supported by most Rust `std::fs` operations unless passed through the extended-length prefix `\\\\?\\UNC\\server\\share\\...`. `std::path::Path::new(\"\\\\\\\\server\\\\share\")` works for basic operations but breaks with `canonicalize()` which returns the verbatim prefix form. When walking directory trees that may start on UNC paths, use the `dunce` crate to strip the verbatim prefix before comparing or displaying paths." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 906.8865000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 908, + "mcp_result_bytes": 1025, + "wire_bytes": 1062, + "reported_used_tokens": 1025, + "working_set_bytes": 290832384, + "peak_working_set_bytes": 291758080 + }, + { + "query": "UTF-8 memory text prints as mojibake in the Windows console", + "ranked": [ + "windows-console-encoding" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZG2K9NX2Y0HQSJ6J8RNX", + "id": "01M1X135RK7SY4ADFPY18HJ880", + "kind": "memory", + "score": 0.9996604919433594, + "summary": "project:fact - [tags: windows console encoding utf8 rust] Windows console code page defaults to the system ANSI code page (usually CP1252 or CP932), not UTF-8. Rust's `println!` writes UTF-8 bytes which display as mojibake in a non-UTF-8 console. Fix at process startup: call `SetConsoleOutputCP(65001)` via `winapi` or `windows-sys`, or set `PYTHONUTF8=1`/`RUST_LOG` before launch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 857.2295, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 757, + "mcp_result_bytes": 838, + "wire_bytes": 875, + "reported_used_tokens": 838, + "working_set_bytes": 290832384, + "peak_working_set_bytes": 291758080 + }, + { + "query": "process exit code is 4294967295 instead of -1 on Windows", + "ranked": [ + "windows-exit-codes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZG4QV3KMGYAKE2Q6XZ5K", + "id": "01M1X136KB6EQ1W4Z1RS5RJSCX", + "kind": "memory", + "score": 0.9966622591018676, + "summary": "project:fact - [tags: windows exit-codes rust process child] On Windows, process exit codes are 32-bit unsigned integers (DWORD). Rust's `ExitStatus::code()` returns `Option` \u2014 it's `None` if the process was killed by a signal (which Windows doesn't use; instead, TerminateProcess with a code). Conventional codes: 0=success, 1=generic error, 0xC0000005=access violation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 866.3086, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 753, + "mcp_result_bytes": 834, + "wire_bytes": 871, + "reported_used_tokens": 834, + "working_set_bytes": 290832384, + "peak_working_set_bytes": 291758080 + }, + { + "query": "tokenizer.json must match the ONNX model \u2014 what breaks if it doesn't?", + "ranked": [ + "onnx-tokenizer-mismatch" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZG6XBEVY7NHFNNKB4TDZ", + "id": "01M1X137EC5CPZJNG7E8630GXR", + "kind": "memory", + "score": 0.9991299510002136, + "summary": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly \u2014 specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings \u2014 cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 906.6381, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 959, + "mcp_result_bytes": 1040, + "wire_bytes": 1077, + "reported_used_tokens": 1040, + "working_set_bytes": 290832384, + "peak_working_set_bytes": 291758080 + }, + { + "query": "embedding quality degraded after I swapped in the INT8 quantized model", + "ranked": [ + "onnx-quantization-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZG7WCFGH0CD0BM3MAQWA", + "id": "01M1X138ANBY1YPENQC3Z72SZH", + "kind": "memory", + "score": 0.997980535030365, + "summary": "project:fact - [2026-09-07] [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals \u2014 cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 897.163, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 990, + "mcp_result_bytes": 1071, + "wire_bytes": 1108, + "reported_used_tokens": 1071, + "working_set_bytes": 290840576, + "peak_working_set_bytes": 291758080 + }, + { + "query": "missing attention mask causes low-norm embeddings in batch inference", + "ranked": [ + "onnx-batch-padding" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZG8RSPWDGNFMJVWG6XJN", + "id": "01M1X1396R8K70GZSF75G77C18", + "kind": "memory", + "score": 0.9998397827148438, + "summary": "project:fact - [tags: onnx batch padding attention-mask embeddings] When running batch inference with an ONNX model, all inputs in the batch must be padded to the same sequence length. The `attention_mask` tensor marks which tokens are real (1) and which are padding (0). Failing to pass `attention_mask` causes the model to average-pool over padding tokens, producing systematically lower-norm embeddings." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 880.0791, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 781, + "mcp_result_bytes": 862, + "wire_bytes": 899, + "reported_used_tokens": 862, + "working_set_bytes": 290844672, + "peak_working_set_bytes": 291758080 + }, + { + "query": "ONNX model download fails in a Docker container with no home directory", + "ranked": [ + "onnx-model-cache-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZG9SW9JS8VF783S447ZM", + "id": "01M1X13A2831YANGM1AVBYNYPQ", + "kind": "memory", + "score": 0.9887272119522096, + "summary": "project:fact - [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 923.5807, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 755, + "mcp_result_bytes": 838, + "wire_bytes": 875, + "reported_used_tokens": 838, + "working_set_bytes": 290844672, + "peak_working_set_bytes": 291758080 + }, + { + "query": "fastembed cache path environment variable for CI", + "ranked": [ + "onnx-model-cache-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZG9SW9JS8VF783S447ZM", + "id": "01M1X13AZ7E8ME1Q1PN22CDA80", + "kind": "memory", + "score": 0.9995118379592896, + "summary": "project:fact - [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 885.9224999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 756, + "mcp_result_bytes": 839, + "wire_bytes": 876, + "reported_used_tokens": 839, + "working_set_bytes": 290844672, + "peak_working_set_bytes": 291758080 + }, + { + "query": "cosine similarity vs dot product for L2-normalized embedding vectors", + "ranked": [ + "onnx-cosine-vs-dot", + "onnx-tokenizer-mismatch", + "onnx-quantization-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZGANK91NVW3PQ4XQ3Y2Z", + "id": "01M1X13BV0W7HRXCF80Z2S5EV9", + "kind": "memory", + "score": 0.9999407529830932, + "summary": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing \u2014 double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + }, + { + "expansion_handle": "memory:01M1X0ZG6XBEVY7NHFNNKB4TDZ", + "id": "01M1X13BV01XD0GZ7DN7E7JK86", + "kind": "memory", + "score": 0.9514977931976318, + "summary": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly \u2014 specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings \u2014 cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo." + }, + { + "expansion_handle": "memory:01M1X0ZG7WCFGH0CD0BM3MAQWA", + "id": "01M1X13BV00G4FN4Q9N97RAM8G", + "kind": "memory", + "score": 0.941756010055542, + "summary": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals \u2014 cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 858.1931999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2245, + "mcp_result_bytes": 2362, + "wire_bytes": 2399, + "reported_used_tokens": 2362, + "working_set_bytes": 290848768, + "peak_working_set_bytes": 291762176 + }, + { + "query": "stored vectors have wrong dimension after switching embedding models", + "ranked": [ + "onnx-dim-mismatch", + "onnx-cosine-vs-dot", + "onnx-tokenizer-mismatch" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZGBQ2VZT14120VKW0MYX", + "id": "01M1X13CPDYCF6TV5R1AYXT710", + "kind": "memory", + "score": 0.9997621178627014, + "summary": "project:fact - [2026-09-07] [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results \u2014 the ANN index shape mismatch isn't always caught at runtime." + }, + { + "expansion_handle": "memory:01M1X0ZGANK91NVW3PQ4XQ3Y2Z", + "id": "01M1X13CPD1MJRFDJEB7022TYY", + "kind": "memory", + "score": 0.997715711593628, + "summary": "project:fact - [2026-09-07] [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing \u2014 double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + }, + { + "expansion_handle": "memory:01M1X0ZG6XBEVY7NHFNNKB4TDZ", + "id": "01M1X13CPD8E3R70FEYP7H3VFP", + "kind": "memory", + "score": 0.9388805031776428, + "summary": "project:fact - [2026-09-07] [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly \u2014 specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings \u2014 cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 778.5001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2049, + "mcp_result_bytes": 2166, + "wire_bytes": 2203, + "reported_used_tokens": 2166, + "working_set_bytes": 290848768, + "peak_working_set_bytes": 291762176 + }, + { + "query": "E5 and Instructor models need a query prefix \u2014 what happens without it?", + "ranked": [ + "onnx-prefix-instructions", + "onnx-cosine-vs-dot" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZGEQVEH601WRF8HDX0SA", + "id": "01M1X13DE32H4KH82RABV8Y914", + "kind": "memory", + "score": 0.996955633163452, + "summary": "project:fact - [tags: onnx embeddings prefix instruction e5 query passage] E5 and Instructor family models require a text prefix on BOTH query and passage sides to produce meaningful similarities: query prefix `\"query: \"`, passage prefix `\"passage: \"`. Omitting the prefix can drop MRR by 10-15 percentage points on out-of-domain datasets. Check the model's README for the exact prefix string \u2014 it varies by model family." + }, + { + "expansion_handle": "memory:01M1X0ZGANK91NVW3PQ4XQ3Y2Z", + "id": "01M1X13DE380W4Q46Y6HVZTESF", + "kind": "memory", + "score": 0.9543967247009276, + "summary": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing \u2014 double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 887.4195, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1339, + "mcp_result_bytes": 1446, + "wire_bytes": 1483, + "reported_used_tokens": 1446, + "working_set_bytes": 290848768, + "peak_working_set_bytes": 291762176 + }, + { + "query": "ORT thread pool contention when running multiple bench processes in parallel", + "ranked": [ + "onnx-ort-threading" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZGFNAEPEZ0F0YYR16DCA", + "id": "01M1X13E9T3X3R3Q3ZXWQZH4Y1", + "kind": "memory", + "score": 0.9998078942298888, + "summary": "project:fact - [2026-09-07] [tags: onnx ort thread-pool parallelism cpu] ORT (ONNX Runtime) creates its own inter-op and intra-op thread pools. In a multi-process bench setup, each child inherits these pools and they compete for CPU cores. Set `SessionOptionsBuilder::with_intra_threads(1).with_inter_threads(1)` if you're running many parallel bench processes \u2014 this sacrifices per-inference throughput for lower contention." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 895.5813, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 802, + "mcp_result_bytes": 883, + "wire_bytes": 920, + "reported_used_tokens": 883, + "working_set_bytes": 290848768, + "peak_working_set_bytes": 291762176 + }, + { + "query": "git worktrees share the .kimetsu brain \u2014 how do I isolate test runs?", + "ranked": [ + "git-worktree-brain-isolation", + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZGGKZ262G85SY6B3T5B1", + "id": "01M1X13F5VPHGE8FNZW7CJ8D82", + "kind": "memory", + "score": 0.9996256828308104, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root \u2014 if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + }, + { + "expansion_handle": "memory:01M1X0ZF1GHG8GMEWTPEF0B3GJ", + "id": "01M1X13F5VCMD2HH8BZH96MW6H", + "kind": "memory", + "score": 0.9904396533966064, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 918.2633999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1435, + "mcp_result_bytes": 1534, + "wire_bytes": 1571, + "reported_used_tokens": 1534, + "working_set_bytes": 290848768, + "peak_working_set_bytes": 291766272 + }, + { + "query": "when is it safe to use --no-verify on git commit?", + "ranked": [ + "git-hooks-bypass", + "git-reflog-rescue" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZGHHZAJR5358GXDKWH4V", + "id": "01M1X13G33FGP9JC9CQBGYXK2P", + "kind": "memory", + "score": 0.9956986904144288, + "summary": "project:fact - [2026-09-07] [tags: git hooks bypass pre-commit skip] `git commit --no-verify` skips ALL hooks (pre-commit and commit-msg). Never use this in shared team repos where hooks enforce quality gates (lint, tests, memory harvest). Instead, fix the failing hook." + }, + { + "expansion_handle": "memory:01M1X0ZGNBH8WYWRWD7YH8MQZF", + "id": "01M1X13G33EER37H5NZY1D6B4V", + "kind": "memory", + "score": 0.5084817409515381, + "summary": "project:fact - [2026-09-07] [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone \u2014 they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only \u2014 remote reflog is not accessible via normal git commands." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 940.2767, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1193, + "mcp_result_bytes": 1292, + "wire_bytes": 1329, + "reported_used_tokens": 1292, + "working_set_bytes": 290848768, + "peak_working_set_bytes": 291766272 + }, + { + "query": "reduce clone size and bandwidth for server-side repo ingest", + "ranked": [ + "git-sparse-checkout", + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZGJFQE0Y1HS6W2QX7VTW", + "id": "01M1X13H01NRFVYA43270QPXHC", + "kind": "memory", + "score": 0.9969936609268188, + "summary": "project:fact - [tags: git sparse-checkout partial-clone bandwidth] `git sparse-checkout init --cone` combined with `git clone --filter=blob:none` (partial clone) fetches only the commit graph and tree objects, not blobs. Individual blobs are fetched on demand when accessed. This cuts clone time for large repos from minutes to seconds." + }, + { + "expansion_handle": "memory:01M1X0ZEJZP4HRFMXFVX53ZJ2M", + "id": "01M1X13H01RSE6Z07M8FSG9VV1", + "kind": "memory", + "score": 0.8199672698974609, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 958.7007, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1744, + "mcp_result_bytes": 1843, + "wire_bytes": 1880, + "reported_used_tokens": 1843, + "working_set_bytes": 290848768, + "peak_working_set_bytes": 291766272 + }, + { + "query": "spurious diffs from Windows CRLF line ending conversion in git", + "ranked": [ + "git-line-endings-windows" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZGKGCQ06DH0G1SDKT6V3", + "id": "01M1X13HXXSMZ0HWW89VXQQ3HA", + "kind": "memory", + "score": 0.9993343949317932, + "summary": "project:fact - [tags: git line-endings windows crlf autocrlf] On Windows, `core.autocrlf=true` (git's default for Windows installs) converts LF to CRLF on checkout and CRLF to LF on commit. This causes spurious diffs when files are edited on Windows then committed \u2014 the content is identical but the line endings differ in the index vs the working tree. Fix: set `core.autocrlf=false` and `.gitattributes` with `* text=auto eol=lf` for the repo." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 885.9011, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 940, + "reported_used_tokens": 903, + "working_set_bytes": 291131392, + "peak_working_set_bytes": 292036608 + }, + { + "query": "git submodule always gets the wrong commit in CI", + "ranked": [ + "git-submodule-pinning", + "git-hooks-bypass" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZGMDQD13JXXQKCJ6FCJG", + "id": "01M1X13JSSABBG8GQKEHGJQJSD", + "kind": "memory", + "score": 0.9992856383323668, + "summary": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip \u2014 this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version." + }, + { + "expansion_handle": "memory:01M1X0ZGHHZAJR5358GXDKWH4V", + "id": "01M1X13JSSVP1EP3813BE8TNWS", + "kind": "memory", + "score": 0.6295387744903564, + "summary": "project:fact - [tags: git hooks bypass pre-commit skip] `git commit --no-verify` skips ALL hooks (pre-commit and commit-msg). Never use this in shared team repos where hooks enforce quality gates (lint, tests, memory harvest). Instead, fix the failing hook." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 898.4296, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1157, + "mcp_result_bytes": 1256, + "wire_bytes": 1293, + "reported_used_tokens": 1256, + "working_set_bytes": 291135488, + "peak_working_set_bytes": 292048896 + }, + { + "query": "accidentally ran git reset --hard and lost commits \u2014 can I recover?", + "ranked": [ + "git-reflog-rescue" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZGNBH8WYWRWD7YH8MQZF", + "id": "01M1X13KNZKWHJA7APSC587N8X", + "kind": "memory", + "score": 0.9995450377464294, + "summary": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone \u2014 they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only \u2014 remote reflog is not accessible via normal git commands." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 929.4942000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 762, + "mcp_result_bytes": 843, + "wire_bytes": 880, + "reported_used_tokens": 843, + "working_set_bytes": 291135488, + "peak_working_set_bytes": 292048896 + }, + { + "query": "blocking SQLite call from an async tokio handler causes latency spikes", + "ranked": [ + "tokio-blocking-in-async" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZGPA0V0JRMAC9T4Z7Y8Y", + "id": "01M1X13MKFC6KKESCKX5PRV7DV", + "kind": "memory", + "score": 0.9996535778045654, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 894.1754, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 766, + "mcp_result_bytes": 847, + "wire_bytes": 884, + "reported_used_tokens": 847, + "working_set_bytes": 291135488, + "peak_working_set_bytes": 292048896 + }, + { + "query": "Cannot start a runtime from within a runtime in a tokio test", + "ranked": [ + "tokio-runtime-in-tests", + "tokio-blocking-in-async" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZGQ908QPA9R8BQ4WTCEK", + "id": "01M1X13NESMNS104AS2T0X7T3H", + "kind": "memory", + "score": 0.9997126460075378, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + }, + { + "expansion_handle": "memory:01M1X0ZGPA0V0JRMAC9T4Z7Y8Y", + "id": "01M1X13NESP222B95BBBSPM0H6", + "kind": "memory", + "score": 0.5779464840888977, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 830.7276, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1370, + "mcp_result_bytes": 1477, + "wire_bytes": 1514, + "reported_used_tokens": 1477, + "working_set_bytes": 291139584, + "peak_working_set_bytes": 292052992 + }, + { + "query": "tokio select cancels the other branch and loses the value in the channel", + "ranked": [ + "tokio-select-cancellation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZGRCAN29FYJAR5E4QAHR", + "id": "01M1X13P9KZF6K1FPGTPZ5CPRN", + "kind": "memory", + "score": 0.9981033802032472, + "summary": "project:fact - [tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 894.8019, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 751, + "mcp_result_bytes": 832, + "wire_bytes": 869, + "reported_used_tokens": 832, + "working_set_bytes": 291110912, + "peak_working_set_bytes": 292052992 + }, + { + "query": "mpsc channel backpressure causing senders to stall", + "ranked": [ + "tokio-channel-backpressure" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZGSEJECPSEAD436HH8VH", + "id": "01M1X13Q4PE8YV52VWK4WEFGNG", + "kind": "memory", + "score": 0.9999104738235474, + "summary": "project:fact - [tags: tokio mpsc channel backpressure async rust] `tokio::sync::mpsc::channel(N)` with a bounded buffer provides backpressure: senders block when the buffer is full. This prevents unbounded memory growth but can cause sender tasks to stall. Choosing N: too small causes frequent backpressure (throughput drops); too large defeats the purpose." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 937.4644, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 733, + "mcp_result_bytes": 814, + "wire_bytes": 851, + "reported_used_tokens": 814, + "working_set_bytes": 291110912, + "peak_working_set_bytes": 292052992 + }, + { + "query": "overhead from calling spawn_blocking on every single query request", + "ranked": [ + "tokio-spawn-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZGTCAKDQT2RZ67DG352J", + "id": "01M1X13R1ZK1KJX1DKZ243P2ZW", + "kind": "memory", + "score": 0.9961729645729064, + "summary": "project:fact - [tags: tokio spawn_blocking thread-pool rust blocking] `tokio::task::spawn_blocking` places work on a dedicated blocking thread pool (default up to 512 threads, configurable via `Builder::max_blocking_threads`). Each call creates or reuses a thread \u2014 there's no true pooling, threads may be created on demand. For many short-duration blocking calls (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 885.7108000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 746, + "mcp_result_bytes": 827, + "wire_bytes": 864, + "reported_used_tokens": 827, + "working_set_bytes": 291123200, + "peak_working_set_bytes": 292052992 + }, + { + "query": "axum server panics during shutdown because the DB pool is already closed", + "ranked": [ + "tokio-shutdown-ordering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZH2GTRNMS5E25B7MJKN7", + "id": "01M1X13RXWR117PYCG2RB5NF01", + "kind": "memory", + "score": 0.98052579164505, + "summary": "project:fact - [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries \u2014 the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 897.4758, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 931, + "mcp_result_bytes": 1012, + "wire_bytes": 1049, + "reported_used_tokens": 1012, + "working_set_bytes": 291127296, + "peak_working_set_bytes": 292052992 + }, + { + "query": "reqwest Client created per-request defeats connection pooling", + "ranked": [ + "http-connection-pooling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZH3JYTK5F1DCRK5A7CSK", + "id": "01M1X13SSWPA4E1RAQQ6P4CEK6", + "kind": "memory", + "score": 0.9998082518577576, + "summary": "project:fact - [tags: http reqwest connection-pool keep-alive rust] reqwest's `Client` holds a connection pool; always create ONE `Client` instance and clone it for each handler \u2014 cloning is cheap (Arc under the hood). Creating a `Client::new()` per request defeats connection pooling and causes TCP connection exhaustion under load. The default pool settings: max_idle_per_host=usize::MAX (unbounded), idle_timeout=90s." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 847.5688, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 797, + "mcp_result_bytes": 878, + "wire_bytes": 915, + "reported_used_tokens": 878, + "working_set_bytes": 291131392, + "peak_working_set_bytes": 292052992 + }, + { + "query": "LLM request times out during streaming \u2014 which timeout setting applies?", + "ranked": [ + "http-timeout-layering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZH4QPHW3QK85J2GV7QZ3", + "id": "01M1X13TMQYVMS78WWKNA5SAKR", + "kind": "memory", + "score": 0.9987107515335084, + "summary": "project:fact - [tags: http reqwest timeout connect read total rust] reqwest has three distinct timeout knobs: `connect_timeout`, `read_timeout`, and `timeout` (total). They compose: if all three are set, the request fails at whichever fires first. For LLM API calls with streaming responses, `read_timeout` must be larger than the slowest expected token (often 30-60s) while `connect_timeout` can be tight (3-5s)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 783.3842, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 788, + "mcp_result_bytes": 869, + "wire_bytes": 906, + "reported_used_tokens": 869, + "working_set_bytes": 291135488, + "peak_working_set_bytes": 292052992 + }, + { + "query": "how do I safely retry a POST to the LLM API without creating duplicates?", + "ranked": [ + "http-retry-idempotency" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZH5SN9GVNQ9Z0WWPXCRG", + "id": "01M1X13VD0N709TXV62KK4RGW5", + "kind": "memory", + "score": 0.9995805621147156, + "summary": "project:fact - [tags: http retry idempotency post put reqwest] Only retry idempotent requests automatically. GET, HEAD, PUT, DELETE are idempotent. POST is NOT \u2014 retrying a POST may create duplicate resources." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 931.1876, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 585, + "mcp_result_bytes": 666, + "wire_bytes": 703, + "reported_used_tokens": 666, + "working_set_bytes": 291143680, + "peak_working_set_bytes": 292061184 + }, + { + "query": "custom enterprise root CA not trusted by rustls on Windows", + "ranked": [ + "http-tls-roots", + "http-proxy-env" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZH6WBZRTDSF50F1ZVCNQ", + "id": "01M1X13WAP165RQ78B8MA6QJR5", + "kind": "memory", + "score": 0.9998220801353456, + "summary": "project:fact - [tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle \u2014 the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle." + }, + { + "expansion_handle": "memory:01M1X0ZH91SP4SET6FWT8P2FVR", + "id": "01M1X13WAPSDR6JBZ330EV57SF", + "kind": "memory", + "score": 0.38715291023254395, + "summary": "project:fact - [tags: http proxy environment reqwest rust corporate] reqwest respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` environment variables by default (with `default-tls` or `rustls-tls`). In a corporate network, these may redirect traffic through an intercepting proxy that breaks mTLS or adds latency. To disable proxy usage entirely: `reqwest::ClientBuilder::no_proxy()`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 928.3733, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1311, + "mcp_result_bytes": 1410, + "wire_bytes": 1447, + "reported_used_tokens": 1410, + "working_set_bytes": 291143680, + "peak_working_set_bytes": 292061184 + }, + { + "query": "parsing server-sent events when a single TCP chunk contains a partial SSE frame", + "ranked": [ + "http-streaming-bodies" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZH7WABMDE31F3K2K6K3E", + "id": "01M1X13X73RQK3NATK103T0M96", + "kind": "memory", + "score": 0.9667426943778992, + "summary": "project:fact - [2026-09-07] [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding \u2014 a chunk may split across frame boundaries." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 832.727, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 859, + "mcp_result_bytes": 940, + "wire_bytes": 977, + "reported_used_tokens": 940, + "working_set_bytes": 291188736, + "peak_working_set_bytes": 292102144 + }, + { + "query": "reqwest does not use the system proxy settings on Windows", + "ranked": [ + "http-proxy-env", + "http-tls-roots", + "http-connection-pooling", + "http-streaming-bodies" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZH91SP4SET6FWT8P2FVR", + "id": "01M1X13Y0X1CDSXT1N6ZYJT6Z4", + "kind": "memory", + "score": 0.9997830986976624, + "summary": "project:fact - [tags: http proxy environment reqwest rust corporate] reqwest respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` environment variables by default (with `default-tls` or `rustls-tls`). In a corporate network, these may redirect traffic through an intercepting proxy that breaks mTLS or adds latency. To disable proxy usage entirely: `reqwest::ClientBuilder::no_proxy()`." + }, + { + "expansion_handle": "memory:01M1X0ZH6WBZRTDSF50F1ZVCNQ", + "id": "01M1X13Y0X60TMQ5V4VC1VDCRK", + "kind": "memory", + "score": 0.9808586239814758, + "summary": "project:fact - [tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle \u2014 the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle." + }, + { + "expansion_handle": "memory:01M1X0ZH3JYTK5F1DCRK5A7CSK", + "id": "01M1X13Y0XY2YC839ZS0E0DPSW", + "kind": "memory", + "score": 0.719273030757904, + "summary": "project:fact - [tags: http reqwest connection-pool keep-alive rust] reqwest's `Client` holds a connection pool; always create ONE `Client` instance and clone it for each handler \u2014 cloning is cheap (Arc under the hood). Creating a `Client::new()` per request defeats connection pooling and causes TCP connection exhaustion under load. The default pool settings: max_idle_per_host=usize::MAX (unbounded), idle_timeout=90s." + }, + { + "expansion_handle": "memory:01M1X0ZH7WABMDE31F3K2K6K3E", + "id": "01M1X13Y0XQM8ZDDGGCYSF7ZE1", + "kind": "memory", + "score": 0.7009692192077637, + "summary": "project:fact - [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding \u2014 a chunk may split across frame boundaries." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 890.2808, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2497, + "mcp_result_bytes": 2632, + "wire_bytes": 2669, + "reported_used_tokens": 2632, + "working_set_bytes": 291217408, + "peak_working_set_bytes": 292126720 + }, + { + "query": "insta snapshot tests fail in CI because output includes a timestamp", + "ranked": [ + "testing-snapshot-churn", + "ci-flaky-quarantine" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZHA2CN8JFZY83X5EE0H1", + "id": "01M1X13YWX62HEQP71SK65ZXMM", + "kind": "memory", + "score": 0.999855637550354, + "summary": "project:fact - [tags: testing snapshot insta assert churn rust] Snapshot tests (e.g. with the `insta` crate) fail whenever the output changes, even for intended changes. In CI, they fail loudly; locally, `cargo insta review` walks you through accepting or rejecting changes." + }, + { + "expansion_handle": "memory:01M1X0ZJ55N7K4YMPJ81M5YN0V", + "id": "01M1X13YWYWKP01EKD1W4SJ48C", + "kind": "memory", + "score": 0.5997360348701477, + "summary": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal \u2014 a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 808.1075, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1196, + "mcp_result_bytes": 1295, + "wire_bytes": 1332, + "reported_used_tokens": 1295, + "working_set_bytes": 291340288, + "peak_working_set_bytes": 292253696 + }, + { + "query": "two test workers writing to the same temp directory path race each other", + "ranked": [ + "testing-temp-dirs-ci" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZHB5SS7Y7DPND7PN1DX1", + "id": "01M1X13ZP8VGJVQP2M8CMMH636", + "kind": "memory", + "score": 0.9889234900474548, + "summary": "project:fact - [tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 866.2689, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 755, + "mcp_result_bytes": 836, + "wire_bytes": 873, + "reported_used_tokens": 836, + "working_set_bytes": 291340288, + "peak_working_set_bytes": 292253696 + }, + { + "query": "test passes locally but fails on a slow CI runner due to a 100ms sleep", + "ranked": [ + "testing-time-dependent-flakes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZHCBWE77ZQJC1D1ZXCX2", + "id": "01M1X140HDNJZKMT6ER0RT23T2", + "kind": "memory", + "score": 0.808289110660553, + "summary": "project:fact - [tags: testing time flaky clock mock rust] Tests that depend on wall-clock time are inherently flaky under load (slow CI runners, GC pauses). Abstract time behind a trait (`Clock: Fn() -> SystemTime`) injected at construction, and supply a fake in tests. For tests checking that something happened \"within N seconds\", use a generous multiple of the expected duration (10x is not unreasonable for CI)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 922.3117, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 790, + "mcp_result_bytes": 875, + "wire_bytes": 912, + "reported_used_tokens": 875, + "working_set_bytes": 291348480, + "peak_working_set_bytes": 292270080 + }, + { + "query": "proptest found a hash collision in text normalization that example tests missed", + "ranked": [ + "testing-property-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZHDBKSBXCYRPMER5RHZM", + "id": "01M1X141E4AJSDY5E8JEY92A52", + "kind": "memory", + "score": 0.9994783997535706, + "summary": "project:fact - [tags: testing property-based proptest quickcheck rust] Property-based tests (proptest, quickcheck) find edge cases that example-based tests miss. For kimetsu's memory text normalization, proptest found that zero-width joiner characters and right-to-left marks caused hash collisions. Run proptest with `PROPTEST_CASES=10000` in CI for thorough coverage." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 916.8739, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 744, + "mcp_result_bytes": 825, + "wire_bytes": 862, + "reported_used_tokens": 825, + "working_set_bytes": 291479552, + "peak_working_set_bytes": 292392960 + }, + { + "query": "set_var in tests races when cargo test runs them in parallel", + "ranked": [ + "testing-serial-vs-parallel" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZHEBS8TQMPSTT182J1MD", + "id": "01M1X142B1386CA7VGF5AW3JER", + "kind": "memory", + "score": 0.9997344613075256, + "summary": "project:fact - [2026-09-07] [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 901.1618, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 832, + "mcp_result_bytes": 913, + "wire_bytes": 950, + "reported_used_tokens": 913, + "working_set_bytes": 291483648, + "peak_working_set_bytes": 292405248 + }, + { + "query": "hardcoded JSON fixtures broke after a schema migration", + "ranked": [ + "testing-fixture-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZHJ7V8BRWKTYR18X6ACN", + "id": "01M1X1437527MFX41K7F9XHQS2", + "kind": "memory", + "score": 0.9998371601104736, + "summary": "project:fact - [2026-09-07] [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 750.6186, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 783, + "mcp_result_bytes": 864, + "wire_bytes": 901, + "reported_used_tokens": 864, + "working_set_bytes": 291491840, + "peak_working_set_bytes": 292405248 + }, + { + "query": "debug print in the MCP handler corrupts the JSON-Lines protocol stream", + "ranked": [ + "mcp-stdout-protocol" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZHK46P88A6EV49JZ72Z3", + "id": "01M1X143YZR6NJZ7ASE853N6P0", + "kind": "memory", + "score": 0.9997472167015076, + "summary": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 927.4448, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 705, + "mcp_result_bytes": 786, + "wire_bytes": 823, + "reported_used_tokens": 786, + "working_set_bytes": 291713024, + "peak_working_set_bytes": 292626432 + }, + { + "query": "kimetsu MCP tool call times out because embedding model is re-initialized every call", + "ranked": [ + "mcp-tool-timeouts", + "mcp-schema-validation", + "kimetsu-bench-remote-embedder-singleton" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZHM6HQ7BES1FK6GH1PFD", + "id": "01M1X144VMS780VHTYXPE0KSGW", + "kind": "memory", + "score": 0.9995898604393004, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + }, + { + "expansion_handle": "memory:01M1X0ZHP9P7ZB2J139BG1P91M", + "id": "01M1X144VMQF4BWP0Q962D1DJV", + "kind": "memory", + "score": 0.6027993559837341, + "summary": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array \u2014 omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error." + }, + { + "expansion_handle": "memory:01M1X0ZJH6EYMQ5H2CB23ZRSY2", + "id": "01M1X144VMHW4C0Q8A9PFPFQPC", + "kind": "memory", + "score": 0.5117799639701843, + "summary": "project:fact - [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 814.1543999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2085, + "mcp_result_bytes": 2202, + "wire_bytes": 2239, + "reported_used_tokens": 2202, + "working_set_bytes": 291770368, + "peak_working_set_bytes": 292683776 + }, + { + "query": "env var set after host launch is not visible to the MCP server process", + "ranked": [ + "mcp-env-propagation", + "kimetsu-daemon-lifecycle" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZHN8D90ZGFZWMSQQJ0A4", + "id": "01M1X145NGQ3EAG6845RZBD0RK", + "kind": "memory", + "score": 0.9984827637672424, + "summary": "project:fact - [2026-09-07] [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment \u2014 changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate." + }, + { + "expansion_handle": "memory:01M1X0ZJ67E5EZD48V1ZXX3EDF", + "id": "01M1X145NG32NMJ78MMHMYEH9P", + "kind": "memory", + "score": 0.9977922439575196, + "summary": "project:fact - [2026-09-07] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 954.9965, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1267, + "mcp_result_bytes": 1366, + "wire_bytes": 1403, + "reported_used_tokens": 1366, + "working_set_bytes": 291807232, + "peak_working_set_bytes": 292728832 + }, + { + "query": "MCP tool call fails because a required field is missing from the JSON input", + "ranked": [ + "mcp-schema-validation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZHP9P7ZB2J139BG1P91M", + "id": "01M1X146JSGQ4NWCRKM7YFDPNB", + "kind": "memory", + "score": 0.998538613319397, + "summary": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array \u2014 omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 887.3282, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 798, + "mcp_result_bytes": 879, + "wire_bytes": 916, + "reported_used_tokens": 879, + "working_set_bytes": 291835904, + "peak_working_set_bytes": 292749312 + }, + { + "query": "Claude Code rejects the tool name with a hyphen in it", + "ranked": [ + "mcp-tool-naming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZHQ906QPDZRG8VM07EPH", + "id": "01M1X147EG0RVG6K1BHN5V2ER5", + "kind": "memory", + "score": 0.9982439279556274, + "summary": "project:fact - [tags: mcp tool naming convention kimetsu] MCP tool names must be valid identifiers for all host agents. Claude Code restricts tool names to `[a-zA-Z0-9_-]` and max 64 chars. Use `snake_case` (kimetsu_brain_context, kimetsu_brain_record) \u2014 hyphen is technically allowed but some hosts reject it." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 854.4791, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 687, + "mcp_result_bytes": 768, + "wire_bytes": 805, + "reported_used_tokens": 768, + "working_set_bytes": 291926016, + "peak_working_set_bytes": 292839424 + }, + { + "query": "MCP response path uses backslashes and the host rejects it", + "ranked": [ + "mcp-transcript-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZHRAJ8GZPHGE2F9AYJSV", + "id": "01M1X1489DM0GD2A3S078F2QXZ", + "kind": "memory", + "score": 0.9984637498855592, + "summary": "project:fact - [tags: mcp transcript paths kimetsu hooks runs] kimetsu writes run transcripts to `/.kimetsu/runs//`. The post-session hook reads the latest run's transcript to trigger memory harvest. On Windows, the path uses backslashes internally but the MCP JSON must use forward slashes or the host may reject path-type arguments." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 923.0840000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 724, + "mcp_result_bytes": 805, + "wire_bytes": 842, + "reported_used_tokens": 805, + "working_set_bytes": 291995648, + "peak_working_set_bytes": 292904960 + }, + { + "query": "AWS credentials not found \u2014 which env var does kimetsu read for Bedrock?", + "ranked": [ + "aws-credentials-chain", + "aws-region-resolution", + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZHSCPRWJGM9PSEB6ZM8G", + "id": "01M1X1496SZBZQJQRHEXD57GQQ", + "kind": "memory", + "score": 0.9990235567092896, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + }, + { + "expansion_handle": "memory:01M1X0ZHTNDF0DYAMM67R34CKC", + "id": "01M1X1496SCCFGRZS2GQPK7TKW", + "kind": "memory", + "score": 0.9968422651290894, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X0ZERMERMXX9NA2KTGPZDG", + "id": "01M1X1496SKRRS7E3S9TQWQJEZ", + "kind": "memory", + "score": 0.9849756360054016, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X0ZEYPBW1N1TY0KVBGW2GN", + "id": "01M1X1496SMZVY7CQR8YF7E47B", + "kind": "memory", + "score": 0.9203452467918396, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 951.8003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3455, + "mcp_result_bytes": 3618, + "wire_bytes": 3655, + "reported_used_tokens": 3618, + "working_set_bytes": 292024320, + "peak_working_set_bytes": 292937728 + }, + { + "query": "Bedrock InvokeModel fails because the region is not configured", + "ranked": [ + "aws-region-resolution", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZHTNDF0DYAMM67R34CKC", + "id": "01M1X14A40AGYXYP0BJZ0SGP7Q", + "kind": "memory", + "score": 0.99688321352005, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X0ZEYPBW1N1TY0KVBGW2GN", + "id": "01M1X14A40JKK1NBHBQ6KB9K8C", + "kind": "memory", + "score": 0.6450709104537964, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 940.9031, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1810, + "mcp_result_bytes": 1929, + "wire_bytes": 1966, + "reported_used_tokens": 1929, + "working_set_bytes": 292024320, + "peak_working_set_bytes": 292941824 + }, + { + "query": "how do I handle ThrottlingException from Bedrock with exponential backoff?", + "ranked": [ + "aws-retry-throttling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZHVNBH39VH86ETTXRK4M", + "id": "01M1X14B1H3K8Z3JNKX244WVA5", + "kind": "memory", + "score": 0.9997082352638244, + "summary": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with \u00b125% jitter." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 968.9115, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 771, + "mcp_result_bytes": 868, + "wire_bytes": 905, + "reported_used_tokens": 868, + "working_set_bytes": 292052992, + "peak_working_set_bytes": 292954112 + }, + { + "query": "generating a presigned S3 URL for brain export without exposing credentials", + "ranked": [ + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZHWS1G2E6ER6NHTRVEWK", + "id": "01M1X14C02J9CNE4D3PZ1SW08M", + "kind": "memory", + "score": 0.9990487694740297, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 924.1211000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 875, + "mcp_result_bytes": 956, + "wire_bytes": 993, + "reported_used_tokens": 956, + "working_set_bytes": 292474880, + "peak_working_set_bytes": 293388288 + }, + { + "query": "IMDSv2 token required for instance metadata \u2014 PUT before GET", + "ranked": [ + "aws-instance-metadata" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZHXVE9GF8BNETQ3ZEZ5E", + "id": "01M1X14CWKK1Q6SK7W4F77CMW9", + "kind": "memory", + "score": 0.9997182488441468, + "summary": "project:fact - [2026-09-07] [tags: aws imds instance-metadata ec2 token] The AWS Instance Metadata Service v2 (IMDSv2) requires a session token: PUT `http://169.254.169.254/latest/api/token` with `X-aws-ec2-metadata-token-ttl-seconds: 21600` to get a token, then GET metadata with `X-aws-ec2-metadata-token: `. IMDSv1 (no token) is disabled on hardened instances. The metadata endpoint is only reachable from within EC2 \u2014 a connection timeout means you're not on EC2." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 908.2138, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 851, + "mcp_result_bytes": 932, + "wire_bytes": 969, + "reported_used_tokens": 932, + "working_set_bytes": 292671488, + "peak_working_set_bytes": 293584896 + }, + { + "query": "Cargo cache key strategy for GitHub Actions to avoid toolchain version collisions", + "ranked": [ + "ci-cache-keys" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZJ19CVXD15YG01JTKR5P", + "id": "01M1X14DSFQX7QMVE7DQ81RD7M", + "kind": "memory", + "score": 0.998869240283966, + "summary": "project:fact - [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key \u2014 macOS and Windows have incompatible artifact formats." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 952.5517, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 788, + "mcp_result_bytes": 869, + "wire_bytes": 906, + "reported_used_tokens": 869, + "working_set_bytes": 292724736, + "peak_working_set_bytes": 293638144 + }, + { + "query": "CI matrix has 18 jobs and costs too much \u2014 how do I reduce it?", + "ranked": [ + "ci-matrix-explosion" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZJ278YVFXK2KZ08VNJN4", + "id": "01M1X14EPSP6VZQ017KS1GZTXQ", + "kind": "memory", + "score": 0.999057948589325, + "summary": "project:fact - [tags: ci github-actions matrix jobs resources] A CI matrix combining OS (3) x Rust toolchain (3) x features (2) = 18 jobs. Each spawns a runner; at $0.008/min for Ubuntu and $0.016/min for Windows, a 10-minute build costs $2.40 per push. Reduce: test the full matrix only on PRs to main; on feature branches, test only Linux+stable." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 930.5550000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 722, + "mcp_result_bytes": 803, + "wire_bytes": 840, + "reported_used_tokens": 803, + "working_set_bytes": 292728832, + "peak_working_set_bytes": 293646336 + }, + { + "query": "GitHub Actions secret accidentally printed in build logs", + "ranked": [ + "ci-secrets-masking", + "ci-cache-keys", + "ci-artifact-retention" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZJ36VASSAJBJPDTPC7ZQ", + "id": "01M1X14FKWYNHPJAZWWP6Z623F", + "kind": "memory", + "score": 0.9963951706886292, + "summary": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output \u2014 but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable." + }, + { + "expansion_handle": "memory:01M1X0ZJ19CVXD15YG01JTKR5P", + "id": "01M1X14FKWA3958MVMKC47B3F0", + "kind": "memory", + "score": 0.4342843890190125, + "summary": "project:fact - [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key \u2014 macOS and Windows have incompatible artifact formats." + }, + { + "expansion_handle": "memory:01M1X0ZJ4844PG8J2NPN9YS65P", + "id": "01M1X14FKWJHVCRAZJQ4RC4WJG", + "kind": "memory", + "score": 0.3422144949436188, + "summary": "project:fact - [tags: ci github-actions artifacts retention benchmark] GitHub Actions artifacts are retained for 90 days (default). For benchmark results, use `actions/upload-artifact` with `retention-days: 365` for long-term tracking. The free tier has 500MB storage \u2014 per-combo JSON files from kimetsu bench (each ~60KB) add up fast if you upload them on every push." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 888.1341, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1791, + "mcp_result_bytes": 1908, + "wire_bytes": 1945, + "reported_used_tokens": 1908, + "working_set_bytes": 292757504, + "peak_working_set_bytes": 293670912 + }, + { + "query": "how long do GitHub Actions artifacts persist and what's the storage limit?", + "ranked": [ + "ci-artifact-retention" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZJ4844PG8J2NPN9YS65P", + "id": "01M1X14GFMW341SSNWPP0NS5QM", + "kind": "memory", + "score": 0.999624252319336, + "summary": "project:fact - [tags: ci github-actions artifacts retention benchmark] GitHub Actions artifacts are retained for 90 days (default). For benchmark results, use `actions/upload-artifact` with `retention-days: 365` for long-term tracking. The free tier has 500MB storage \u2014 per-combo JSON files from kimetsu bench (each ~60KB) add up fast if you upload them on every push." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 962.9290000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 744, + "mcp_result_bytes": 825, + "wire_bytes": 862, + "reported_used_tokens": 825, + "working_set_bytes": 292761600, + "peak_working_set_bytes": 293679104 + }, + { + "query": "timing-based test flake in CI \u2014 quarantine or fix?", + "ranked": [ + "ci-flaky-quarantine", + "testing-time-dependent-flakes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZJ55N7K4YMPJ81M5YN0V", + "id": "01M1X14HDXMR1GZA0583D8MAS6", + "kind": "memory", + "score": 0.9994743466377258, + "summary": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal \u2014 a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output." + }, + { + "expansion_handle": "memory:01M1X0ZHCBWE77ZQJC1D1ZXCX2", + "id": "01M1X14HDXVJGBXNW7PC5RD4W0", + "kind": "memory", + "score": 0.9849997162818908, + "summary": "project:fact - [tags: testing time flaky clock mock rust] Tests that depend on wall-clock time are inherently flaky under load (slow CI runners, GC pauses). Abstract time behind a trait (`Clock: Fn() -> SystemTime`) injected at construction, and supply a fake in tests. For tests checking that something happened \"within N seconds\", use a generous multiple of the expected duration (10x is not unreasonable for CI)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 908.3561, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1340, + "mcp_result_bytes": 1443, + "wire_bytes": 1480, + "reported_used_tokens": 1443, + "working_set_bytes": 292773888, + "peak_working_set_bytes": 293695488 + }, + { + "query": "kimetsu doctor says the MCP server is running \u2014 how do I stop it before an update?", + "ranked": [ + "kimetsu-daemon-lifecycle", + "mcp-env-propagation", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZJ67E5EZD48V1ZXX3EDF", + "id": "01M1X14JA8VHT11VG5ZN504DYP", + "kind": "memory", + "score": 0.9989782571792604, + "summary": "project:fact - [2026-09-07] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1X0ZHN8D90ZGFZWMSQQJ0A4", + "id": "01M1X14JA8H52XG91P14E3RB6C", + "kind": "memory", + "score": 0.9049031734466552, + "summary": "project:fact - [2026-09-07] [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment \u2014 changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate." + }, + { + "expansion_handle": "memory:01M1X0ZEN209JTV53G9N189X79", + "id": "01M1X14JA8SR7R2BCT1WBD5D5G", + "kind": "memory", + "score": 0.4812128245830536, + "summary": "project:fact - [2026-09-07] [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 937.4522, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2046, + "mcp_result_bytes": 2211, + "wire_bytes": 2248, + "reported_used_tokens": 2211, + "working_set_bytes": 292782080, + "peak_working_set_bytes": 293695488 + }, + { + "query": "noise capsules consuming token budget without contributing retrieval signal", + "ranked": [ + "kimetsu-capsule-budgets" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZJ77B838M293BJYVZABG", + "id": "01M1X14K7B2NQ8CRF7G84WDQBP", + "kind": "memory", + "score": 0.9997420907020568, + "summary": "project:fact - [tags: kimetsu capsule tokens budget retrieval] kimetsu retrieval enforces a token budget per capsule type: memory capsules are capped at 6000 tokens total (across all retrieved memories), file capsules at 3000 tokens. When a memory is large and would exceed the budget, it is truncated at a sentence boundary. The budget is enforced AFTER reranking \u2014 reranking may reorder results so that a truncated high-ranked memory displaces a full lower-ranked one." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 765.3433, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 847, + "mcp_result_bytes": 928, + "wire_bytes": 965, + "reported_used_tokens": 928, + "working_set_bytes": 292790272, + "peak_working_set_bytes": 293703680 + }, + { + "query": "kimetsu_brain_record writes to the wrong brain location \u2014 user vs project scope", + "ranked": [ + "kimetsu-memory-scopes", + "kimetsu-write-tools-gate", + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZJ838F1BR8VTV7ERMJB7", + "id": "01M1X14KZPMJ307JZD9KKX10A4", + "kind": "memory", + "score": 0.999030828475952, + "summary": "project:fact - [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available \u2014 if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope." + }, + { + "expansion_handle": "memory:01M1X0ZJB78MBB69JA7ZTYBJNG", + "id": "01M1X14KZPG4RCKNVQYMD2MP1P", + "kind": "memory", + "score": 0.9838979840278624, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level \u2014 disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1X0ZF1GHG8GMEWTPEF0B3GJ", + "id": "01M1X14KZPKYMPBCB2SZZACJGF", + "kind": "memory", + "score": 0.3852712512016296, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 900.1895, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2098, + "mcp_result_bytes": 2215, + "wire_bytes": 2252, + "reported_used_tokens": 2215, + "working_set_bytes": 292790272, + "peak_working_set_bytes": 293703680 + }, + { + "query": "how do I configure kimetsu to use Claude Haiku for harvesting but Opus for the agent?", + "ranked": [ + "kimetsu-distiller-config" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZJ95AG09ZC025VQY3SYJ", + "id": "01M1X14MVNR832MCCN04P5QM8K", + "kind": "memory", + "score": 0.9989088773727416, + "summary": "project:fact - [tags: kimetsu distiller harvest config provider] The kimetsu distiller (auto-harvester) uses a SEPARATE provider configuration from the main agent: `distiller.provider`, `distiller.model`, `distiller.api_key`. This allows running the agent on an expensive model (Claude Opus) while harvesting with a cheap model (Claude Haiku). If `distiller.provider` is not set, it inherits `provider`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 918.3923000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 778, + "mcp_result_bytes": 859, + "wire_bytes": 896, + "reported_used_tokens": 859, + "working_set_bytes": 292790272, + "peak_working_set_bytes": 293703680 + }, + { + "query": "first agent turn is slow because kimetsu proactive hook runs embedding inference", + "ranked": [ + "kimetsu-proactive-hooks", + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZJA7VJPYZXDWE76RW90E", + "id": "01M1X14NR8Z20280JYJV1SFQKF", + "kind": "memory", + "score": 0.999568521976471, + "summary": "project:fact - [2026-09-07] [tags: kimetsu proactive hooks context injection] kimetsu's proactive context injection runs before each agent turn (pre-turn hook) and injects relevant memories into the system prompt prefix. The hook invocation adds latency to the first token: embedding inference + vector search + reranking + context formatting. On a cold start, this can be 1-3 seconds." + }, + { + "expansion_handle": "memory:01M1X0ZHM6HQ7BES1FK6GH1PFD", + "id": "01M1X14NR8DQ99GN7HKMTW3N9D", + "kind": "memory", + "score": 0.9405298233032228, + "summary": "project:fact - [2026-09-07] [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 941.4086000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1403, + "mcp_result_bytes": 1502, + "wire_bytes": 1539, + "reported_used_tokens": 1502, + "working_set_bytes": 292798464, + "peak_working_set_bytes": 293711872 + }, + { + "query": "make the kimetsu brain read-only for certain repos on a shared remote server", + "ranked": [ + "kimetsu-write-tools-gate", + "remote-ingest-split-roots", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZJB78MBB69JA7ZTYBJNG", + "id": "01M1X14PNWA31KFV0EPYVNSVF2", + "kind": "memory", + "score": 0.997682809829712, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level \u2014 disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1X0ZEJZP4HRFMXFVX53ZJ2M", + "id": "01M1X14PNWB40P58D91GQSVV5K", + "kind": "memory", + "score": 0.9957050681114196, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1X0ZEN209JTV53G9N189X79", + "id": "01M1X14PNW9A5MB0JJYMZEPMGH", + "kind": "memory", + "score": 0.9909282326698304, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 888.9683, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2725, + "mcp_result_bytes": 2890, + "wire_bytes": 2927, + "reported_used_tokens": 2890, + "working_set_bytes": 292798464, + "peak_working_set_bytes": 293711872 + }, + { + "query": "kimetsu FTS search misses 'deadlocking' when memory says 'deadlock'", + "ranked": [ + "kimetsu-query-stemming", + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZJF4CV4GMMY9RCT11XHB", + "id": "01M1X14QHJZHY08Q3KE88EJDAY", + "kind": "memory", + "score": 0.9904030561447144, + "summary": "project:fact - [2026-09-07] [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression." + }, + { + "expansion_handle": "memory:01M1X0ZEHVJGD07YGEDZ14AVMV", + "id": "01M1X14QHJXC0CM3B35SFVNE44", + "kind": "memory", + "score": 0.91664320230484, + "summary": "project:fact - [2026-09-07] [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure \u2014 `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 866.1037, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1363, + "mcp_result_bytes": 1478, + "wire_bytes": 1515, + "reported_used_tokens": 1478, + "working_set_bytes": 292798464, + "peak_working_set_bytes": 293711872 + }, + { + "query": "how does pool size affect retrieval recall and latency in the bench?", + "ranked": [ + "kimetsu-rerank-pool" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZJG6K4EX4N7DGGV6B6NS", + "id": "01M1X14RCV14EWGEPR2TJ5TJ7G", + "kind": "memory", + "score": 0.9998373985290528, + "summary": "project:fact - [tags: kimetsu reranker pool size ann retrieval] kimetsu's retrieval pipeline: ANN (approximate nearest neighbor) retrieves a pool of candidates, then the reranker reorders them, then the top-K are returned. The pool size (default 6 for production, 12 in bench) controls the recall-latency tradeoff: larger pool = higher recall = more reranker calls = more latency. For the jina-tiny reranker, pool 12 adds ~80ms vs pool 6." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 901.9145, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 813, + "mcp_result_bytes": 894, + "wire_bytes": 931, + "reported_used_tokens": 894, + "working_set_bytes": 292798464, + "peak_working_set_bytes": 293715968 + }, + { + "query": "second embedder in a remote bench run gets worse results than the first", + "ranked": [ + "kimetsu-bench-remote-embedder-singleton" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZJH6EYMQ5H2CB23ZRSY2", + "id": "01M1X14S8VEPPYXAVY3VJGMD2R", + "kind": "memory", + "score": 0.9939629435539246, + "summary": "project:fact - [2026-09-07] [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 944.8769, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 895, + "mcp_result_bytes": 976, + "wire_bytes": 1013, + "reported_used_tokens": 976, + "working_set_bytes": 292798464, + "peak_working_set_bytes": 293715968 + }, + { + "query": "what is the expected JSON schema for kimetsu brain bench dataset files?", + "ranked": [ + "kimetsu-eval-fixture-shape", + "testing-fixture-drift", + "kimetsu-mrr-metric", + "mcp-schema-validation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZJJ71P374MV9AEP4YS28", + "id": "01M1X14T6GR425EHA6901RT950", + "kind": "memory", + "score": 0.9996767044067384, + "summary": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` \u2014 a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases)." + }, + { + "expansion_handle": "memory:01M1X0ZHJ7V8BRWKTYR18X6ACN", + "id": "01M1X14T6G2EJ1V9QBKHKTMKA4", + "kind": "memory", + "score": 0.9682880640029908, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + }, + { + "expansion_handle": "memory:01M1X0ZJKCP6EMVFNJJTBAXD3M", + "id": "01M1X14T6G3V4GB3AB7W1MRVRV", + "kind": "memory", + "score": 0.8818408250808716, + "summary": "project:fact - [tags: kimetsu bench mrr recall metrics evaluation] kimetsu bench reports MRR (Mean Reciprocal Rank) and Recall@K. MRR is 1/rank_of_first_relevant_result, averaged across cases; it penalizes models that rank the correct answer 2nd or 3rd. Recall@K is the fraction of cases where at least one relevant answer appears in the top K." + }, + { + "expansion_handle": "memory:01M1X0ZHP9P7ZB2J139BG1P91M", + "id": "01M1X14T6G46S6NMRAX5X157W7", + "kind": "memory", + "score": 0.6527947187423706, + "summary": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array \u2014 omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 916.8209, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2424, + "mcp_result_bytes": 2603, + "wire_bytes": 2640, + "reported_used_tokens": 2603, + "working_set_bytes": 292798464, + "peak_working_set_bytes": 293715968 + }, + { + "query": "what does MRR mean and how do I interpret a 0.01 difference between combos?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 953.7112, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 228, + "mcp_result_bytes": 291, + "wire_bytes": 328, + "reported_used_tokens": 291, + "working_set_bytes": 292794368, + "peak_working_set_bytes": 293715968 + }, + { + "query": "SQLITE_BUSY keeps appearing even with WAL mode enabled", + "ranked": [ + "sqlite-busy-timeout-wal", + "sqlite-wal-network-drive" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZFCB0HTB40H3S45W1XS7", + "id": "01M1X14W1N6AJH4JX1CKWC3WXF", + "kind": "memory", + "score": 0.9982662796974182, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + }, + { + "expansion_handle": "memory:01M1X0ZFF44WCC49W67QG1BAQR", + "id": "01M1X14W1N8XHZEE0X7DRMBBJP", + "kind": "memory", + "score": 0.7844027280807495, + "summary": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 964.4465, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1423, + "mcp_result_bytes": 1522, + "wire_bytes": 1559, + "reported_used_tokens": 1522, + "working_set_bytes": 292794368, + "peak_working_set_bytes": 293715968 + }, + { + "query": "my brain file got huge again right after I compacted it", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 882.0556, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 292814848, + "peak_working_set_bytes": 293715968 + }, + { + "query": "all my FTS queries stopped returning results after I changed the tokenizer config", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 911.0307, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 292823040, + "peak_working_set_bytes": 293736448 + }, + { + "query": "something is preventing the kimetsu binary from being replaced during update", + "ranked": [ + "kimetsu-daemon-lifecycle", + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZJ67E5EZD48V1ZXX3EDF", + "id": "01M1X14YQ49V2ZFE429A29QY5Z", + "kind": "memory", + "score": 0.9678457975387572, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1X0ZFA17MW7EDQGRKH93EW9", + "id": "01M1X14YQ52ZZ1R65Y7NASHZAV", + "kind": "memory", + "score": 0.9395453929901124, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 0.6666666666666666, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 838.6941999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1657, + "mcp_result_bytes": 1756, + "wire_bytes": 1793, + "reported_used_tokens": 1756, + "working_set_bytes": 292823040, + "peak_working_set_bytes": 293736448 + }, + { + "query": "tool call results not appearing in the context \u2014 is the semantic floor too high?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 937.3485000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 228, + "mcp_result_bytes": 291, + "wire_bytes": 328, + "reported_used_tokens": 291, + "working_set_bytes": 292823040, + "peak_working_set_bytes": 293744640 + }, + { + "query": "CARGO_INCREMENTAL=0 in CI prevents a class of spurious compilation errors", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZFSK3Z1FRY1DKJNKHT4Y", + "id": "01M1X150EKVS3B9MMFG2P0WYQ3", + "kind": "memory", + "score": 0.7995238304138184, + "summary": "project:fact - [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 888.3365, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 877, + "mcp_result_bytes": 958, + "wire_bytes": 995, + "reported_used_tokens": 958, + "working_set_bytes": 292823040, + "peak_working_set_bytes": 293744640 + }, + { + "query": "how do I check whether my Cargo workspace respects the MSRV constraint?", + "ranked": [ + "cargo-msrv", + "cargo-dev-dep-leak", + "cargo-patch-section", + "cargo-target-dir-sharing" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZFWD6FKV99DDXXHKC0MD", + "id": "01M1X151ADW7WX2JW4DXMET9DR", + "kind": "memory", + "score": 0.9921064376831056, + "summary": "project:fact - [tags: cargo rust msrv edition compatibility] Set `rust-version` in each `Cargo.toml` to declare the minimum supported Rust version (MSRV). Cargo enforces this with `--check`: `cargo check` fails if the toolchain is older than `rust-version`. Keep MSRV as old as your oldest supported deployment target." + }, + { + "expansion_handle": "memory:01M1X0ZFQJ2YCP0MRA63WFP9S4", + "id": "01M1X151ADQH8Z9DTA2QZ2G3MA", + "kind": "memory", + "score": 0.887407660484314, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + }, + { + "expansion_handle": "memory:01M1X0ZFVEAGHAWDDF12PQAM8P", + "id": "01M1X151ADDBTKCB79QW5PC3VG", + "kind": "memory", + "score": 0.7220955491065979, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace \u2014 including transitive deps \u2014 that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1X0ZFRM3VQ2YK91809T077K", + "id": "01M1X151ADKK61AVX0ESDQ6KVM", + "kind": "memory", + "score": 0.4095200598239898, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps \u2014 use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 906.5948000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2682, + "mcp_result_bytes": 2821, + "wire_bytes": 2858, + "reported_used_tokens": 2821, + "working_set_bytes": 292831232, + "peak_working_set_bytes": 293752832 + }, + { + "query": "rusqlite connection opened but ON DELETE CASCADE cascade never fires", + "ranked": [ + "sqlite-foreign-keys-default-off" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZFHZS4JQ8R8W6MPR6CZ9", + "id": "01M1X1527KNBJ2V0KWZXDZNQJ0", + "kind": "memory", + "score": 0.9922945499420166, + "summary": "project:fact - [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting \u2014 every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 909.9094, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 735, + "mcp_result_bytes": 816, + "wire_bytes": 853, + "reported_used_tokens": 816, + "working_set_bytes": 292839424, + "peak_working_set_bytes": 293752832 + }, + { + "query": "I cannot connect to kimetsu-remote \u2014 something about TLS cert validation failed", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 878.4315, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 292847616, + "peak_working_set_bytes": 293765120 + }, + { + "query": "graceful shutdown fails because in-flight SQLite queries are still running when pool closes", + "ranked": [ + "tokio-shutdown-ordering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZH2GTRNMS5E25B7MJKN7", + "id": "01M1X153ZFQYEM0Z4WEREQXX9A", + "kind": "memory", + "score": 0.9996342658996582, + "summary": "project:fact - [2026-09-07] [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries \u2014 the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 892.0002999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 947, + "mcp_result_bytes": 1028, + "wire_bytes": 1065, + "reported_used_tokens": 1028, + "working_set_bytes": 292847616, + "peak_working_set_bytes": 293765120 + }, + { + "query": "kimetsu-remote response takes 8 seconds \u2014 which stage is slow?", + "ranked": [ + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZHM6HQ7BES1FK6GH1PFD", + "id": "01M1X154TMH0ZTSK85721MED0B", + "kind": "memory", + "score": 0.9876242876052856, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1103.2649999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 858, + "mcp_result_bytes": 939, + "wire_bytes": 976, + "reported_used_tokens": 939, + "working_set_bytes": 292847616, + "peak_working_set_bytes": 293765120 + }, + { + "query": "git reflog to rescue accidentally deleted branch", + "ranked": [ + "git-reflog-rescue" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZGNBH8WYWRWD7YH8MQZF", + "id": "01M1X155WYRA16812SEPARN5XP", + "kind": "memory", + "score": 0.998464822769165, + "summary": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone \u2014 they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only \u2014 remote reflog is not accessible via normal git commands." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 973.9513, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 761, + "mcp_result_bytes": 842, + "wire_bytes": 879, + "reported_used_tokens": 842, + "working_set_bytes": 292864000, + "peak_working_set_bytes": 293773312 + }, + { + "query": "git submodule --remote advances the pinned SHA unexpectedly", + "ranked": [ + "git-submodule-pinning", + "git-reflog-rescue", + "ci-secrets-masking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZGMDQD13JXXQKCJ6FCJG", + "id": "01M1X156VFW5WG3WDBZFTYANRQ", + "kind": "memory", + "score": 0.9998551607131958, + "summary": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip \u2014 this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version." + }, + { + "expansion_handle": "memory:01M1X0ZGNBH8WYWRWD7YH8MQZF", + "id": "01M1X156VF7Q900N5S0PKARE41", + "kind": "memory", + "score": 0.8857361078262329, + "summary": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone \u2014 they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only \u2014 remote reflog is not accessible via normal git commands." + }, + { + "expansion_handle": "memory:01M1X0ZJ36VASSAJBJPDTPC7ZQ", + "id": "01M1X156VFG7BCXJMXM8TQDF0J", + "kind": "memory", + "score": 0.8434544205665588, + "summary": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output \u2014 but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 906.6738, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1771, + "mcp_result_bytes": 1888, + "wire_bytes": 1925, + "reported_used_tokens": 1888, + "working_set_bytes": 292872192, + "peak_working_set_bytes": 293777408 + }, + { + "query": "axum SSE streaming drops the last event when client disconnects", + "ranked": [ + "http-streaming-bodies" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZH7WABMDE31F3K2K6K3E", + "id": "01M1X157QZ1FWBRCH3MJJCQ0G7", + "kind": "memory", + "score": 0.9926375150680542, + "summary": "project:fact - [2026-09-07] [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding \u2014 a chunk may split across frame boundaries." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 976.3629000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 859, + "mcp_result_bytes": 940, + "wire_bytes": 977, + "reported_used_tokens": 940, + "working_set_bytes": 292876288, + "peak_working_set_bytes": 293789696 + }, + { + "query": "how do I detect that I am running inside a git worktree vs the main checkout?", + "ranked": [ + "git-worktree-brain-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZGGKZ262G85SY6B3T5B1", + "id": "01M1X158PYGTK9DFTWEC9YVE1W", + "kind": "memory", + "score": 0.9857924580574036, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root \u2014 if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 960.6381, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 881, + "mcp_result_bytes": 962, + "wire_bytes": 999, + "reported_used_tokens": 962, + "working_set_bytes": 292876288, + "peak_working_set_bytes": 293793792 + }, + { + "query": "ONNX Runtime intra-op threads causing CPU contention during parallel bench", + "ranked": [ + "onnx-ort-threading", + "tokio-blocking-in-async" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZGFNAEPEZ0F0YYR16DCA", + "id": "01M1X159MPNV19EFEHZQBJ8QNH", + "kind": "memory", + "score": 0.9999210834503174, + "summary": "project:fact - [tags: onnx ort thread-pool parallelism cpu] ORT (ONNX Runtime) creates its own inter-op and intra-op thread pools. In a multi-process bench setup, each child inherits these pools and they compete for CPU cores. Set `SessionOptionsBuilder::with_intra_threads(1).with_inter_threads(1)` if you're running many parallel bench processes \u2014 this sacrifices per-inference throughput for lower contention." + }, + { + "expansion_handle": "memory:01M1X0ZGPA0V0JRMAC9T4Z7Y8Y", + "id": "01M1X159MPKD11QWZMEKW5HDNE", + "kind": "memory", + "score": 0.5390238761901855, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 897.6116, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1328, + "mcp_result_bytes": 1427, + "wire_bytes": 1464, + "reported_used_tokens": 1427, + "working_set_bytes": 292876288, + "peak_working_set_bytes": 293793792 + }, + { + "query": "what is the right way to supply AWS session token alongside access key and secret?", + "ranked": [ + "aws-credentials-chain" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X0ZHSCPRWJGM9PSEB6ZM8G", + "id": "01M1X15AJ0N1X0S9MWD1BTTN78", + "kind": "memory", + "score": 0.9493365287780762, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1487.4288, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 895, + "mcp_result_bytes": 976, + "wire_bytes": 1013, + "reported_used_tokens": 976, + "working_set_bytes": 292880384, + "peak_working_set_bytes": 293793792 + } + ], + "id": "existing-development-100", + "dimension": "retrieval", + "tier": "hard", + "score": 0.8182539682539681, + "skipped": false, + "detail": "positive-recall@4=0.84 mrr=0.85 stale-hit=n/a resolution=n/a false-injection=0.538 (n=13) positive-n=197 negative-n=13 (210 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 0.8182539682539681, + 1 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 0.8182539682539681, + "n": 1, + "ci95": null + } + }, + "overall_index": 0.8182539682539681, + "scenario_weighted_index": 0.8182539682539681 +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-retrieval/development/3-candidate.json b/docs/audits/2026-09-07-retrieval/development/3-candidate.json new file mode 100644 index 0000000..811c49a --- /dev/null +++ b/docs/audits/2026-09-07-retrieval/development/3-candidate.json @@ -0,0 +1,6391 @@ +{ + "generated_at": "2026-09-07T04:18:25.6401004Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\tmp-tests\\brainbench-development-100.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "test_env_lock inside with_user_brain_disabled deadlock", + "ranked": [ + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15D3BB2XE33PHHB8HPP3R", + "id": "01M1X15KDP8TDR3FK7KVEFMQ3F", + "kind": "memory", + "score": 0.9999797344207764, + "summary": "project:fact - [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure \u2014 `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2357.8848, + "first_query": true, + "server_startup_ms": 76.5544, + "model_text_bytes": 796, + "mcp_result_bytes": 877, + "wire_bytes": 912, + "reported_used_tokens": 877, + "working_set_bytes": 688320512, + "peak_working_set_bytes": 689217536 + }, + { + "query": "why does my test hang after calling with_user_brain_disabled when I also lock test_env_lock?", + "ranked": [ + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15D3BB2XE33PHHB8HPP3R", + "id": "01M1X15MB586E1DHV9A63Y5510", + "kind": "memory", + "score": 0.9996507167816162, + "summary": "project:fact - [2026-09-07] [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure \u2014 `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1093.3616, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 808, + "mcp_result_bytes": 889, + "wire_bytes": 924, + "reported_used_tokens": 889, + "working_set_bytes": 705024000, + "peak_working_set_bytes": 705949696 + }, + { + "query": "ingest_repo_at_root brain_root files_root kimetsu remote", + "ranked": [ + "remote-ingest-split-roots", + "kimetsu-write-tools-gate", + "remote-mcp-host-wiring", + "kimetsu-bench-remote-embedder-singleton" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15D4FA0TN48H977JEDBCS", + "id": "01M1X15NDCJB7KKXQR3YTARNKA", + "kind": "memory", + "score": 0.9999620914459229, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1X15HD2AZTFCTS1HWHGGHBC", + "id": "01M1X15NDCSTKPQA7AXT1RNVCM", + "kind": "memory", + "score": 0.9088719487190248, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level \u2014 disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1X15D6GYW49ER8HFZNZ2A0Y", + "id": "01M1X15NDCNCMCKFZ83SGEZE5X", + "kind": "memory", + "score": 0.8598380088806152, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + }, + { + "expansion_handle": "memory:01M1X15HJBN2B5ZVHFDEHG37WC", + "id": "01M1X15NDCZNRGQ7635TY5JSPK", + "kind": "memory", + "score": 0.6798340678215027, + "summary": "project:fact - [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1482.966, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3381, + "mcp_result_bytes": 3564, + "wire_bytes": 3599, + "reported_used_tokens": 3564, + "working_set_bytes": 830230528, + "peak_working_set_bytes": 831135744 + }, + { + "query": "why does the remote server index the wrong directory when I run kimetsu brain ingest?", + "ranked": [ + "remote-ingest-split-roots", + "git-sparse-checkout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15D4FA0TN48H977JEDBCS", + "id": "01M1X15PVHET58816W7DVDDW7N", + "kind": "memory", + "score": 0.9676534533500672, + "summary": "project:fact - [2026-09-07] [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1X15FQ3ZFGWA8Q1Z4H69EZ4", + "id": "01M1X15PVHJVGN7HQH8P9JD4EC", + "kind": "memory", + "score": 0.6956315040588379, + "summary": "project:fact - [2026-09-07] [tags: git sparse-checkout partial-clone bandwidth] `git sparse-checkout init --cone` combined with `git clone --filter=blob:none` (partial clone) fetches only the commit graph and tree objects, not blobs. Individual blobs are fetched on demand when accessed. This cuts clone time for large repos from minutes to seconds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1766.4808, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1770, + "mcp_result_bytes": 1869, + "wire_bytes": 1904, + "reported_used_tokens": 1869, + "working_set_bytes": 922271744, + "peak_working_set_bytes": 923189248 + }, + { + "query": "kimetsu plugin install --remote mcp.json authorization bearer token", + "ranked": [ + "remote-mcp-host-wiring", + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15D6GYW49ER8HFZNZ2A0Y", + "id": "01M1X15RJVGQW0RWBBHPG15FGV", + "kind": "memory", + "score": 0.9999654293060304, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + }, + { + "expansion_handle": "memory:01M1X15DCPS88Q93B4KKV2ZBHQ", + "id": "01M1X15RJVC7WZVEHJS6FNAQ9X", + "kind": "memory", + "score": 0.9509484171867372, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1464.6527, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1572, + "mcp_result_bytes": 1727, + "wire_bytes": 1762, + "reported_used_tokens": 1727, + "working_set_bytes": 922484736, + "peak_working_set_bytes": 923398144 + }, + { + "query": "how do I wire a remote kimetsu brain into Claude Code without storing the token in the config file?", + "ranked": [ + "remote-mcp-host-wiring", + "bedrock-kimetsu-provider", + "mcp-tool-naming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15D6GYW49ER8HFZNZ2A0Y", + "id": "01M1X15T0FYDB02TZEY9K096ZM", + "kind": "memory", + "score": 0.9997368454933168, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + }, + { + "expansion_handle": "memory:01M1X15D9SXHDTY1FDBVM3GCF4", + "id": "01M1X15T0FEB6TPMAEX2MFR1KN", + "kind": "memory", + "score": 0.9267048239707948, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X15GSF48E4ZJHKNQKF9X43", + "id": "01M1X15T0FQFCDQ58G927SBV87", + "kind": "memory", + "score": 0.6635663509368896, + "summary": "project:fact - [tags: mcp tool naming convention kimetsu] MCP tool names must be valid identifiers for all host agents. Claude Code restricts tool names to `[a-zA-Z0-9_-]` and max 64 chars. Use `snake_case` (kimetsu_brain_context, kimetsu_brain_record) \u2014 hyphen is technically allowed but some hosts reject it." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1308.864, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2430, + "mcp_result_bytes": 2603, + "wire_bytes": 2638, + "reported_used_tokens": 2603, + "working_set_bytes": 924889088, + "peak_working_set_bytes": 925818880 + }, + { + "query": "cargo feature unification kimetsu-brain embeddings fastembed test failure", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-dev-dep-leak", + "cargo-profile-override", + "clap-version-build-flavor" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15D8D3WDFPKKV2KCWRK3G", + "id": "01M1X15V9D3FRVQZYGZNQHJ5J0", + "kind": "memory", + "score": 0.9999688863754272, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X15E933WNR915BWNY6P1NR", + "id": "01M1X15V9DXBSQV9Q9PVBFRRRG", + "kind": "memory", + "score": 0.6766564249992371, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + }, + { + "expansion_handle": "memory:01M1X15EBTY4FPHXMVSCFFTB73", + "id": "01M1X15V9EB98QW0VRZWBJDJGM", + "kind": "memory", + "score": 0.667348325252533, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1X15DJS5JYG6MADVMGPEE92", + "id": "01M1X15V9EHKKK7819M2PA1C6P", + "kind": "memory", + "score": 0.6029285192489624, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1177.6979000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3090, + "mcp_result_bytes": 3245, + "wire_bytes": 3280, + "reported_used_tokens": 3245, + "working_set_bytes": 925253632, + "peak_working_set_bytes": 926171136 + }, + { + "query": "my integration tests pass in isolation but break when I run cargo test --workspace \u2014 embedder changed?", + "ranked": [ + "cargo-feature-unification-embeddings" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15D8D3WDFPKKV2KCWRK3G", + "id": "01M1X15WEFFZ5YJF5PQZ3QM1V7", + "kind": "memory", + "score": 0.9974289536476136, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1254.193, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1147, + "mcp_result_bytes": 1232, + "wire_bytes": 1267, + "reported_used_tokens": 1232, + "working_set_bytes": 925515776, + "peak_working_set_bytes": 926433280 + }, + { + "query": "build_anthropic_body bedrock-2023-05-31 InvokeModel blocking reqwest", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15D9SXHDTY1FDBVM3GCF4", + "id": "01M1X15XNRKC8F1ZC5E8BWP9WD", + "kind": "memory", + "score": 0.9999423027038574, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X15DFBBMCQ54NSMT2FRBJC", + "id": "01M1X15XNRZ6J17GPS4TVNS4MY", + "kind": "memory", + "score": 0.9967412352561952, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1151.1085, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2193, + "mcp_result_bytes": 2320, + "wire_bytes": 2356, + "reported_used_tokens": 2320, + "working_set_bytes": 925622272, + "peak_working_set_bytes": 926535680 + }, + { + "query": "how do I add AWS Bedrock as a model provider in Kimetsu without pulling in the aws-sdk?", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-region-resolution", + "aws-sigv4-bedrock-blocking", + "aws-retry-throttling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15D9SXHDTY1FDBVM3GCF4", + "id": "01M1X15YSJP565QAY18WEWV30P", + "kind": "memory", + "score": 0.9999690055847168, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X15GWVRFEK49MEY6ZDYBPM", + "id": "01M1X15YSKP8DPBRRBZ1FP5786", + "kind": "memory", + "score": 0.9984136819839478, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X15DFBBMCQ54NSMT2FRBJC", + "id": "01M1X15YSKYY8ZANJ0KYZC82YG", + "kind": "memory", + "score": 0.9974077343940736, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1X15GXVY9V58V419NEB9CCJ", + "id": "01M1X15YSKEB38R04NKJZ0276G", + "kind": "memory", + "score": 0.9369313716888428, + "summary": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with \u00b125% jitter." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1314.6479, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3330, + "mcp_result_bytes": 3509, + "wire_bytes": 3545, + "reported_used_tokens": 3509, + "working_set_bytes": 925995008, + "peak_working_set_bytes": 926908416 + }, + { + "query": "BridgeTarget enum seams plugin_install_inner plugin_status_inner resolve_setup_hosts", + "ranked": [ + "bridge-target-enum-seams", + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DBJFHXQMC9585TTVFQY", + "id": "01M1X1602MVTK7SQGD4C3NQFXQ", + "kind": "memory", + "score": 0.9999773502349854, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + }, + { + "expansion_handle": "memory:01M1X15DCPS88Q93B4KKV2ZBHQ", + "id": "01M1X1602M6GHZ6WBX44YT2PKC", + "kind": "memory", + "score": 0.7292461395263672, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1347.6099000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1636, + "mcp_result_bytes": 1743, + "wire_bytes": 1779, + "reported_used_tokens": 1743, + "working_set_bytes": 926277632, + "peak_working_set_bytes": 927191040 + }, + { + "query": "I added a new host to the bridge enum but cargo gives me compile errors in five different match arms \u2014 what did I miss?", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DBJFHXQMC9585TTVFQY", + "id": "01M1X161CYSMYV72H0H5XGM1RV", + "kind": "memory", + "score": 0.997710347175598, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1554.7205, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1059, + "mcp_result_bytes": 1140, + "wire_bytes": 1176, + "reported_used_tokens": 1140, + "working_set_bytes": 926650368, + "peak_working_set_bytes": 927559680 + }, + { + "query": "Pi extension factory defineExtension agent_end session_shutdown kimetsu.ts", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DCPS88Q93B4KKV2ZBHQ", + "id": "01M1X162XESR9212FZ7GSDP92D", + "kind": "memory", + "score": 0.9995530247688292, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1534.9334, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 804, + "mcp_result_bytes": 893, + "wire_bytes": 929, + "reported_used_tokens": 893, + "working_set_bytes": 927059968, + "peak_working_set_bytes": 927969280 + }, + { + "query": "how does Pi (earendil-works/pi) load plugins and what lifecycle hooks does it expose?", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DCPS88Q93B4KKV2ZBHQ", + "id": "01M1X164DAK7ZWRY46ZZDQR8B8", + "kind": "memory", + "score": 0.9957007765769958, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1549.3983999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 803, + "mcp_result_bytes": 892, + "wire_bytes": 928, + "reported_used_tokens": 892, + "working_set_bytes": 927375360, + "peak_working_set_bytes": 928280576 + }, + { + "query": "aws-sigv4 SigningParams apply_to_request_http1x reqwest sign-http", + "ranked": [ + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider", + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DFBBMCQ54NSMT2FRBJC", + "id": "01M1X165XS3P5E9PRDW2WMRAVC", + "kind": "memory", + "score": 0.999910831451416, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1X15D9SXHDTY1FDBVM3GCF4", + "id": "01M1X165XSPZA30XC2RVAF4N9V", + "kind": "memory", + "score": 0.9909924268722534, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X15GYXQNDNN6J2DBBTTCC5", + "id": "01M1X165XS3BXK0DWGY2F5YEBX", + "kind": "memory", + "score": 0.9447544813156128, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1168.1698000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2839, + "mcp_result_bytes": 2984, + "wire_bytes": 3020, + "reported_used_tokens": 2984, + "working_set_bytes": 927461376, + "peak_working_set_bytes": 928362496 + }, + { + "query": "how do I sign a Bedrock InvokeModel request with aws-sigv4 in blocking Rust?", + "ranked": [ + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider", + "aws-presigned-urls", + "aws-credentials-chain" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DFBBMCQ54NSMT2FRBJC", + "id": "01M1X1672AKHQXH7SJDS568S6S", + "kind": "memory", + "score": 0.99993896484375, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1X15D9SXHDTY1FDBVM3GCF4", + "id": "01M1X1672AFJH6BH4PRZMQ3NCH", + "kind": "memory", + "score": 0.9999256134033204, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X15GYXQNDNN6J2DBBTTCC5", + "id": "01M1X1672ANSFH7W2GP91GN0M4", + "kind": "memory", + "score": 0.9729819893836976, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + }, + { + "expansion_handle": "memory:01M1X15GVJQ1VDPFPZB2X2ZX87", + "id": "01M1X1672B2D1P7M745J5XBN6E", + "kind": "memory", + "score": 0.5573575496673584, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1333.512, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3506, + "mcp_result_bytes": 3669, + "wire_bytes": 3705, + "reported_used_tokens": 3669, + "working_set_bytes": 927481856, + "peak_working_set_bytes": 928395264 + }, + { + "query": "KIMETSU_RUNS_GC env opt-out TraceWriter create gc_old_runs caller", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DH41RH973KKDHSPYTEX", + "id": "01M1X168D5RB450JY5T8DD5KM3", + "kind": "memory", + "score": 0.9999783039093018, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1067.8727, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 762, + "mcp_result_bytes": 843, + "wire_bytes": 879, + "reported_used_tokens": 843, + "working_set_bytes": 927526912, + "peak_working_set_bytes": 928436224 + }, + { + "query": "where should I put the KIMETSU_RUNS_GC=0 guard \u2014 inside the GC function or at the call site?", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DH41RH973KKDHSPYTEX", + "id": "01M1X169DTR5NGX8PRTE5RPWV0", + "kind": "memory", + "score": 0.9999699592590332, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1137.878, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 762, + "mcp_result_bytes": 843, + "wire_bytes": 879, + "reported_used_tokens": 843, + "working_set_bytes": 927899648, + "peak_working_set_bytes": 928817152 + }, + { + "query": "git_init_boundary ProjectPaths::discover temp dir user brain isolation", + "ranked": [ + "init-project-git-boundary", + "git-worktree-brain-isolation", + "testing-temp-dirs-ci" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DHYTJCJX4D45ABR9SVG", + "id": "01M1X16AGZ96VV69E85RCZ4ZR3", + "kind": "memory", + "score": 0.9999781847000122, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + }, + { + "expansion_handle": "memory:01M1X15FN49X5ZX6NQND34AZTX", + "id": "01M1X16AGZSKZEBWK6D1N5WZ2C", + "kind": "memory", + "score": 0.998727023601532, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root \u2014 if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + }, + { + "expansion_handle": "memory:01M1X15GDND956M3ZYSP4VNSR9", + "id": "01M1X16AGZR81Z37A2YZENH1DZ", + "kind": "memory", + "score": 0.8577821850776672, + "summary": "project:fact - [tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1093.2336, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1961, + "mcp_result_bytes": 2078, + "wire_bytes": 2114, + "reported_used_tokens": 2078, + "working_set_bytes": 928354304, + "peak_working_set_bytes": 929263616 + }, + { + "query": "my test calls init_project but it writes to the real ~/.kimetsu instead of the temp folder \u2014 why?", + "ranked": [ + "init-project-git-boundary", + "cargo-feature-unification-embeddings", + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DHYTJCJX4D45ABR9SVG", + "id": "01M1X16BK3GXB0NWN2M8D7PWS4", + "kind": "memory", + "score": 0.9999724626541138, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + }, + { + "expansion_handle": "memory:01M1X15D8D3WDFPKKV2KCWRK3G", + "id": "01M1X16BK3FMY76GX9PCDJHX51", + "kind": "memory", + "score": 0.5625059604644775, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X15DCPS88Q93B4KKV2ZBHQ", + "id": "01M1X16BK349JYCQGDEBVC7TTN", + "kind": "memory", + "score": 0.5564239621162415, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1563.6455, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2263, + "mcp_result_bytes": 2392, + "wire_bytes": 2428, + "reported_used_tokens": 2392, + "working_set_bytes": 928800768, + "peak_working_set_bytes": 929710080 + }, + { + "query": "clap command version KIMETSU_VERSION_DISPLAY cfg feature embeddings", + "ranked": [ + "clap-version-build-flavor", + "cargo-feature-unification-embeddings" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DJS5JYG6MADVMGPEE92", + "id": "01M1X16D4S3VNTRT1E2B8N1EXR", + "kind": "memory", + "score": 0.9999767541885376, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + }, + { + "expansion_handle": "memory:01M1X15D8D3WDFPKKV2KCWRK3G", + "id": "01M1X16D4SQA3ARWJ6EGB0YBWR", + "kind": "memory", + "score": 0.9207596778869628, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1066.5739, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1922, + "mcp_result_bytes": 2041, + "wire_bytes": 2077, + "reported_used_tokens": 2041, + "working_set_bytes": 928952320, + "peak_working_set_bytes": 929853440 + }, + { + "query": "how do I show the build flavor (lean vs embeddings) in the kimetsu --version output?", + "ranked": [ + "clap-version-build-flavor", + "cargo-feature-unification-embeddings" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DJS5JYG6MADVMGPEE92", + "id": "01M1X16E5JM514E69ZE0SH08Q9", + "kind": "memory", + "score": 0.9999544620513916, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + }, + { + "expansion_handle": "memory:01M1X15D8D3WDFPKKV2KCWRK3G", + "id": "01M1X16E5KTNWXJ73GMYJ8AKMZ", + "kind": "memory", + "score": 0.8056868314743042, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1256.3836, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1923, + "mcp_result_bytes": 2042, + "wire_bytes": 2078, + "reported_used_tokens": 2042, + "working_set_bytes": 929628160, + "peak_working_set_bytes": 930541568 + }, + { + "query": "Harbor pyiceberg os.getcwd stale WSL2 DrvFs worker-result subprocess re-exec", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DM14YPC8W07H8AJ38CD", + "id": "01M1X16FCQJKGH1ESPYFE1BTNE", + "kind": "memory", + "score": 0.9999732971191406, + "summary": "project:fact - [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1244.6257, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1026, + "mcp_result_bytes": 1107, + "wire_bytes": 1143, + "reported_used_tokens": 1107, + "working_set_bytes": 929714176, + "peak_working_set_bytes": 930619392 + }, + { + "query": "why does my kbench sweep crash after the first trial with 'result.json missing' on WSL2?", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DM14YPC8W07H8AJ38CD", + "id": "01M1X16GKKX2EKS154DW80EHW3", + "kind": "memory", + "score": 0.9995384216308594, + "summary": "project:fact - [2026-09-07] [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1267.6798000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1039, + "mcp_result_bytes": 1120, + "wire_bytes": 1156, + "reported_used_tokens": 1120, + "working_set_bytes": 929718272, + "peak_working_set_bytes": 930635776 + }, + { + "query": "rusqlite VACUUM transaction WAL checkpoint wal_checkpoint TRUNCATE", + "ranked": [ + "sqlite-vacuum-wal-checkpoint", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DNBCE6TEGNBSDPZR023", + "id": "01M1X16HV91H3PKBV5Q8KCXK6N", + "kind": "memory", + "score": 0.9998983144760132, + "summary": "project:fact - [tags: rust sqlite vacuum rusqlite windows] When implementing SQLite VACUUM in rusqlite: VACUUM cannot run inside a transaction. rusqlite's Connection does not hold an implicit transaction, so `conn.execute_batch(\"VACUUM;\")` works directly. After VACUUM, run `PRAGMA wal_checkpoint(TRUNCATE);` before measuring file size \u2014 on Windows the WAL file can hold significant space that isn't reflected in the main db file until the checkpoint runs." + }, + { + "expansion_handle": "memory:01M1X15DVHN86HGNM6VMN30KRH", + "id": "01M1X16HV90BJHK7ZGH23XN3ME", + "kind": "memory", + "score": 0.9661141633987428, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1232.6391999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1507, + "mcp_result_bytes": 1610, + "wire_bytes": 1646, + "reported_used_tokens": 1610, + "working_set_bytes": 930783232, + "peak_working_set_bytes": 931684352 + }, + { + "query": "my SQLite VACUUM reports the file shrank but the disk usage stayed the same \u2014 Windows WAL?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1154.4069000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 931127296, + "peak_working_set_bytes": 932040704 + }, + { + "query": "add_memory import dedup seen_ids snapshot pre-existing active memory IDs", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DP5KJZQA9YVK6J8R7P0", + "id": "01M1X16M6KW46ZHXRGFVEMVR1J", + "kind": "memory", + "score": 0.9999771118164062, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount \u2014 both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1128.0408, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 966, + "mcp_result_bytes": 1047, + "wire_bytes": 1083, + "reported_used_tokens": 1047, + "working_set_bytes": 931155968, + "peak_working_set_bytes": 932061184 + }, + { + "query": "brain import re-imports the same JSON file but the deduplication counter is wrong \u2014 why?", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DP5KJZQA9YVK6J8R7P0", + "id": "01M1X16N953G46GY25ZMN3Q9J3", + "kind": "memory", + "score": 0.7890511751174927, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount \u2014 both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1183.1536, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 965, + "mcp_result_bytes": 1046, + "wire_bytes": 1082, + "reported_used_tokens": 1046, + "working_set_bytes": 931692544, + "peak_working_set_bytes": 932605952 + }, + { + "query": "toml::from_str Value parse document unexpected content str.parse", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DQ9TKTKGW89A4HXSYDK", + "id": "01M1X16PE6K2SAHJVSRHGWGXPX", + "kind": "memory", + "score": 0.9994491934776306, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 968.3338, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 734, + "mcp_result_bytes": 815, + "wire_bytes": 851, + "reported_used_tokens": 815, + "working_set_bytes": 931704832, + "peak_working_set_bytes": 932614144 + }, + { + "query": "how do I parse a TOML configuration file into a toml::Value in toml 0.9?", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DQ9TKTKGW89A4HXSYDK", + "id": "01M1X16QCQ0AZ35NWSY4D39AXN", + "kind": "memory", + "score": 0.9999773502349854, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1078.3355999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 733, + "mcp_result_bytes": 814, + "wire_bytes": 850, + "reported_used_tokens": 814, + "working_set_bytes": 931827712, + "peak_working_set_bytes": 932737024 + }, + { + "query": "CIM CreationDate DMTF WMI ps etimes started_at assess_mcp_skew", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DR75WE1TPEP2Y4ZWYT8", + "id": "01M1X16REAGPD3NMF087331ADD", + "kind": "memory", + "score": 0.9999735355377196, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1033.3817999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 924, + "mcp_result_bytes": 1013, + "wire_bytes": 1049, + "reported_used_tokens": 1013, + "working_set_bytes": 931893248, + "peak_working_set_bytes": 932798464 + }, + { + "query": "how do I read a process start time on both Windows and Linux in pure Rust?", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DR75WE1TPEP2Y4ZWYT8", + "id": "01M1X16SFFZ44JM72SK45XC7ZQ", + "kind": "memory", + "score": 0.9942069053649902, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1156.7792, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 923, + "mcp_result_bytes": 1012, + "wire_bytes": 1048, + "reported_used_tokens": 1012, + "working_set_bytes": 931917824, + "peak_working_set_bytes": 932839424 + }, + { + "query": "processes_locking_target decide_preflight_action BufRead Write update.rs", + "ranked": [ + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DSFPF33KASC60BPJT3A", + "id": "01M1X16TJP5NNG5AQ6NT35BFAB", + "kind": "memory", + "score": 0.999950647354126, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 977.275, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1132, + "mcp_result_bytes": 1213, + "wire_bytes": 1249, + "reported_used_tokens": 1213, + "working_set_bytes": 932118528, + "peak_working_set_bytes": 933036032 + }, + { + "query": "how should I reuse the existing process enumerator in the update preflight check to avoid a second PowerShell query?", + "ranked": [ + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DSFPF33KASC60BPJT3A", + "id": "01M1X16VHE1H5EA81DZBSE34TG", + "kind": "memory", + "score": 0.9999661445617676, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1164.4165, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1132, + "mcp_result_bytes": 1213, + "wire_bytes": 1249, + "reported_used_tokens": 1213, + "working_set_bytes": 932638720, + "peak_working_set_bytes": 933552128 + }, + { + "query": "cfg_attr windows allow dead_code parse_unix_ps cross-platform tests", + "ranked": [ + "cfg-cross-platform-dead-code", + "process-start-time-cross-platform", + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DTMCJMRZQ3DV0ZN9P8A", + "id": "01M1X16WNSMMCTBC1KD0AVF1EM", + "kind": "memory", + "score": 0.9999802112579346, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + }, + { + "expansion_handle": "memory:01M1X15DR75WE1TPEP2Y4ZWYT8", + "id": "01M1X16WNSBF01TP52F16G6908", + "kind": "memory", + "score": 0.8274164795875549, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + }, + { + "expansion_handle": "memory:01M1X15DSFPF33KASC60BPJT3A", + "id": "01M1X16WNSEC3G9YCNQ0V8XFWG", + "kind": "memory", + "score": 0.7637738585472107, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 957.7873999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2422, + "mcp_result_bytes": 2547, + "wire_bytes": 2583, + "reported_used_tokens": 2547, + "working_set_bytes": 932642816, + "peak_working_set_bytes": 933556224 + }, + { + "query": "how do I keep a function that is only called on Unix from triggering dead_code warnings on Windows?", + "ranked": [ + "cfg-cross-platform-dead-code" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DTMCJMRZQ3DV0ZN9P8A", + "id": "01M1X16XM3ER8NYYDS3QNA6CN1", + "kind": "memory", + "score": 0.9998409748077391, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1106.8841, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 939, + "reported_used_tokens": 903, + "working_set_bytes": 932655104, + "peak_working_set_bytes": 933564416 + }, + { + "query": "deadlocking a Rust mutex in integration tests", + "ranked": [ + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15D3BB2XE33PHHB8HPP3R", + "id": "01M1X16YP3F54F2RT8GD3SEYMG", + "kind": "memory", + "score": 0.9991264939308168, + "summary": "project:fact - [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure \u2014 `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1024.4911000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 796, + "mcp_result_bytes": 877, + "wire_bytes": 913, + "reported_used_tokens": 877, + "working_set_bytes": 932667392, + "peak_working_set_bytes": 933576704 + }, + { + "query": "benchmarking retrieval quality across embedders", + "ranked": [ + "onnx-quantization-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15FBQ9F9C7XM5PYQHS0MG", + "id": "01M1X16ZP4VW4SW0KW5Y1BXEBP", + "kind": "memory", + "score": 0.7459741234779358, + "summary": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals \u2014 cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 1026.3617000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 978, + "mcp_result_bytes": 1059, + "wire_bytes": 1095, + "reported_used_tokens": 1059, + "working_set_bytes": 932675584, + "peak_working_set_bytes": 933580800 + }, + { + "query": "process memory working set RSS peak measurement Windows", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1185.6062, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 938156032, + "peak_working_set_bytes": 939044864 + }, + { + "query": "cloning a git repository server-side into a managed checkout", + "ranked": [ + "remote-ingest-split-roots", + "git-sparse-checkout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15D4FA0TN48H977JEDBCS", + "id": "01M1X171VBKA3SQBMEKK2DEFME", + "kind": "memory", + "score": 0.9940817952156068, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1X15FQ3ZFGWA8Q1Z4H69EZ4", + "id": "01M1X171VBYFK1TM97FF4SJXKC", + "kind": "memory", + "score": 0.9041922092437744, + "summary": "project:fact - [tags: git sparse-checkout partial-clone bandwidth] `git sparse-checkout init --cone` combined with `git clone --filter=blob:none` (partial clone) fetches only the commit graph and tree objects, not blobs. Individual blobs are fetched on demand when accessed. This cuts clone time for large repos from minutes to seconds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1254.8802, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1744, + "mcp_result_bytes": 1843, + "wire_bytes": 1879, + "reported_used_tokens": 1843, + "working_set_bytes": 938532864, + "peak_working_set_bytes": 939438080 + }, + { + "query": "SigV4 signing HTTP requests in Rust", + "ranked": [ + "aws-sigv4-bedrock-blocking", + "aws-presigned-urls", + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DFBBMCQ54NSMT2FRBJC", + "id": "01M1X1732J2329PXXWH0WJ1JXD", + "kind": "memory", + "score": 0.995133101940155, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1X15GYXQNDNN6J2DBBTTCC5", + "id": "01M1X1732J8T4N4DZTRFSSR4GJ", + "kind": "memory", + "score": 0.9750049114227296, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + }, + { + "expansion_handle": "memory:01M1X15D9SXHDTY1FDBVM3GCF4", + "id": "01M1X1732J36RGHPDQ32Q8167Y", + "kind": "memory", + "score": 0.936759352684021, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1270.5157, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2838, + "mcp_result_bytes": 2983, + "wire_bytes": 3019, + "reported_used_tokens": 2983, + "working_set_bytes": 938647552, + "peak_working_set_bytes": 939540480 + }, + { + "query": "cargo test --workspace feature flag changes broke my unit tests", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15D8D3WDFPKKV2KCWRK3G", + "id": "01M1X174ABH6MWR08JQ0H9M03G", + "kind": "memory", + "score": 0.9825970530509948, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X15E933WNR915BWNY6P1NR", + "id": "01M1X174AB7GYFG688N9FD1M9W", + "kind": "memory", + "score": 0.8159734606742859, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1069.4169, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1837, + "mcp_result_bytes": 1940, + "wire_bytes": 1976, + "reported_used_tokens": 1940, + "working_set_bytes": 938729472, + "peak_working_set_bytes": 939638784 + }, + { + "query": "how do I make pasta carbonara?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 1440.1409, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 938737664, + "peak_working_set_bytes": 939651072 + }, + { + "query": "what is the offside rule in football?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 1352.1935, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 938741760, + "peak_working_set_bytes": 939651072 + }, + { + "query": "best way to train for a half marathon", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 1264.828, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 938754048, + "peak_working_set_bytes": 939671552 + }, + { + "query": "my test passes when I run it alone but fails under cargo test --workspace", + "ranked": [ + "cargo-feature-unification-embeddings" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15D8D3WDFPKKV2KCWRK3G", + "id": "01M1X179AMKXRMPDHQ16RF1K0H", + "kind": "memory", + "score": 0.9996844530105592, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1239.7930999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1148, + "mcp_result_bytes": 1233, + "wire_bytes": 1269, + "reported_used_tokens": 1233, + "working_set_bytes": 938819584, + "peak_working_set_bytes": 939732992 + }, + { + "query": "all the project tests started hanging forever after I added my new test", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1179.0072, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 938885120, + "peak_working_set_bytes": 939790336 + }, + { + "query": "my integration test silently wrote memories into my real home brain instead of the temp workspace", + "ranked": [ + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DHYTJCJX4D45ABR9SVG", + "id": "01M1X17BP6Q7MKVMJ9V153Y0TH", + "kind": "memory", + "score": 0.7656484842300415, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1483.7088999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 780, + "mcp_result_bytes": 861, + "wire_bytes": 897, + "reported_used_tokens": 861, + "working_set_bytes": 938835968, + "peak_working_set_bytes": 939802624 + }, + { + "query": "where should the env-var opt-out check live for a cleanup feature triggered from a hot code path", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DH41RH973KKDHSPYTEX", + "id": "01M1X17D4W1QG5RB2SSPNASK6V", + "kind": "memory", + "score": 0.9995137453079224, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1162.6007, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 761, + "mcp_result_bytes": 842, + "wire_bytes": 878, + "reported_used_tokens": 842, + "working_set_bytes": 938848256, + "peak_working_set_bytes": 939802624 + }, + { + "query": "the brain database file stays huge on Windows even after deleting most rows", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1098.541, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 938921984, + "peak_working_set_bytes": 939839488 + }, + { + "query": "re-importing the same exported memories file counts them as new instead of deduplicated", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DP5KJZQA9YVK6J8R7P0", + "id": "01M1X17FBFCB94FDTTW4WX79H8", + "kind": "memory", + "score": 0.9581347703933716, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount \u2014 both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1524.3329999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 965, + "mcp_result_bytes": 1046, + "wire_bytes": 1082, + "reported_used_tokens": 1046, + "working_set_bytes": 938954752, + "peak_working_set_bytes": 939868160 + }, + { + "query": "a helper function only called on Unix at runtime fails the dead-code lint on the Windows build", + "ranked": [ + "cfg-cross-platform-dead-code" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DTMCJMRZQ3DV0ZN9P8A", + "id": "01M1X17GV96SD3KQEBVZP4VFSA", + "kind": "memory", + "score": 0.9773045778274536, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1111.3182, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 939, + "reported_used_tokens": 903, + "working_set_bytes": 938979328, + "peak_working_set_bytes": 939896832 + }, + { + "query": "the second Terminal-Bench trial always crashes even though the first one passes", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DM14YPC8W07H8AJ38CD", + "id": "01M1X17HY29PEV4YXM9A9A4T82", + "kind": "memory", + "score": 0.9474697113037108, + "summary": "project:fact - [2026-09-07] [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1220.8135, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1038, + "mcp_result_bytes": 1119, + "wire_bytes": 1155, + "reported_used_tokens": 1119, + "working_set_bytes": 938995712, + "peak_working_set_bytes": 939905024 + }, + { + "query": "how does doctor tell a running MCP server process is older than the kimetsu binary on disk", + "ranked": [ + "kimetsu-daemon-lifecycle" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15H82NFSZJR4K18E3AJP1", + "id": "01M1X17K43TRQ4FJZWCJZ4EZ2F", + "kind": "memory", + "score": 0.9223618507385254, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1257.527, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 752, + "mcp_result_bytes": 833, + "wire_bytes": 869, + "reported_used_tokens": 833, + "working_set_bytes": 939134976, + "peak_working_set_bytes": 940048384 + }, + { + "query": "the self-update preflight needs the list of running kimetsu processes without re-running the OS query", + "ranked": [ + "windows-update-process-locking", + "kimetsu-daemon-lifecycle" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DSFPF33KASC60BPJT3A", + "id": "01M1X17MBHQYR3M0DZW0X82JEQ", + "kind": "memory", + "score": 0.9925383925437928, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + }, + { + "expansion_handle": "memory:01M1X15H82NFSZJR4K18E3AJP1", + "id": "01M1X17MBJGCKVSP76A662D8DA", + "kind": "memory", + "score": 0.5589243769645691, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1108.1162, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1658, + "mcp_result_bytes": 1757, + "wire_bytes": 1793, + "reported_used_tokens": 1757, + "working_set_bytes": 939147264, + "peak_working_set_bytes": 940048384 + }, + { + "query": "parsing the WMI DMTF CreationDate timestamp into epoch seconds without extra crates", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DR75WE1TPEP2Y4ZWYT8", + "id": "01M1X17NDWQ0YDXBNPNG0GVJ3Z", + "kind": "memory", + "score": 0.9751563668251038, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1367.6399999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 924, + "mcp_result_bytes": 1013, + "wire_bytes": 1049, + "reported_used_tokens": 1013, + "working_set_bytes": 939151360, + "peak_working_set_bytes": 940060672 + }, + { + "query": "calling Bedrock InvokeModel from blocking reqwest without the aws sdk", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking", + "aws-region-resolution" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15D9SXHDTY1FDBVM3GCF4", + "id": "01M1X17PRV6G3W01K28R44P924", + "kind": "memory", + "score": 0.99994158744812, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X15DFBBMCQ54NSMT2FRBJC", + "id": "01M1X17PRVQ5EE7RT41EHKV11C", + "kind": "memory", + "score": 0.9989731311798096, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1X15GWVRFEK49MEY6ZDYBPM", + "id": "01M1X17PRVXJGSNZQPZTHKMCR7", + "kind": "memory", + "score": 0.6464323401451111, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1377.8292, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2787, + "mcp_result_bytes": 2932, + "wire_bytes": 2968, + "reported_used_tokens": 2932, + "working_set_bytes": 939155456, + "peak_working_set_bytes": 940068864 + }, + { + "query": "how do I rotate the encryption key protecting the kimetsu brain database", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 1142.0562, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 939159552, + "peak_working_set_bytes": 940068864 + }, + { + "query": "which tokio runtime worker-thread settings does the kimetsu MCP server use", + "ranked": [ + "tokio-blocking-in-async", + "tokio-runtime-in-tests", + "tokio-spawn-blocking", + "mcp-stdout-protocol" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15FV23NVFH7CBKPA19KAM", + "id": "01M1X17S7QCK5P3KQ1Q0WRFWDW", + "kind": "memory", + "score": 0.9897258877754213, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + }, + { + "expansion_handle": "memory:01M1X15FW4W876QNP42FE7PQYT", + "id": "01M1X17S7QC04VJAYM2WYJSSAE", + "kind": "memory", + "score": 0.9347747564315796, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + }, + { + "expansion_handle": "memory:01M1X15FZF9QWQTQS7XGKCV214", + "id": "01M1X17S7Q8HWT93F96JNS91PZ", + "kind": "memory", + "score": 0.840076208114624, + "summary": "project:fact - [tags: tokio spawn_blocking thread-pool rust blocking] `tokio::task::spawn_blocking` places work on a dedicated blocking thread pool (default up to 512 threads, configurable via `Builder::max_blocking_threads`). Each call creates or reuses a thread \u2014 there's no true pooling, threads may be created on demand. For many short-duration blocking calls (e.g." + }, + { + "expansion_handle": "memory:01M1X15GNDA8AGM499HW3DG48J", + "id": "01M1X17S7QGFQQZNCFS4SBX1KS", + "kind": "memory", + "score": 0.6246721744537354, + "summary": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 1126.8818, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2364, + "mcp_result_bytes": 2507, + "wire_bytes": 2543, + "reported_used_tokens": 2507, + "working_set_bytes": 939220992, + "peak_working_set_bytes": 940130304 + }, + { + "query": "how does kimetsu sync memories between two machines over the network", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 1039.1021, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 939220992, + "peak_working_set_bytes": 940130304 + }, + { + "query": "recovering a corrupted usearch ANN index after a power loss", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 988.0461, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 939220992, + "peak_working_set_bytes": 940130304 + }, + { + "query": "what postgres schema should I use to store kimetsu memories", + "ranked": [ + "onnx-dim-mismatch" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15FFFCT7F77MVS57G64GD", + "id": "01M1X17WA4CQ9K061JQPNGE6FB", + "kind": "memory", + "score": 0.6243454217910767, + "summary": "project:fact - [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results \u2014 the ANN index shape mismatch isn't always caught at runtime." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 1049.4098000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 740, + "mcp_result_bytes": 821, + "wire_bytes": 857, + "reported_used_tokens": 821, + "working_set_bytes": 939229184, + "peak_working_set_bytes": 940130304 + }, + { + "query": "the whole CI job just froze forever with no failure output after my latest test PR", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1087.5802999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 939229184, + "peak_working_set_bytes": 940142592 + }, + { + "query": "running the test suite left junk state in my home directory", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1089.0466999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 939249664, + "peak_working_set_bytes": 940163072 + }, + { + "query": "I deleted a bunch of old rows but the file on disk is still the same size", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1051.5095999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 939249664, + "peak_working_set_bytes": 940163072 + }, + { + "query": "adding one new crate quietly changed how the whole workspace builds", + "ranked": [ + "cargo-lockfile-drift", + "cargo-feature-unification-embeddings" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15E77TZ09VG4QKMKT0Z1H", + "id": "01M1X180FTQFWBX8FCVA7CDSWM", + "kind": "memory", + "score": 0.9958756566047668, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this \u2014 it errors on any lockfile diff." + }, + { + "expansion_handle": "memory:01M1X15D8D3WDFPKKV2KCWRK3G", + "id": "01M1X180FT458HAN5TEEEK6VNQ", + "kind": "memory", + "score": 0.9790327548980712, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 0.5, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1226.3717000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1671, + "mcp_result_bytes": 1774, + "wire_bytes": 1810, + "reported_used_tokens": 1774, + "working_set_bytes": 939249664, + "peak_working_set_bytes": 940167168 + }, + { + "query": "we cannot pull an async runtime into the agent just to talk to AWS", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1571.2972, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 939446272, + "peak_working_set_bytes": 940351488 + }, + { + "query": "users should be able to tell which build variant they installed from the version output", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1390.5432, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 939503616, + "peak_working_set_bytes": 940417024 + }, + { + "query": "what gotchas should I expect writing process-inspection code that works on both Windows and Unix?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1332.8373, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 939503616, + "peak_working_set_bytes": 940421120 + }, + { + "query": "why might tests behave differently on my machine than in the full CI run?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1206.9483, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 939511808, + "peak_working_set_bytes": 940425216 + }, + { + "query": "what do I need to know before wiring kimetsu into a brand new host agent?", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DBJFHXQMC9585TTVFQY", + "id": "01M1X18731JX6S7MTRY22VB00J", + "kind": "memory", + "score": 0.8340779542922974, + "summary": "project:fact - [2026-09-07] [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 0.3333333333333333, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1400.7385000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1072, + "mcp_result_bytes": 1153, + "wire_bytes": 1189, + "reported_used_tokens": 1153, + "working_set_bytes": 939511808, + "peak_working_set_bytes": 940425216 + }, + { + "query": "tell me everything relevant to running kimetsu against AWS", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1244.9721, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 939511808, + "peak_working_set_bytes": 940425216 + }, + { + "query": "ingesting a cloned repo when the brain lives under a different root", + "ranked": [ + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15D4FA0TN48H977JEDBCS", + "id": "01M1X189ND4QK6S962PSBXT8PK", + "kind": "memory", + "score": 0.9854778051376344, + "summary": "project:fact - [2026-09-07] [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1387.5681, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1274, + "mcp_result_bytes": 1355, + "wire_bytes": 1391, + "reported_used_tokens": 1355, + "working_set_bytes": 940560384, + "peak_working_set_bytes": 941465600 + }, + { + "query": "streamable-http transport entry for openclaw.json with a bearer token", + "ranked": [ + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15D6GYW49ER8HFZNZ2A0Y", + "id": "01M1X18B0C8VEVTFS1W9QB54A2", + "kind": "memory", + "score": 0.9984819293022156, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1513.0852, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 996, + "mcp_result_bytes": 1125, + "wire_bytes": 1161, + "reported_used_tokens": 1125, + "working_set_bytes": 940560384, + "peak_working_set_bytes": 941473792 + }, + { + "query": "serializing ingests with a tokio mutex to avoid checkout races", + "ranked": [ + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15D4FA0TN48H977JEDBCS", + "id": "01M1X18CFMRE5YWGKB7XKVGR73", + "kind": "memory", + "score": 0.9989351630210876, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1421.3337, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1261, + "mcp_result_bytes": 1342, + "wire_bytes": 1378, + "reported_used_tokens": 1342, + "working_set_bytes": 940560384, + "peak_working_set_bytes": 941477888 + }, + { + "query": "percent-encoding the colon in the bedrock model id for the invoke URL", + "ranked": [ + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15D9SXHDTY1FDBVM3GCF4", + "id": "01M1X18DW2ZAF5KAM5T3P69GD2", + "kind": "memory", + "score": 0.9490103721618652, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1306.0443, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1204, + "mcp_result_bytes": 1293, + "wire_bytes": 1329, + "reported_used_tokens": 1293, + "working_set_bytes": 940625920, + "peak_working_set_bytes": 941535232 + }, + { + "query": "deduplicating re-imported memories against pre-existing ids", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DP5KJZQA9YVK6J8R7P0", + "id": "01M1X18F51AX9NDPCT8PZCDHGV", + "kind": "memory", + "score": 0.996511161327362, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount \u2014 both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1226.0142, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 966, + "mcp_result_bytes": 1047, + "wire_bytes": 1083, + "reported_used_tokens": 1047, + "working_set_bytes": 940711936, + "peak_working_set_bytes": 941604864 + }, + { + "query": "parsing DMTF datetimes", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DR75WE1TPEP2Y4ZWYT8", + "id": "01M1X18GB3Q9QGGEPS1PPNCKJC", + "kind": "memory", + "score": 0.990456759929657, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 897.8541, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 923, + "mcp_result_bytes": 1012, + "wire_bytes": 1048, + "reported_used_tokens": 1012, + "working_set_bytes": 940716032, + "peak_working_set_bytes": 941604864 + }, + { + "query": "how should install derive a stable identifier from the git remote URL?", + "ranked": [ + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15D6GYW49ER8HFZNZ2A0Y", + "id": "01M1X18H7EVBD597G17CXE3B78", + "kind": "memory", + "score": 0.9954527020454408, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1382.9264, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 995, + "mcp_result_bytes": 1124, + "wire_bytes": 1160, + "reported_used_tokens": 1124, + "working_set_bytes": 940851200, + "peak_working_set_bytes": 941760512 + }, + { + "query": "the secret token must not end up written into the host config file", + "ranked": [ + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15D6GYW49ER8HFZNZ2A0Y", + "id": "01M1X18JJPST68J14QXW8GEPY8", + "kind": "memory", + "score": 0.8757492899894714, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1553.3120999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 996, + "mcp_result_bytes": 1125, + "wire_bytes": 1161, + "reported_used_tokens": 1125, + "working_set_bytes": 940851200, + "peak_working_set_bytes": 941764608 + }, + { + "query": "keep the cleanup logic unit-testable without touching environment variables", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1118.117, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 940851200, + "peak_working_set_bytes": 941764608 + }, + { + "query": "how do we stop the server from cloning arbitrary repos clients request?", + "ranked": [ + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15D4FA0TN48H977JEDBCS", + "id": "01M1X18N6R6KZXZKQTTN8ND05M", + "kind": "memory", + "score": 0.6168935894966125, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1390.3291, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1261, + "mcp_result_bytes": 1342, + "wire_bytes": 1378, + "reported_used_tokens": 1342, + "working_set_bytes": 940879872, + "peak_working_set_bytes": 941793280 + }, + { + "query": "make sure a wrong guess about a host plugin API never breaks that host", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DCPS88Q93B4KKV2ZBHQ", + "id": "01M1X18PHGR392T2QA3CV8XJHB", + "kind": "memory", + "score": 0.9010460376739502, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1378.4787999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 803, + "mcp_result_bytes": 892, + "wire_bytes": 928, + "reported_used_tokens": 892, + "working_set_bytes": 940888064, + "peak_working_set_bytes": 941805568 + }, + { + "query": "which wire-format trick lets us reuse the existing Anthropic request builder for AWS?", + "ranked": [ + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15D9SXHDTY1FDBVM3GCF4", + "id": "01M1X18QWPB0XDNV1MMDK4DJN2", + "kind": "memory", + "score": 0.9861636757850648, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1393.8305, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1203, + "mcp_result_bytes": 1292, + "wire_bytes": 1328, + "reported_used_tokens": 1292, + "working_set_bytes": 940892160, + "peak_working_set_bytes": 941805568 + }, + { + "query": "the self-update froze because something was still holding the executable", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1108.7758999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 940896256, + "peak_working_set_bytes": 941805568 + }, + { + "query": "our notes about the extension API turned out wrong once we read the actual repo", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DCPS88Q93B4KKV2ZBHQ", + "id": "01M1X18TBCHCK0DB65NPT516TQ", + "kind": "memory", + "score": 0.7192176580429077, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1582.0104000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 803, + "mcp_result_bytes": 892, + "wire_bytes": 928, + "reported_used_tokens": 892, + "working_set_bytes": 941019136, + "peak_working_set_bytes": 941924352 + }, + { + "query": "half the benchmark trials die right after the first one finishes", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1253.1805, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 941019136, + "peak_working_set_bytes": 941932544 + }, + { + "query": "I need this parser visible to tests on every OS even though only one OS calls it", + "ranked": [ + "cfg-cross-platform-dead-code" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DTMCJMRZQ3DV0ZN9P8A", + "id": "01M1X18X56Q7Q85APX0V2DX42C", + "kind": "memory", + "score": 0.5910465121269226, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1407.1594, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 821, + "mcp_result_bytes": 902, + "wire_bytes": 938, + "reported_used_tokens": 902, + "working_set_bytes": 941019136, + "peak_working_set_bytes": 941932544 + }, + { + "query": "the config file content refuses to parse even though the TOML looks valid", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DQ9TKTKGW89A4HXSYDK", + "id": "01M1X18YGAB23A5892DPVFN6V4", + "kind": "memory", + "score": 0.8025842905044556, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1379.2542, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 733, + "mcp_result_bytes": 814, + "wire_bytes": 850, + "reported_used_tokens": 814, + "working_set_bytes": 941019136, + "peak_working_set_bytes": 941932544 + }, + { + "query": "the remote server must refresh its checkout before answering file queries", + "ranked": [ + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15D4FA0TN48H977JEDBCS", + "id": "01M1X18ZVFQ20VFZPETZT70MCX", + "kind": "memory", + "score": 0.7441006898880005, + "summary": "project:fact - [2026-09-07] [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1494.925, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1274, + "mcp_result_bytes": 1355, + "wire_bytes": 1391, + "reported_used_tokens": 1355, + "working_set_bytes": 941019136, + "peak_working_set_bytes": 941932544 + }, + { + "query": "tests must not climb to a parent git repository when resolving project paths", + "ranked": [ + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DHYTJCJX4D45ABR9SVG", + "id": "01M1X191A2NFZ6JYE5ZCNCW15A", + "kind": "memory", + "score": 0.9996840953826904, + "summary": "project:fact - [2026-09-07] [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1443.8546999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 794, + "mcp_result_bytes": 875, + "wire_bytes": 911, + "reported_used_tokens": 875, + "working_set_bytes": 942215168, + "peak_working_set_bytes": 943120384 + }, + { + "query": "how do I test request signing deterministically when timestamps change every run?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1300.6895, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 942661632, + "peak_working_set_bytes": 943570944 + }, + { + "query": "adding a new variant to the host target enum - which places will I forget to update?", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DBJFHXQMC9585TTVFQY", + "id": "01M1X193Z06AEEHPC971RB881T", + "kind": "memory", + "score": 0.885578989982605, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1541.4235999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1058, + "mcp_result_bytes": 1139, + "wire_bytes": 1175, + "reported_used_tokens": 1139, + "working_set_bytes": 942669824, + "peak_working_set_bytes": 943579136 + }, + { + "query": "how do I enable GPU acceleration for kimetsu embedding inference", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 1066.4569, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 942669824, + "peak_working_set_bytes": 943579136 + }, + { + "query": "how do I throttle kimetsu API spend per month", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 1501.0059, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 942682112, + "peak_working_set_bytes": 943595520 + }, + { + "query": "can the kimetsu brain database be stored in S3 instead of on disk", + "ranked": [ + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15GYXQNDNN6J2DBBTTCC5", + "id": "01M1X197ZQZYPE11W1MDC66EQS", + "kind": "memory", + "score": 0.6605784893035889, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 986.1297999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 874, + "mcp_result_bytes": 955, + "wire_bytes": 991, + "reported_used_tokens": 955, + "working_set_bytes": 942694400, + "peak_working_set_bytes": 943607808 + }, + { + "query": "how do I plug a custom tokenizer into the FTS index", + "ranked": [ + "sqlite-fts5-tokenizer" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15E1TGG28S7WH2DKR0SQA", + "id": "01M1X198YFY2R9J1AF10RZ8HQZ", + "kind": "memory", + "score": 0.8707897067070007, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 1508.1749, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 671, + "mcp_result_bytes": 756, + "wire_bytes": 792, + "reported_used_tokens": 756, + "working_set_bytes": 942694400, + "peak_working_set_bytes": 943607808 + }, + { + "query": "what should I check when kimetsu behaves differently on Windows than on Linux?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1059.6009, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 942698496, + "peak_working_set_bytes": 943607808 + }, + { + "query": "what are the moving parts of the kimetsu remote deployment story?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1073.5718000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 942706688, + "peak_working_set_bytes": 943620096 + }, + { + "query": "which lessons cover guarding behavior behind environment variables?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1147.7153, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 942706688, + "peak_working_set_bytes": 943620096 + }, + { + "query": "SQLite BUSY error under concurrent writes", + "ranked": [ + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DVHN86HGNM6VMN30KRH", + "id": "01M1X19DM13AT22WN396GDZ0Y9", + "kind": "memory", + "score": 0.8938739895820618, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 936.0353, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 898, + "mcp_result_bytes": 979, + "wire_bytes": 1016, + "reported_used_tokens": 979, + "working_set_bytes": 942706688, + "peak_working_set_bytes": 943620096 + }, + { + "query": "SQLite WAL mode breaks when the database is on a network share", + "ranked": [ + "sqlite-wal-network-drive", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15E0REZ5X10NXK6B8EJM2", + "id": "01M1X19EHAJ4PFD5SB4DQSM6YB", + "kind": "memory", + "score": 0.999137282371521, + "summary": "project:fact - [2026-09-07] [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + }, + { + "expansion_handle": "memory:01M1X15DVHN86HGNM6VMN30KRH", + "id": "01M1X19EHA35XRRMM15CJY03C4", + "kind": "memory", + "score": 0.9803613424301147, + "summary": "project:fact - [2026-09-07] [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1091.7937, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1448, + "mcp_result_bytes": 1547, + "wire_bytes": 1584, + "reported_used_tokens": 1547, + "working_set_bytes": 943132672, + "peak_working_set_bytes": 944041984 + }, + { + "query": "my SQLite WAL database causes SQLITE_IOERR_LOCK on a mapped drive", + "ranked": [ + "sqlite-wal-network-drive", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15E0REZ5X10NXK6B8EJM2", + "id": "01M1X19FKH395JB2WGC31Z32QA", + "kind": "memory", + "score": 0.999721109867096, + "summary": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + }, + { + "expansion_handle": "memory:01M1X15DVHN86HGNM6VMN30KRH", + "id": "01M1X19FKHJWGRQZ07ZHGRJMKX", + "kind": "memory", + "score": 0.6342206001281738, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1107.2249000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1421, + "mcp_result_bytes": 1520, + "wire_bytes": 1557, + "reported_used_tokens": 1520, + "working_set_bytes": 943144960, + "peak_working_set_bytes": 944050176 + }, + { + "query": "FTS5 tokenizer configuration for Rust identifiers with underscores", + "ranked": [ + "sqlite-fts5-tokenizer" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15E1TGG28S7WH2DKR0SQA", + "id": "01M1X19GP4EDB9APCYM4BTM0TC", + "kind": "memory", + "score": 0.9998878240585328, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1048.8602, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 671, + "mcp_result_bytes": 756, + "wire_bytes": 793, + "reported_used_tokens": 756, + "working_set_bytes": 943153152, + "peak_working_set_bytes": 944058368 + }, + { + "query": "I switched the FTS5 tokenizer but search stopped returning results", + "ranked": [ + "sqlite-fts5-tokenizer" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15E1TGG28S7WH2DKR0SQA", + "id": "01M1X19HQ1740440194DDVHPVW", + "kind": "memory", + "score": 0.943705141544342, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1120.8301999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 669, + "mcp_result_bytes": 754, + "wire_bytes": 791, + "reported_used_tokens": 754, + "working_set_bytes": 943407104, + "peak_working_set_bytes": 944312320 + }, + { + "query": "optimal SQLite page size for storing embedding vectors", + "ranked": [ + "sqlite-page-size" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15E2S6M0NX4GP0Q6FC5XX", + "id": "01M1X19JTSE0YD2YPXHBES3WP1", + "kind": "memory", + "score": 0.9990623593330384, + "summary": "project:fact - [tags: sqlite page_size performance rusqlite] SQLite's default page_size is 4096 bytes. For a write-heavy brain database with large BLOB payloads (embedding vectors), raising page_size to 16384 reduces fragmentation and improves sequential scan throughput. `PRAGMA page_size = 16384;` must be set BEFORE the first table is created \u2014 changing it on an existing database requires a VACUUM afterward to rebuild all pages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1033.9560999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 809, + "mcp_result_bytes": 890, + "wire_bytes": 927, + "reported_used_tokens": 890, + "working_set_bytes": 943407104, + "peak_working_set_bytes": 944312320 + }, + { + "query": "ON DELETE CASCADE in SQLite does nothing \u2014 foreign keys not enforced", + "ranked": [ + "sqlite-foreign-keys-default-off" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15E3N8MXFVS8YB17MBFKH", + "id": "01M1X19KTRA9KMME7K9N6C9PSS", + "kind": "memory", + "score": 0.9999727010726928, + "summary": "project:fact - [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting \u2014 every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1127.9596999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 736, + "mcp_result_bytes": 817, + "wire_bytes": 854, + "reported_used_tokens": 817, + "working_set_bytes": 943407104, + "peak_working_set_bytes": 944312320 + }, + { + "query": "indexing a JSON metadata column in SQLite without a schema migration", + "ranked": [ + "sqlite-json1-extract" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15E4EPNPQD78NJEVF2M15", + "id": "01M1X19MZ0625Z2BE63KYEVAHC", + "kind": "memory", + "score": 0.9998306035995485, + "summary": "project:fact - [tags: sqlite json1 json_extract rusqlite] SQLite's json1 extension (built in since 3.38.0) lets you index and query JSONB columns with `json_extract(col, '$.field')`. To create a partial index over a JSON field: `CREATE INDEX idx ON memories (json_extract(metadata, '$.scope')) WHERE json_extract(metadata, '$.scope') IS NOT NULL;`. Use `json_each` for array fields." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1109.2231, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 757, + "mcp_result_bytes": 838, + "wire_bytes": 875, + "reported_used_tokens": 838, + "working_set_bytes": 943452160, + "peak_working_set_bytes": 944357376 + }, + { + "query": "prepare() vs prepare_cached() in rusqlite hot insert loop", + "ranked": [ + "sqlite-prepared-stmt-cache" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15E5EKX726V2VF181EXPF", + "id": "01M1X19P19KP6WK08F4WDA1M75", + "kind": "memory", + "score": 0.9999525547027588, + "summary": "project:fact - [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1073.8542, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 689, + "mcp_result_bytes": 770, + "wire_bytes": 807, + "reported_used_tokens": 770, + "working_set_bytes": 943464448, + "peak_working_set_bytes": 944365568 + }, + { + "query": "speed up bulk memory ingest by caching SQL statements", + "ranked": [ + "sqlite-prepared-stmt-cache" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15E5EKX726V2VF181EXPF", + "id": "01M1X19Q1YP9AKQ25BNHZ8ZSZY", + "kind": "memory", + "score": 0.6780275106430054, + "summary": "project:fact - [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1361.3917, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 688, + "mcp_result_bytes": 769, + "wire_bytes": 806, + "reported_used_tokens": 769, + "working_set_bytes": 943464448, + "peak_working_set_bytes": 944365568 + }, + { + "query": "partial index on deleted_at IS NULL for faster active memory queries", + "ranked": [ + "sqlite-partial-index", + "sqlite-json1-extract" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15E6AEGVGQ3SQR465FW1J", + "id": "01M1X19RCFZCGXJMKDA7R6DH1S", + "kind": "memory", + "score": 0.999954104423523, + "summary": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query \u2014 the planner uses the partial index only when the WHERE clause matches." + }, + { + "expansion_handle": "memory:01M1X15E4EPNPQD78NJEVF2M15", + "id": "01M1X19RCFTZ4EJ8CTVKKQ50R9", + "kind": "memory", + "score": 0.5821903347969055, + "summary": "project:fact - [tags: sqlite json1 json_extract rusqlite] SQLite's json1 extension (built in since 3.38.0) lets you index and query JSONB columns with `json_extract(col, '$.field')`. To create a partial index over a JSON field: `CREATE INDEX idx ON memories (json_extract(metadata, '$.scope')) WHERE json_extract(metadata, '$.scope') IS NOT NULL;`. Use `json_each` for array fields." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1076.2499, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1323, + "mcp_result_bytes": 1422, + "wire_bytes": 1459, + "reported_used_tokens": 1422, + "working_set_bytes": 943472640, + "peak_working_set_bytes": 944369664 + }, + { + "query": "the brain query is slow because it scans all rows including soft-deleted ones", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1093.2542, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 943505408, + "peak_working_set_bytes": 944418816 + }, + { + "query": "Cargo.lock changed unexpectedly after adding a new workspace crate", + "ranked": [ + "cargo-lockfile-drift", + "cargo-feature-unification-embeddings" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15E77TZ09VG4QKMKT0Z1H", + "id": "01M1X19TGA51Y4QHA2S36MA0SD", + "kind": "memory", + "score": 0.9998825788497924, + "summary": "project:fact - [2026-09-07] [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this \u2014 it errors on any lockfile diff." + }, + { + "expansion_handle": "memory:01M1X15D8D3WDFPKKV2KCWRK3G", + "id": "01M1X19TGAVQHTKXVQTSRY3H54", + "kind": "memory", + "score": 0.9923800230026244, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1113.1211, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1698, + "mcp_result_bytes": 1801, + "wire_bytes": 1838, + "reported_used_tokens": 1801, + "working_set_bytes": 943738880, + "peak_working_set_bytes": 944652288 + }, + { + "query": "how do I prevent CI from accepting a modified lockfile silently?", + "ranked": [ + "cargo-lockfile-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15E77TZ09VG4QKMKT0Z1H", + "id": "01M1X19VK8G54V7EFY1GW7GTB5", + "kind": "memory", + "score": 0.770147979259491, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this \u2014 it errors on any lockfile diff." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1115.4255, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 764, + "mcp_result_bytes": 845, + "wire_bytes": 882, + "reported_used_tokens": 845, + "working_set_bytes": 943738880, + "peak_working_set_bytes": 944652288 + }, + { + "query": "build.rs reruns on every incremental build even when nothing changed", + "ranked": [ + "cargo-build-script-rerun" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15E83CSQKH971SR4D1YWD", + "id": "01M1X19WP5TW8F7TW3QAFEEMVG", + "kind": "memory", + "score": 0.9999223947525024, + "summary": "project:fact - [2026-09-07] [tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1413.7959, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 698, + "mcp_result_bytes": 779, + "wire_bytes": 816, + "reported_used_tokens": 779, + "working_set_bytes": 944001024, + "peak_working_set_bytes": 944914432 + }, + { + "query": "incremental cargo build is slow because build script runs every time", + "ranked": [ + "cargo-build-script-rerun" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15E83CSQKH971SR4D1YWD", + "id": "01M1X19Y33KTA34Q56QJ3YXF6S", + "kind": "memory", + "score": 0.9998290538787842, + "summary": "project:fact - [tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1240.7950999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 685, + "mcp_result_bytes": 766, + "wire_bytes": 803, + "reported_used_tokens": 766, + "working_set_bytes": 944259072, + "peak_working_set_bytes": 945168384 + }, + { + "query": "a dev-dependency is activating an embeddings feature in my production build", + "ranked": [ + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15E933WNR915BWNY6P1NR", + "id": "01M1X19Z9AE0BARAV31EYARDW3", + "kind": "memory", + "score": 0.999002993106842, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1230.8726000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 930, + "mcp_result_bytes": 1011, + "wire_bytes": 1048, + "reported_used_tokens": 1011, + "working_set_bytes": 944259072, + "peak_working_set_bytes": 945168384 + }, + { + "query": "how do I prevent a test-only feature from bleeding into the non-test compilation?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1295.1396, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 944259072, + "peak_working_set_bytes": 945168384 + }, + { + "query": "linker errors in target/ caused by antivirus holding the exe file", + "ranked": [ + "windows-file-locking-av" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15F4AFCKHQ6B41D25SWN4", + "id": "01M1X1A1QX40AXKCY4KBVV9876", + "kind": "memory", + "score": 0.9991186261177064, + "summary": "project:fact - [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1409.3704, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 756, + "mcp_result_bytes": 837, + "wire_bytes": 874, + "reported_used_tokens": 837, + "working_set_bytes": 944259072, + "peak_working_set_bytes": 945168384 + }, + { + "query": "Access is denied (os error 5) when linking on Windows \u2014 how do I fix this?", + "ranked": [ + "windows-file-locking-av" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15F4AFCKHQ6B41D25SWN4", + "id": "01M1X1A342PH0B6RMKKK4MCKCV", + "kind": "memory", + "score": 0.9984531402587892, + "summary": "project:fact - [2026-09-07] [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1088.5067, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 769, + "mcp_result_bytes": 850, + "wire_bytes": 887, + "reported_used_tokens": 850, + "working_set_bytes": 944263168, + "peak_working_set_bytes": 945168384 + }, + { + "query": "incremental build broke with a type mismatch after switching branches", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15EB0QZ0G0J4SXC242WJ7", + "id": "01M1X1A4644BC7BBVXXMHBX7ET", + "kind": "memory", + "score": 0.7982672452926636, + "summary": "project:fact - [2026-09-07] [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1087.0258, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 890, + "mcp_result_bytes": 971, + "wire_bytes": 1008, + "reported_used_tokens": 971, + "working_set_bytes": 944517120, + "peak_working_set_bytes": 945426432 + }, + { + "query": "cargo reports a type error that references a type not in the codebase", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15EB0QZ0G0J4SXC242WJ7", + "id": "01M1X1A5824TGMP19G0QM7H7FV", + "kind": "memory", + "score": 0.7982914447784424, + "summary": "project:fact - [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1249.3783, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 877, + "mcp_result_bytes": 958, + "wire_bytes": 995, + "reported_used_tokens": 958, + "working_set_bytes": 944517120, + "peak_working_set_bytes": 945434624 + }, + { + "query": "compile fastembed at O2 in debug builds to avoid slow embedding inference", + "ranked": [ + "cargo-profile-override", + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15EBTY4FPHXMVSCFFTB73", + "id": "01M1X1A6FDVE961374TRDY78QS", + "kind": "memory", + "score": 0.9999233484268188, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1X15GPD31A6FPKDTDB60673", + "id": "01M1X1A6FD6W9B5WNGQC2Y8RH1", + "kind": "memory", + "score": 0.7583951950073242, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1241.5555, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1323, + "mcp_result_bytes": 1422, + "wire_bytes": 1459, + "reported_used_tokens": 1422, + "working_set_bytes": 944525312, + "peak_working_set_bytes": 945438720 + }, + { + "query": "override compilation profile for a single crate in a Cargo workspace", + "ranked": [ + "cargo-profile-override", + "cargo-patch-section", + "cargo-target-dir-sharing", + "cargo-lockfile-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15EBTY4FPHXMVSCFFTB73", + "id": "01M1X1A7P14N3FVXMHMWW5BK8P", + "kind": "memory", + "score": 0.9999361038208008, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1X15ECTTF4R566QQ07DFFTA", + "id": "01M1X1A7P11WBAGQY4YPCXMC02", + "kind": "memory", + "score": 0.9970531463623048, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace \u2014 including transitive deps \u2014 that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1X15EA3ZV3KE9WW80Y3YFGC", + "id": "01M1X1A7P1Y5XTKCSC2K4AVVNF", + "kind": "memory", + "score": 0.9418804049491882, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps \u2014 use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + }, + { + "expansion_handle": "memory:01M1X15E77TZ09VG4QKMKT0Z1H", + "id": "01M1X1A7P1DR98YZHXPANAFVK0", + "kind": "memory", + "score": 0.6213672161102295, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this \u2014 it errors on any lockfile diff." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1246.4716, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2516, + "mcp_result_bytes": 2655, + "wire_bytes": 2692, + "reported_used_tokens": 2655, + "working_set_bytes": 944525312, + "peak_working_set_bytes": 945442816 + }, + { + "query": "[patch.crates-io] workspace dependency override", + "ranked": [ + "cargo-patch-section", + "cargo-dev-dep-leak", + "cargo-profile-override" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15ECTTF4R566QQ07DFFTA", + "id": "01M1X1A8X6N7NTP4VYXP3903WT", + "kind": "memory", + "score": 0.9999796152114868, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace \u2014 including transitive deps \u2014 that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1X15E933WNR915BWNY6P1NR", + "id": "01M1X1A8X6PE8VA2EXTKA0MRCX", + "kind": "memory", + "score": 0.7515549063682556, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + }, + { + "expansion_handle": "memory:01M1X15EBTY4FPHXMVSCFFTB73", + "id": "01M1X1A8X68FQ28NC9AY49GJBG", + "kind": "memory", + "score": 0.6568455696105957, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1043.1463, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1917, + "mcp_result_bytes": 2038, + "wire_bytes": 2075, + "reported_used_tokens": 2038, + "working_set_bytes": 944525312, + "peak_working_set_bytes": 945442816 + }, + { + "query": "pin minimum supported Rust version in Cargo.toml", + "ranked": [ + "cargo-msrv" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15EDPST4QNV7CFTPT7T1H", + "id": "01M1X1A9XVFHVQ4CVXRP1ZT93T", + "kind": "memory", + "score": 0.9998986721038818, + "summary": "project:fact - [tags: cargo rust msrv edition compatibility] Set `rust-version` in each `Cargo.toml` to declare the minimum supported Rust version (MSRV). Cargo enforces this with `--check`: `cargo check` fails if the toolchain is older than `rust-version`. Keep MSRV as old as your oldest supported deployment target." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1019.1306, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 693, + "mcp_result_bytes": 774, + "wire_bytes": 811, + "reported_used_tokens": 774, + "working_set_bytes": 944537600, + "peak_working_set_bytes": 945455104 + }, + { + "query": "Windows path over 260 characters causes OS error 3 during Cargo build", + "ranked": [ + "windows-long-paths", + "windows-file-locking-av" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15F3D1GXX208SJEBB4Y8Z", + "id": "01M1X1AAY2F3A9Q3RZ46AT10AA", + "kind": "memory", + "score": 0.9998657703399658, + "summary": "project:fact - [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe." + }, + { + "expansion_handle": "memory:01M1X15F4AFCKHQ6B41D25SWN4", + "id": "01M1X1AAY2YFGCXFRC1CHG3DW5", + "kind": "memory", + "score": 0.6231384873390198, + "summary": "project:fact - [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1018.246, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1297, + "mcp_result_bytes": 1406, + "wire_bytes": 1443, + "reported_used_tokens": 1406, + "working_set_bytes": 944545792, + "peak_working_set_bytes": 945455104 + }, + { + "query": "how do I enable long file paths for Cargo on Windows?", + "ranked": [ + "windows-long-paths", + "windows-registry-rust" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15F3D1GXX208SJEBB4Y8Z", + "id": "01M1X1ABXBVP6GKGYNXXRDCBB9", + "kind": "memory", + "score": 0.9999781847000122, + "summary": "project:fact - [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe." + }, + { + "expansion_handle": "memory:01M1X15F9K999S54DC752Q7Z9W", + "id": "01M1X1ABXB8JXTKZ5GKE9DY961", + "kind": "memory", + "score": 0.7387577891349792, + "summary": "project:fact - [tags: windows registry rust winreg read write] Reading and writing the Windows registry from Rust requires the `winreg` crate. Open a key with `RegKey::predef(HKEY_LOCAL_MACHINE).open_subkey_with_flags(path, KEY_READ)` \u2014 use `KEY_READ` for reads and `KEY_READ | KEY_WRITE` for writes (NOT `KEY_ALL_ACCESS`, which requires admin). To set a DWORD value: `key.set_value(\"LongPathsEnabled\", &1u32)`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1378.4621000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1332, + "mcp_result_bytes": 1445, + "wire_bytes": 1482, + "reported_used_tokens": 1445, + "working_set_bytes": 944545792, + "peak_working_set_bytes": 945459200 + }, + { + "query": "intermittent sharing violation errors when Rust linker writes the exe on Windows", + "ranked": [ + "windows-file-locking-av" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15F4AFCKHQ6B41D25SWN4", + "id": "01M1X1AD8D1K55PAZ9A7KZ1X4N", + "kind": "memory", + "score": 0.9999388456344604, + "summary": "project:fact - [2026-09-07] [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1013.9134999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 769, + "mcp_result_bytes": 850, + "wire_bytes": 887, + "reported_used_tokens": 850, + "working_set_bytes": 944553984, + "peak_working_set_bytes": 945463296 + }, + { + "query": "Rust walkdir follows junctions differently from symlinks on Windows", + "ranked": [ + "windows-junctions-vs-symlinks" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15F7DFSTM082C4W5GJAYV", + "id": "01M1X1AE86W8NNB42PME90XCF5", + "kind": "memory", + "score": 0.9999210834503174, + "summary": "project:fact - [tags: windows junctions symlinks rust std::fs] On Windows, directory junctions (NTFS reparse points) behave like symlinks for directory traversal but `std::fs::symlink_metadata` returns `FileType::is_symlink() = false` for junctions (only true for regular symlinks). Use `std::fs::read_link` \u2014 it succeeds for both junction and symlink. `walkdir` crate's `follow_links` follows both, but its `is_symlink()` method correctly reports only actual symlinks." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1071.5166000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 845, + "mcp_result_bytes": 926, + "wire_bytes": 963, + "reported_used_tokens": 926, + "working_set_bytes": 944553984, + "peak_working_set_bytes": 945463296 + }, + { + "query": "UNC path canonicalize returns verbatim prefix \u2014 how do I strip it?", + "ranked": [ + "windows-unc-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15F56H1VHB746XJJPQBKW", + "id": "01M1X1AF9KCGE6BDNFEFVQ5J0V", + "kind": "memory", + "score": 0.999847412109375, + "summary": "project:fact - [tags: windows unc-paths rust std::fs] Windows UNC paths (`\\\\server\\share\\...`) are not supported by most Rust `std::fs` operations unless passed through the extended-length prefix `\\\\?\\UNC\\server\\share\\...`. `std::path::Path::new(\"\\\\\\\\server\\\\share\")` works for basic operations but breaks with `canonicalize()` which returns the verbatim prefix form. When walking directory trees that may start on UNC paths, use the `dunce` crate to strip the verbatim prefix before comparing or displaying paths." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1400.4905, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 907, + "mcp_result_bytes": 1024, + "wire_bytes": 1061, + "reported_used_tokens": 1024, + "working_set_bytes": 944553984, + "peak_working_set_bytes": 945475584 + }, + { + "query": "UTF-8 memory text prints as mojibake in the Windows console", + "ranked": [ + "windows-console-encoding" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15F6ANEY0PPNCHRC9S221", + "id": "01M1X1AGNKT89PR7Q84WMPA8QY", + "kind": "memory", + "score": 0.9999754428863524, + "summary": "project:fact - [tags: windows console encoding utf8 rust] Windows console code page defaults to the system ANSI code page (usually CP1252 or CP932), not UTF-8. Rust's `println!` writes UTF-8 bytes which display as mojibake in a non-UTF-8 console. Fix at process startup: call `SetConsoleOutputCP(65001)` via `winapi` or `windows-sys`, or set `PYTHONUTF8=1`/`RUST_LOG` before launch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1049.4703000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 757, + "mcp_result_bytes": 838, + "wire_bytes": 875, + "reported_used_tokens": 838, + "working_set_bytes": 944553984, + "peak_working_set_bytes": 945475584 + }, + { + "query": "process exit code is 4294967295 instead of -1 on Windows", + "ranked": [ + "windows-exit-codes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15F8GD5YB33CYZNZK6NS1", + "id": "01M1X1AHP99R6ZKPX8SZWY15BH", + "kind": "memory", + "score": 0.9999797344207764, + "summary": "project:fact - [tags: windows exit-codes rust process child] On Windows, process exit codes are 32-bit unsigned integers (DWORD). Rust's `ExitStatus::code()` returns `Option` \u2014 it's `None` if the process was killed by a signal (which Windows doesn't use; instead, TerminateProcess with a code). Conventional codes: 0=success, 1=generic error, 0xC0000005=access violation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1165.4583, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 753, + "mcp_result_bytes": 834, + "wire_bytes": 871, + "reported_used_tokens": 834, + "working_set_bytes": 944553984, + "peak_working_set_bytes": 945475584 + }, + { + "query": "tokenizer.json must match the ONNX model \u2014 what breaks if it doesn't?", + "ranked": [ + "onnx-tokenizer-mismatch" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15FAQG6ND17FDMK3XVZ3Z", + "id": "01M1X1AJTR7W4QW5EZ7WAZEK32", + "kind": "memory", + "score": 0.9999275207519532, + "summary": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly \u2014 specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings \u2014 cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1119.0776, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 959, + "mcp_result_bytes": 1040, + "wire_bytes": 1077, + "reported_used_tokens": 1040, + "working_set_bytes": 944807936, + "peak_working_set_bytes": 945717248 + }, + { + "query": "embedding quality degraded after I swapped in the INT8 quantized model", + "ranked": [ + "onnx-quantization-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15FBQ9F9C7XM5PYQHS0MG", + "id": "01M1X1AKXP66DNB8KZTKZQP3AK", + "kind": "memory", + "score": 0.9944571256637572, + "summary": "project:fact - [2026-09-07] [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals \u2014 cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1100.0018, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 991, + "mcp_result_bytes": 1072, + "wire_bytes": 1109, + "reported_used_tokens": 1072, + "working_set_bytes": 944807936, + "peak_working_set_bytes": 945721344 + }, + { + "query": "missing attention mask causes low-norm embeddings in batch inference", + "ranked": [ + "onnx-batch-padding" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15FCN2QMY1SKV5JEYZ6YG", + "id": "01M1X1AN0A0J05ZPJ8MJQKZ8HN", + "kind": "memory", + "score": 0.9998724460601808, + "summary": "project:fact - [tags: onnx batch padding attention-mask embeddings] When running batch inference with an ONNX model, all inputs in the batch must be padded to the same sequence length. The `attention_mask` tensor marks which tokens are real (1) and which are padding (0). Failing to pass `attention_mask` causes the model to average-pool over padding tokens, producing systematically lower-norm embeddings." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1084.9271, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 781, + "mcp_result_bytes": 862, + "wire_bytes": 899, + "reported_used_tokens": 862, + "working_set_bytes": 944816128, + "peak_working_set_bytes": 945721344 + }, + { + "query": "ONNX model download fails in a Docker container with no home directory", + "ranked": [ + "onnx-model-cache-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15FDNCVMMGBZ6T521FZ1G", + "id": "01M1X1AP2A7DPVD4CWTPZKAY23", + "kind": "memory", + "score": 0.9924855828285216, + "summary": "project:fact - [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1118.0368999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 755, + "mcp_result_bytes": 838, + "wire_bytes": 875, + "reported_used_tokens": 838, + "working_set_bytes": 944816128, + "peak_working_set_bytes": 945721344 + }, + { + "query": "fastembed cache path environment variable for CI", + "ranked": [ + "onnx-model-cache-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15FDNCVMMGBZ6T521FZ1G", + "id": "01M1X1AQ4VK70J3QYYS4KH6SMB", + "kind": "memory", + "score": 0.9999436140060424, + "summary": "project:fact - [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1057.4575, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 756, + "mcp_result_bytes": 839, + "wire_bytes": 876, + "reported_used_tokens": 839, + "working_set_bytes": 944816128, + "peak_working_set_bytes": 945721344 + }, + { + "query": "cosine similarity vs dot product for L2-normalized embedding vectors", + "ranked": [ + "onnx-cosine-vs-dot" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15FEHFW8WM24R2JYYW6NZ", + "id": "01M1X1AR62T3ESDJRFR74MBK74", + "kind": "memory", + "score": 0.9999769926071168, + "summary": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing \u2014 double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1036.2168000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 765, + "mcp_result_bytes": 846, + "wire_bytes": 883, + "reported_used_tokens": 846, + "working_set_bytes": 944828416, + "peak_working_set_bytes": 945737728 + }, + { + "query": "stored vectors have wrong dimension after switching embedding models", + "ranked": [ + "onnx-dim-mismatch", + "onnx-cosine-vs-dot" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15FFFCT7F77MVS57G64GD", + "id": "01M1X1AS6AAVPQXHFCN91A9Y74", + "kind": "memory", + "score": 0.9999632835388184, + "summary": "project:fact - [2026-09-07] [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results \u2014 the ANN index shape mismatch isn't always caught at runtime." + }, + { + "expansion_handle": "memory:01M1X15FEHFW8WM24R2JYYW6NZ", + "id": "01M1X1AS6A539KFMVV52WVWS9K", + "kind": "memory", + "score": 0.8715931177139282, + "summary": "project:fact - [2026-09-07] [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing \u2014 double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 939.0842, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1305, + "mcp_result_bytes": 1404, + "wire_bytes": 1441, + "reported_used_tokens": 1404, + "working_set_bytes": 944828416, + "peak_working_set_bytes": 945737728 + }, + { + "query": "E5 and Instructor models need a query prefix \u2014 what happens without it?", + "ranked": [ + "onnx-prefix-instructions", + "onnx-cosine-vs-dot" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15FK1HDGS4C7FK359MDBN", + "id": "01M1X1AT41ZWVZRGTV222N2ES3", + "kind": "memory", + "score": 0.9999685287475586, + "summary": "project:fact - [tags: onnx embeddings prefix instruction e5 query passage] E5 and Instructor family models require a text prefix on BOTH query and passage sides to produce meaningful similarities: query prefix `\"query: \"`, passage prefix `\"passage: \"`. Omitting the prefix can drop MRR by 10-15 percentage points on out-of-domain datasets. Check the model's README for the exact prefix string \u2014 it varies by model family." + }, + { + "expansion_handle": "memory:01M1X15FEHFW8WM24R2JYYW6NZ", + "id": "01M1X1AT41XBYGEJC9C194S56S", + "kind": "memory", + "score": 0.5567834973335266, + "summary": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing \u2014 double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1380.4859000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1339, + "mcp_result_bytes": 1446, + "wire_bytes": 1483, + "reported_used_tokens": 1446, + "working_set_bytes": 944828416, + "peak_working_set_bytes": 945737728 + }, + { + "query": "ORT thread pool contention when running multiple bench processes in parallel", + "ranked": [ + "onnx-ort-threading" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15FM575W5FVQQGC9YSPHV", + "id": "01M1X1AVFE6X1P2QXYNSM1HKFM", + "kind": "memory", + "score": 0.9999712705612184, + "summary": "project:fact - [2026-09-07] [tags: onnx ort thread-pool parallelism cpu] ORT (ONNX Runtime) creates its own inter-op and intra-op thread pools. In a multi-process bench setup, each child inherits these pools and they compete for CPU cores. Set `SessionOptionsBuilder::with_intra_threads(1).with_inter_threads(1)` if you're running many parallel bench processes \u2014 this sacrifices per-inference throughput for lower contention." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1127.7118, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 802, + "mcp_result_bytes": 883, + "wire_bytes": 920, + "reported_used_tokens": 883, + "working_set_bytes": 944832512, + "peak_working_set_bytes": 945741824 + }, + { + "query": "git worktrees share the .kimetsu brain \u2014 how do I isolate test runs?", + "ranked": [ + "git-worktree-brain-isolation", + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15FN49X5ZX6NQND34AZTX", + "id": "01M1X1AWJ6PXB6DS86R0M6XXE2", + "kind": "memory", + "score": 0.9999784231185912, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root \u2014 if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + }, + { + "expansion_handle": "memory:01M1X15DHYTJCJX4D45ABR9SVG", + "id": "01M1X1AWJ7DSG9DW7AZ4MQDT2P", + "kind": "memory", + "score": 0.9857950210571288, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1083.09, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1435, + "mcp_result_bytes": 1534, + "wire_bytes": 1571, + "reported_used_tokens": 1534, + "working_set_bytes": 944832512, + "peak_working_set_bytes": 945745920 + }, + { + "query": "when is it safe to use --no-verify on git commit?", + "ranked": [ + "git-hooks-bypass" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15FP4GQFXKQM1SMJ05X2N", + "id": "01M1X1AXM5TPF0RF0R3DZT185M", + "kind": "memory", + "score": 0.99863463640213, + "summary": "project:fact - [2026-09-07] [tags: git hooks bypass pre-commit skip] `git commit --no-verify` skips ALL hooks (pre-commit and commit-msg). Never use this in shared team repos where hooks enforce quality gates (lint, tests, memory harvest). Instead, fix the failing hook." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1116.3465999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 645, + "mcp_result_bytes": 726, + "wire_bytes": 763, + "reported_used_tokens": 726, + "working_set_bytes": 944832512, + "peak_working_set_bytes": 945745920 + }, + { + "query": "reduce clone size and bandwidth for server-side repo ingest", + "ranked": [ + "git-sparse-checkout", + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15FQ3ZFGWA8Q1Z4H69EZ4", + "id": "01M1X1AYQBFEPF7P73HNQ60G5V", + "kind": "memory", + "score": 0.9952669143676758, + "summary": "project:fact - [tags: git sparse-checkout partial-clone bandwidth] `git sparse-checkout init --cone` combined with `git clone --filter=blob:none` (partial clone) fetches only the commit graph and tree objects, not blobs. Individual blobs are fetched on demand when accessed. This cuts clone time for large repos from minutes to seconds." + }, + { + "expansion_handle": "memory:01M1X15D4FA0TN48H977JEDBCS", + "id": "01M1X1AYQBF2ZBT06XJ4MPDN2F", + "kind": "memory", + "score": 0.5591859817504883, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1483.3889, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1744, + "mcp_result_bytes": 1843, + "wire_bytes": 1880, + "reported_used_tokens": 1843, + "working_set_bytes": 944959488, + "peak_working_set_bytes": 945864704 + }, + { + "query": "spurious diffs from Windows CRLF line ending conversion in git", + "ranked": [ + "git-line-endings-windows" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15FR5HBX5P0HYVG6TN04V", + "id": "01M1X1B05WQCXRB749QKKM1RS8", + "kind": "memory", + "score": 0.9999781847000122, + "summary": "project:fact - [tags: git line-endings windows crlf autocrlf] On Windows, `core.autocrlf=true` (git's default for Windows installs) converts LF to CRLF on checkout and CRLF to LF on commit. This causes spurious diffs when files are edited on Windows then committed \u2014 the content is identical but the line endings differ in the index vs the working tree. Fix: set `core.autocrlf=false` and `.gitattributes` with `* text=auto eol=lf` for the repo." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1447.585, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 940, + "reported_used_tokens": 903, + "working_set_bytes": 945213440, + "peak_working_set_bytes": 946114560 + }, + { + "query": "git submodule always gets the wrong commit in CI", + "ranked": [ + "git-submodule-pinning", + "git-hooks-bypass" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15FS59X3BFZNNCXVKT8T8", + "id": "01M1X1B1JS4YQKKTQJ18W0WQJ8", + "kind": "memory", + "score": 0.9990686774253844, + "summary": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip \u2014 this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version." + }, + { + "expansion_handle": "memory:01M1X15FP4GQFXKQM1SMJ05X2N", + "id": "01M1X1B1JSD8CQCXB8BRDKWJG8", + "kind": "memory", + "score": 0.7485930919647217, + "summary": "project:fact - [tags: git hooks bypass pre-commit skip] `git commit --no-verify` skips ALL hooks (pre-commit and commit-msg). Never use this in shared team repos where hooks enforce quality gates (lint, tests, memory harvest). Instead, fix the failing hook." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1084.2857999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1157, + "mcp_result_bytes": 1256, + "wire_bytes": 1293, + "reported_used_tokens": 1256, + "working_set_bytes": 945278976, + "peak_working_set_bytes": 946188288 + }, + { + "query": "accidentally ran git reset --hard and lost commits \u2014 can I recover?", + "ranked": [ + "git-reflog-rescue" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15FT4V885GF8WS7X3HBC8", + "id": "01M1X1B2MGVF278C1AZ4FVQHWW", + "kind": "memory", + "score": 0.9999775886535645, + "summary": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone \u2014 they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only \u2014 remote reflog is not accessible via normal git commands." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1436.354, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 762, + "mcp_result_bytes": 843, + "wire_bytes": 880, + "reported_used_tokens": 843, + "working_set_bytes": 945291264, + "peak_working_set_bytes": 946188288 + }, + { + "query": "blocking SQLite call from an async tokio handler causes latency spikes", + "ranked": [ + "tokio-blocking-in-async", + "tokio-runtime-in-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15FV23NVFH7CBKPA19KAM", + "id": "01M1X1B41K43SBGC2BXZEDD7DC", + "kind": "memory", + "score": 0.9999537467956544, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + }, + { + "expansion_handle": "memory:01M1X15FW4W876QNP42FE7PQYT", + "id": "01M1X1B41K169YNTBV6JW04A3A", + "kind": "memory", + "score": 0.7837615609169006, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1052.4402, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1370, + "mcp_result_bytes": 1477, + "wire_bytes": 1514, + "reported_used_tokens": 1477, + "working_set_bytes": 945545216, + "peak_working_set_bytes": 946450432 + }, + { + "query": "Cannot start a runtime from within a runtime in a tokio test", + "ranked": [ + "tokio-runtime-in-tests", + "tokio-blocking-in-async" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15FW4W876QNP42FE7PQYT", + "id": "01M1X1B52EGRQB1CV9AKAJP9SH", + "kind": "memory", + "score": 0.9999808073043824, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + }, + { + "expansion_handle": "memory:01M1X15FV23NVFH7CBKPA19KAM", + "id": "01M1X1B52EX7B47F0375DZ90ZC", + "kind": "memory", + "score": 0.7143720388412476, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1297.0628, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1370, + "mcp_result_bytes": 1477, + "wire_bytes": 1514, + "reported_used_tokens": 1477, + "working_set_bytes": 945557504, + "peak_working_set_bytes": 946466816 + }, + { + "query": "tokio select cancels the other branch and loses the value in the channel", + "ranked": [ + "tokio-select-cancellation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15FXC92XAC3HKBQ7Q4CHJ", + "id": "01M1X1B6AX2VJFE7428WQW6Y0A", + "kind": "memory", + "score": 0.9996563196182252, + "summary": "project:fact - [tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1079.9452, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 751, + "mcp_result_bytes": 832, + "wire_bytes": 869, + "reported_used_tokens": 832, + "working_set_bytes": 945561600, + "peak_working_set_bytes": 946475008 + }, + { + "query": "mpsc channel backpressure causing senders to stall", + "ranked": [ + "tokio-channel-backpressure" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15FYESE8NP9J5GDJD8ARJ", + "id": "01M1X1B7CQSTANGAGBVAHQ3GPV", + "kind": "memory", + "score": 0.999940037727356, + "summary": "project:fact - [tags: tokio mpsc channel backpressure async rust] `tokio::sync::mpsc::channel(N)` with a bounded buffer provides backpressure: senders block when the buffer is full. This prevents unbounded memory growth but can cause sender tasks to stall. Choosing N: too small causes frequent backpressure (throughput drops); too large defeats the purpose." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1140.1297, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 732, + "mcp_result_bytes": 813, + "wire_bytes": 850, + "reported_used_tokens": 813, + "working_set_bytes": 945565696, + "peak_working_set_bytes": 946475008 + }, + { + "query": "overhead from calling spawn_blocking on every single query request", + "ranked": [ + "tokio-spawn-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15FZF9QWQTQS7XGKCV214", + "id": "01M1X1B8GFKWNMVP72MEJT3QHT", + "kind": "memory", + "score": 0.9911772012710572, + "summary": "project:fact - [tags: tokio spawn_blocking thread-pool rust blocking] `tokio::task::spawn_blocking` places work on a dedicated blocking thread pool (default up to 512 threads, configurable via `Builder::max_blocking_threads`). Each call creates or reuses a thread \u2014 there's no true pooling, threads may be created on demand. For many short-duration blocking calls (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1076.7638, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 746, + "mcp_result_bytes": 827, + "wire_bytes": 864, + "reported_used_tokens": 827, + "working_set_bytes": 945602560, + "peak_working_set_bytes": 946507776 + }, + { + "query": "axum server panics during shutdown because the DB pool is already closed", + "ranked": [ + "tokio-shutdown-ordering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15G54KE7TFDARK6V4JN17", + "id": "01M1X1B9J6AM2Q4ZMGS0GFKJQM", + "kind": "memory", + "score": 0.9920267462730408, + "summary": "project:fact - [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries \u2014 the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1546.1842, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 933, + "mcp_result_bytes": 1014, + "wire_bytes": 1051, + "reported_used_tokens": 1014, + "working_set_bytes": 945602560, + "peak_working_set_bytes": 946515968 + }, + { + "query": "reqwest Client created per-request defeats connection pooling", + "ranked": [ + "http-connection-pooling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15G65XV4C14WGXGZ8A9ZZ", + "id": "01M1X1BB2ZZCXTXC6JWDKTSQQX", + "kind": "memory", + "score": 0.9999794960021972, + "summary": "project:fact - [tags: http reqwest connection-pool keep-alive rust] reqwest's `Client` holds a connection pool; always create ONE `Client` instance and clone it for each handler \u2014 cloning is cheap (Arc under the hood). Creating a `Client::new()` per request defeats connection pooling and causes TCP connection exhaustion under load. The default pool settings: max_idle_per_host=usize::MAX (unbounded), idle_timeout=90s." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1064.4217, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 797, + "mcp_result_bytes": 878, + "wire_bytes": 915, + "reported_used_tokens": 878, + "working_set_bytes": 945618944, + "peak_working_set_bytes": 946515968 + }, + { + "query": "LLM request times out during streaming \u2014 which timeout setting applies?", + "ranked": [ + "http-timeout-layering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15G795PVGXDC2R14Q0EW1", + "id": "01M1X1BC3JQWK97MHC6A534X6Q", + "kind": "memory", + "score": 0.999026656150818, + "summary": "project:fact - [tags: http reqwest timeout connect read total rust] reqwest has three distinct timeout knobs: `connect_timeout`, `read_timeout`, and `timeout` (total). They compose: if all three are set, the request fails at whichever fires first. For LLM API calls with streaming responses, `read_timeout` must be larger than the slowest expected token (often 30-60s) while `connect_timeout` can be tight (3-5s)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1174.9269, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 788, + "mcp_result_bytes": 869, + "wire_bytes": 906, + "reported_used_tokens": 869, + "working_set_bytes": 945635328, + "peak_working_set_bytes": 946548736 + }, + { + "query": "how do I safely retry a POST to the LLM API without creating duplicates?", + "ranked": [ + "http-retry-idempotency" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15G8CEZPE9SD3AC7AB95C", + "id": "01M1X1BD8A64YYB269S51CA90N", + "kind": "memory", + "score": 0.9997218251228333, + "summary": "project:fact - [tags: http retry idempotency post put reqwest] Only retry idempotent requests automatically. GET, HEAD, PUT, DELETE are idempotent. POST is NOT \u2014 retrying a POST may create duplicate resources." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1343.1972, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 585, + "mcp_result_bytes": 666, + "wire_bytes": 703, + "reported_used_tokens": 666, + "working_set_bytes": 945635328, + "peak_working_set_bytes": 946548736 + }, + { + "query": "custom enterprise root CA not trusted by rustls on Windows", + "ranked": [ + "http-tls-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15G9ERJZ5NKP0NN0RXD3Q", + "id": "01M1X1BEJEB2ATQ05MDJA78D50", + "kind": "memory", + "score": 0.9999604225158693, + "summary": "project:fact - [tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle \u2014 the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1411.0146000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 780, + "mcp_result_bytes": 861, + "wire_bytes": 898, + "reported_used_tokens": 861, + "working_set_bytes": 945635328, + "peak_working_set_bytes": 946548736 + }, + { + "query": "parsing server-sent events when a single TCP chunk contains a partial SSE frame", + "ranked": [ + "http-streaming-bodies" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15GAEC9TS1JVE2SXPZNZV", + "id": "01M1X1BFYESZNV33YM996A9F2X", + "kind": "memory", + "score": 0.9942779541015624, + "summary": "project:fact - [2026-09-07] [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding \u2014 a chunk may split across frame boundaries." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1077.5667, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 859, + "mcp_result_bytes": 940, + "wire_bytes": 977, + "reported_used_tokens": 940, + "working_set_bytes": 945635328, + "peak_working_set_bytes": 946548736 + }, + { + "query": "reqwest does not use the system proxy settings on Windows", + "ranked": [ + "http-proxy-env", + "http-tls-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15GBJT1853SZ5H7JVGPX1", + "id": "01M1X1BH05GTDRRMYAJTR7AKNW", + "kind": "memory", + "score": 0.9999799728393556, + "summary": "project:fact - [tags: http proxy environment reqwest rust corporate] reqwest respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` environment variables by default (with `default-tls` or `rustls-tls`). In a corporate network, these may redirect traffic through an intercepting proxy that breaks mTLS or adds latency. To disable proxy usage entirely: `reqwest::ClientBuilder::no_proxy()`." + }, + { + "expansion_handle": "memory:01M1X15G9ERJZ5NKP0NN0RXD3Q", + "id": "01M1X1BH05GNW0EW997V6HYH3W", + "kind": "memory", + "score": 0.9782498478889464, + "summary": "project:fact - [tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle \u2014 the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1082.4177, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1310, + "mcp_result_bytes": 1409, + "wire_bytes": 1446, + "reported_used_tokens": 1409, + "working_set_bytes": 945639424, + "peak_working_set_bytes": 946548736 + }, + { + "query": "insta snapshot tests fail in CI because output includes a timestamp", + "ranked": [ + "testing-snapshot-churn" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15GCKQENQB3N0MVQ7YT6Y", + "id": "01M1X1BJ210ETK98PV1NMATFVV", + "kind": "memory", + "score": 0.9999759197235109, + "summary": "project:fact - [tags: testing snapshot insta assert churn rust] Snapshot tests (e.g. with the `insta` crate) fail whenever the output changes, even for intended changes. In CI, they fail loudly; locally, `cargo insta review` walks you through accepting or rejecting changes." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1143.5291, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 649, + "mcp_result_bytes": 730, + "wire_bytes": 767, + "reported_used_tokens": 730, + "working_set_bytes": 945639424, + "peak_working_set_bytes": 946548736 + }, + { + "query": "two test workers writing to the same temp directory path race each other", + "ranked": [ + "testing-temp-dirs-ci" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15GDND956M3ZYSP4VNSR9", + "id": "01M1X1BK61MVR9XPASVGA4KGR4", + "kind": "memory", + "score": 0.9582907557487488, + "summary": "project:fact - [tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1175.2174, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 755, + "mcp_result_bytes": 836, + "wire_bytes": 873, + "reported_used_tokens": 836, + "working_set_bytes": 945639424, + "peak_working_set_bytes": 946548736 + }, + { + "query": "test passes locally but fails on a slow CI runner due to a 100ms sleep", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1321.0464, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 945651712, + "peak_working_set_bytes": 946569216 + }, + { + "query": "proptest found a hash collision in text normalization that example tests missed", + "ranked": [ + "testing-property-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15GFSNF3AWC1KPXMFC32Q", + "id": "01M1X1BNMX085FQM2NEHEA0BXP", + "kind": "memory", + "score": 0.9999779462814332, + "summary": "project:fact - [tags: testing property-based proptest quickcheck rust] Property-based tests (proptest, quickcheck) find edge cases that example-based tests miss. For kimetsu's memory text normalization, proptest found that zero-width joiner characters and right-to-left marks caused hash collisions. Run proptest with `PROPTEST_CASES=10000` in CI for thorough coverage." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1305.1825, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 744, + "mcp_result_bytes": 825, + "wire_bytes": 862, + "reported_used_tokens": 825, + "working_set_bytes": 945651712, + "peak_working_set_bytes": 946569216 + }, + { + "query": "set_var in tests races when cargo test runs them in parallel", + "ranked": [ + "testing-serial-vs-parallel" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15GGQBJJVZJ8XKCQE7B53", + "id": "01M1X1BPWRVVK7VYYCH3800KRP", + "kind": "memory", + "score": 0.9995468258857728, + "summary": "project:fact - [2026-09-07] [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1118.6697000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 832, + "mcp_result_bytes": 913, + "wire_bytes": 950, + "reported_used_tokens": 913, + "working_set_bytes": 945651712, + "peak_working_set_bytes": 946569216 + }, + { + "query": "hardcoded JSON fixtures broke after a schema migration", + "ranked": [ + "testing-fixture-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15GMH0SFNH2G4KTD24XB1", + "id": "01M1X1BR0B763J567FNHH04HSD", + "kind": "memory", + "score": 0.9999451637268066, + "summary": "project:fact - [2026-09-07] [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1110.7994999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 783, + "mcp_result_bytes": 864, + "wire_bytes": 901, + "reported_used_tokens": 864, + "working_set_bytes": 945651712, + "peak_working_set_bytes": 946569216 + }, + { + "query": "debug print in the MCP handler corrupts the JSON-Lines protocol stream", + "ranked": [ + "mcp-stdout-protocol" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15GNDA8AGM499HW3DG48J", + "id": "01M1X1BS2GXRR00BT0WJNZAHBR", + "kind": "memory", + "score": 0.9999716281890868, + "summary": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1364.7691, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 705, + "mcp_result_bytes": 786, + "wire_bytes": 823, + "reported_used_tokens": 786, + "working_set_bytes": 945676288, + "peak_working_set_bytes": 946585600 + }, + { + "query": "kimetsu MCP tool call times out because embedding model is re-initialized every call", + "ranked": [ + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15GPD31A6FPKDTDB60673", + "id": "01M1X1BTDARPZQ0AR3GE1Q9TWG", + "kind": "memory", + "score": 0.9981862902641296, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1014.8843, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 858, + "mcp_result_bytes": 939, + "wire_bytes": 976, + "reported_used_tokens": 939, + "working_set_bytes": 945676288, + "peak_working_set_bytes": 946585600 + }, + { + "query": "env var set after host launch is not visible to the MCP server process", + "ranked": [ + "mcp-env-propagation", + "kimetsu-daemon-lifecycle" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15GQFBSJYRJP99F7WKK7A", + "id": "01M1X1BVCRGGVQYADAPGFZHVPS", + "kind": "memory", + "score": 0.9996464252471924, + "summary": "project:fact - [2026-09-07] [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment \u2014 changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate." + }, + { + "expansion_handle": "memory:01M1X15H82NFSZJR4K18E3AJP1", + "id": "01M1X1BVCRRGHFNCSQ3K8511PX", + "kind": "memory", + "score": 0.8873274922370911, + "summary": "project:fact - [2026-09-07] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1413.1714, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1267, + "mcp_result_bytes": 1366, + "wire_bytes": 1403, + "reported_used_tokens": 1366, + "working_set_bytes": 945676288, + "peak_working_set_bytes": 946593792 + }, + { + "query": "MCP tool call fails because a required field is missing from the JSON input", + "ranked": [ + "mcp-schema-validation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15GRGE1XMYD5WEEJEMEQ7", + "id": "01M1X1BWRY06XEKM4SSCR4T892", + "kind": "memory", + "score": 0.9998551607131958, + "summary": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array \u2014 omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1241.8556, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 799, + "mcp_result_bytes": 880, + "wire_bytes": 917, + "reported_used_tokens": 880, + "working_set_bytes": 945676288, + "peak_working_set_bytes": 946593792 + }, + { + "query": "Claude Code rejects the tool name with a hyphen in it", + "ranked": [ + "mcp-tool-naming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15GSF48E4ZJHKNQKF9X43", + "id": "01M1X1BXZV0TKVT6A9GAT6WKKM", + "kind": "memory", + "score": 0.999729573726654, + "summary": "project:fact - [tags: mcp tool naming convention kimetsu] MCP tool names must be valid identifiers for all host agents. Claude Code restricts tool names to `[a-zA-Z0-9_-]` and max 64 chars. Use `snake_case` (kimetsu_brain_context, kimetsu_brain_record) \u2014 hyphen is technically allowed but some hosts reject it." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1321.99, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 686, + "mcp_result_bytes": 767, + "wire_bytes": 804, + "reported_used_tokens": 767, + "working_set_bytes": 945676288, + "peak_working_set_bytes": 946593792 + }, + { + "query": "MCP response path uses backslashes and the host rejects it", + "ranked": [ + "mcp-transcript-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15GTGH8FTP7JJPHRMWRG6", + "id": "01M1X1BZ98RA5G78GQEZ7RGJDS", + "kind": "memory", + "score": 0.9998076558113098, + "summary": "project:fact - [tags: mcp transcript paths kimetsu hooks runs] kimetsu writes run transcripts to `/.kimetsu/runs//`. The post-session hook reads the latest run's transcript to trigger memory harvest. On Windows, the path uses backslashes internally but the MCP JSON must use forward slashes or the host may reject path-type arguments." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1544.3944, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 724, + "mcp_result_bytes": 805, + "wire_bytes": 842, + "reported_used_tokens": 805, + "working_set_bytes": 945676288, + "peak_working_set_bytes": 946593792 + }, + { + "query": "AWS credentials not found \u2014 which env var does kimetsu read for Bedrock?", + "ranked": [ + "aws-credentials-chain", + "aws-region-resolution", + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15GVJQ1VDPFPZB2X2ZX87", + "id": "01M1X1C0SQ2NVYCYXKCSDREMNJ", + "kind": "memory", + "score": 0.9999332427978516, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + }, + { + "expansion_handle": "memory:01M1X15GWVRFEK49MEY6ZDYBPM", + "id": "01M1X1C0SQR0KZJQ4Z6SQMTDRV", + "kind": "memory", + "score": 0.999756395816803, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X15D9SXHDTY1FDBVM3GCF4", + "id": "01M1X1C0SQD7S855CQ98BVW717", + "kind": "memory", + "score": 0.9983052015304564, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X15DFBBMCQ54NSMT2FRBJC", + "id": "01M1X1C0SQ1262ZV5ZR5BY5E9D", + "kind": "memory", + "score": 0.7727437615394592, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1402.6293, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3454, + "mcp_result_bytes": 3617, + "wire_bytes": 3654, + "reported_used_tokens": 3617, + "working_set_bytes": 945680384, + "peak_working_set_bytes": 946593792 + }, + { + "query": "Bedrock InvokeModel fails because the region is not configured", + "ranked": [ + "aws-region-resolution", + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15GWVRFEK49MEY6ZDYBPM", + "id": "01M1X1C258PCGJ15DT0ERFPCEK", + "kind": "memory", + "score": 0.9998329877853394, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X15D9SXHDTY1FDBVM3GCF4", + "id": "01M1X1C258VZJA24F73K5GSJH6", + "kind": "memory", + "score": 0.871902346611023, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X15DFBBMCQ54NSMT2FRBJC", + "id": "01M1X1C258B6Z2K1B1CRB6BDPS", + "kind": "memory", + "score": 0.7683040499687195, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1394.1812, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2786, + "mcp_result_bytes": 2931, + "wire_bytes": 2968, + "reported_used_tokens": 2931, + "working_set_bytes": 945930240, + "peak_working_set_bytes": 946843648 + }, + { + "query": "how do I handle ThrottlingException from Bedrock with exponential backoff?", + "ranked": [ + "aws-retry-throttling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15GXVY9V58V419NEB9CCJ", + "id": "01M1X1C3GTN7CJTV0X8Z3YZHMW", + "kind": "memory", + "score": 0.9984448552131652, + "summary": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with \u00b125% jitter." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1434.7441000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 771, + "mcp_result_bytes": 868, + "wire_bytes": 905, + "reported_used_tokens": 868, + "working_set_bytes": 945942528, + "peak_working_set_bytes": 946843648 + }, + { + "query": "generating a presigned S3 URL for brain export without exposing credentials", + "ranked": [ + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15GYXQNDNN6J2DBBTTCC5", + "id": "01M1X1C4XQN5T316J23V3FKS4C", + "kind": "memory", + "score": 0.9999775886535645, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1442.757, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 875, + "mcp_result_bytes": 956, + "wire_bytes": 993, + "reported_used_tokens": 956, + "working_set_bytes": 945946624, + "peak_working_set_bytes": 946855936 + }, + { + "query": "IMDSv2 token required for instance metadata \u2014 PUT before GET", + "ranked": [ + "aws-instance-metadata" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15H00238A3GDJG56BTM0C", + "id": "01M1X1C6B7VZD3DMYPSCE9GCTZ", + "kind": "memory", + "score": 0.999979853630066, + "summary": "project:fact - [2026-09-07] [tags: aws imds instance-metadata ec2 token] The AWS Instance Metadata Service v2 (IMDSv2) requires a session token: PUT `http://169.254.169.254/latest/api/token` with `X-aws-ec2-metadata-token-ttl-seconds: 21600` to get a token, then GET metadata with `X-aws-ec2-metadata-token: `. IMDSv1 (no token) is disabled on hardened instances. The metadata endpoint is only reachable from within EC2 \u2014 a connection timeout means you're not on EC2." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1259.8235, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 851, + "mcp_result_bytes": 932, + "wire_bytes": 969, + "reported_used_tokens": 932, + "working_set_bytes": 946065408, + "peak_working_set_bytes": 946974720 + }, + { + "query": "Cargo cache key strategy for GitHub Actions to avoid toolchain version collisions", + "ranked": [ + "ci-cache-keys" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15H2ZYQKTWETMNW4C9053", + "id": "01M1X1C7JAY2EX0D26D83FGYRE", + "kind": "memory", + "score": 0.9999439716339112, + "summary": "project:fact - [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key \u2014 macOS and Windows have incompatible artifact formats." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1109.1235, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 788, + "mcp_result_bytes": 869, + "wire_bytes": 906, + "reported_used_tokens": 869, + "working_set_bytes": 946077696, + "peak_working_set_bytes": 946987008 + }, + { + "query": "CI matrix has 18 jobs and costs too much \u2014 how do I reduce it?", + "ranked": [ + "ci-matrix-explosion" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15H3XY4YRBD06CQD512M8", + "id": "01M1X1C8MZNHB0CMBTDPRX5W7F", + "kind": "memory", + "score": 0.99962317943573, + "summary": "project:fact - [tags: ci github-actions matrix jobs resources] A CI matrix combining OS (3) x Rust toolchain (3) x features (2) = 18 jobs. Each spawns a runner; at $0.008/min for Ubuntu and $0.016/min for Windows, a 10-minute build costs $2.40 per push. Reduce: test the full matrix only on PRs to main; on feature branches, test only Linux+stable." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1133.1717, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 721, + "mcp_result_bytes": 802, + "wire_bytes": 839, + "reported_used_tokens": 802, + "working_set_bytes": 946327552, + "peak_working_set_bytes": 947240960 + }, + { + "query": "GitHub Actions secret accidentally printed in build logs", + "ranked": [ + "ci-secrets-masking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15H4YMVTSKVC2DW4SMB12", + "id": "01M1X1C9R9AXM2BRWR9PB7H1RR", + "kind": "memory", + "score": 0.9951270818710328, + "summary": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output \u2014 but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1346.2715, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 711, + "mcp_result_bytes": 792, + "wire_bytes": 829, + "reported_used_tokens": 792, + "working_set_bytes": 946327552, + "peak_working_set_bytes": 947240960 + }, + { + "query": "how long do GitHub Actions artifacts persist and what's the storage limit?", + "ranked": [ + "ci-artifact-retention" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15H60GTN7SN08T6HSHJTH", + "id": "01M1X1CB2G3P5PWHCJQQMZPT3T", + "kind": "memory", + "score": 0.9989684820175172, + "summary": "project:fact - [tags: ci github-actions artifacts retention benchmark] GitHub Actions artifacts are retained for 90 days (default). For benchmark results, use `actions/upload-artifact` with `retention-days: 365` for long-term tracking. The free tier has 500MB storage \u2014 per-combo JSON files from kimetsu bench (each ~60KB) add up fast if you upload them on every push." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1203.0947, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 744, + "mcp_result_bytes": 825, + "wire_bytes": 862, + "reported_used_tokens": 825, + "working_set_bytes": 946335744, + "peak_working_set_bytes": 947249152 + }, + { + "query": "timing-based test flake in CI \u2014 quarantine or fix?", + "ranked": [ + "ci-flaky-quarantine", + "testing-time-dependent-flakes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15H6Z807KGNRBH6RDYNH9", + "id": "01M1X1CC81P6E6BXK6DPXCYC5T", + "kind": "memory", + "score": 0.9940990209579468, + "summary": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal \u2014 a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output." + }, + { + "expansion_handle": "memory:01M1X15GES1J30TKX65EVTF5NW", + "id": "01M1X1CC817RX2N120CFJRA4R5", + "kind": "memory", + "score": 0.7894570231437683, + "summary": "project:fact - [tags: testing time flaky clock mock rust] Tests that depend on wall-clock time are inherently flaky under load (slow CI runners, GC pauses). Abstract time behind a trait (`Clock: Fn() -> SystemTime`) injected at construction, and supply a fake in tests. For tests checking that something happened \"within N seconds\", use a generous multiple of the expected duration (10x is not unreasonable for CI)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1091.8699000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1340, + "mcp_result_bytes": 1443, + "wire_bytes": 1480, + "reported_used_tokens": 1443, + "working_set_bytes": 946335744, + "peak_working_set_bytes": 947253248 + }, + { + "query": "kimetsu doctor says the MCP server is running \u2014 how do I stop it before an update?", + "ranked": [ + "kimetsu-daemon-lifecycle", + "mcp-env-propagation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15H82NFSZJR4K18E3AJP1", + "id": "01M1X1CDACHEVF1E1V4X8KMT06", + "kind": "memory", + "score": 0.9999032020568848, + "summary": "project:fact - [2026-09-07] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1X15GQFBSJYRJP99F7WKK7A", + "id": "01M1X1CDACN8X7AVT9PK0AAESQ", + "kind": "memory", + "score": 0.9228461980819702, + "summary": "project:fact - [2026-09-07] [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment \u2014 changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1430.4136999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1266, + "mcp_result_bytes": 1365, + "wire_bytes": 1402, + "reported_used_tokens": 1365, + "working_set_bytes": 946335744, + "peak_working_set_bytes": 947253248 + }, + { + "query": "noise capsules consuming token budget without contributing retrieval signal", + "ranked": [ + "kimetsu-capsule-budgets" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15H91W0ZJG8BRDC1SR7NB", + "id": "01M1X1CEQ38EDVNASG6AYVD9TS", + "kind": "memory", + "score": 0.9999643564224244, + "summary": "project:fact - [tags: kimetsu capsule tokens budget retrieval] kimetsu retrieval enforces a token budget per capsule type: memory capsules are capped at 6000 tokens total (across all retrieved memories), file capsules at 3000 tokens. When a memory is large and would exceed the budget, it is truncated at a sentence boundary. The budget is enforced AFTER reranking \u2014 reranking may reorder results so that a truncated high-ranked memory displaces a full lower-ranked one." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1287.2729000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 847, + "mcp_result_bytes": 928, + "wire_bytes": 965, + "reported_used_tokens": 928, + "working_set_bytes": 946335744, + "peak_working_set_bytes": 947253248 + }, + { + "query": "kimetsu_brain_record writes to the wrong brain location \u2014 user vs project scope", + "ranked": [ + "kimetsu-memory-scopes", + "kimetsu-write-tools-gate", + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15H9YK299AXZZWRR1XWBF", + "id": "01M1X1CFZ4B9HSZB766WAY07GM", + "kind": "memory", + "score": 0.9998672008514404, + "summary": "project:fact - [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available \u2014 if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope." + }, + { + "expansion_handle": "memory:01M1X15HD2AZTFCTS1HWHGGHBC", + "id": "01M1X1CFZ4JDYZJW2FXNHCH7N4", + "kind": "memory", + "score": 0.9389453530311584, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level \u2014 disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1X15DHYTJCJX4D45ABR9SVG", + "id": "01M1X1CFZ4TGKSM2MDHPNENDVB", + "kind": "memory", + "score": 0.9388486742973328, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1075.2848999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2097, + "mcp_result_bytes": 2214, + "wire_bytes": 2251, + "reported_used_tokens": 2214, + "working_set_bytes": 946335744, + "peak_working_set_bytes": 947253248 + }, + { + "query": "how do I configure kimetsu to use Claude Haiku for harvesting but Opus for the agent?", + "ranked": [ + "kimetsu-distiller-config", + "aws-region-resolution", + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15HB16G89KJ4X823D57SP", + "id": "01M1X1CH0WRAEABRDPGFN7EPPZ", + "kind": "memory", + "score": 0.9999725818634032, + "summary": "project:fact - [tags: kimetsu distiller harvest config provider] The kimetsu distiller (auto-harvester) uses a SEPARATE provider configuration from the main agent: `distiller.provider`, `distiller.model`, `distiller.api_key`. This allows running the agent on an expensive model (Claude Opus) while harvesting with a cheap model (Claude Haiku). If `distiller.provider` is not set, it inherits `provider`." + }, + { + "expansion_handle": "memory:01M1X15GWVRFEK49MEY6ZDYBPM", + "id": "01M1X1CH0W0AP1V4TQPMZS459K", + "kind": "memory", + "score": 0.8705393075942993, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X15D9SXHDTY1FDBVM3GCF4", + "id": "01M1X1CH0W6216SK1T57FJ2KCM", + "kind": "memory", + "score": 0.8454174399375916, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1376.3392999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2348, + "mcp_result_bytes": 2473, + "wire_bytes": 2510, + "reported_used_tokens": 2473, + "working_set_bytes": 946335744, + "peak_working_set_bytes": 947253248 + }, + { + "query": "first agent turn is slow because kimetsu proactive hook runs embedding inference", + "ranked": [ + "kimetsu-proactive-hooks", + "kimetsu-distiller-config", + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15HC2XT8V3KQVAQ57F3GD", + "id": "01M1X1CJBSH74KZP5932EFVAYB", + "kind": "memory", + "score": 0.999129831790924, + "summary": "project:fact - [2026-09-07] [tags: kimetsu proactive hooks context injection] kimetsu's proactive context injection runs before each agent turn (pre-turn hook) and injects relevant memories into the system prompt prefix. The hook invocation adds latency to the first token: embedding inference + vector search + reranking + context formatting. On a cold start, this can be 1-3 seconds." + }, + { + "expansion_handle": "memory:01M1X15HB16G89KJ4X823D57SP", + "id": "01M1X1CJBSR6PNHT2NCGVM1DYY", + "kind": "memory", + "score": 0.8709061145782471, + "summary": "project:fact - [2026-09-07] [tags: kimetsu distiller harvest config provider] The kimetsu distiller (auto-harvester) uses a SEPARATE provider configuration from the main agent: `distiller.provider`, `distiller.model`, `distiller.api_key`. This allows running the agent on an expensive model (Claude Opus) while harvesting with a cheap model (Claude Haiku). If `distiller.provider` is not set, it inherits `provider`." + }, + { + "expansion_handle": "memory:01M1X15GPD31A6FPKDTDB60673", + "id": "01M1X1CJBTDESZ1KXGQC1R4ZA7", + "kind": "memory", + "score": 0.6799831390380859, + "summary": "project:fact - [2026-09-07] [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1580.0475000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1967, + "mcp_result_bytes": 2084, + "wire_bytes": 2121, + "reported_used_tokens": 2084, + "working_set_bytes": 946335744, + "peak_working_set_bytes": 947253248 + }, + { + "query": "make the kimetsu brain read-only for certain repos on a shared remote server", + "ranked": [ + "kimetsu-write-tools-gate", + "remote-ingest-split-roots", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15HD2AZTFCTS1HWHGGHBC", + "id": "01M1X1CKX5RD2E9ERC3SCGMHQX", + "kind": "memory", + "score": 0.9999514818191528, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level \u2014 disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1X15D4FA0TN48H977JEDBCS", + "id": "01M1X1CKX5NVSKSHR63Q58BJ02", + "kind": "memory", + "score": 0.9976721405982972, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1X15D6GYW49ER8HFZNZ2A0Y", + "id": "01M1X1CKX5V1MWANSFQ1KRPKYA", + "kind": "memory", + "score": 0.915355622768402, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1420.502, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2725, + "mcp_result_bytes": 2890, + "wire_bytes": 2927, + "reported_used_tokens": 2890, + "working_set_bytes": 946335744, + "peak_working_set_bytes": 947253248 + }, + { + "query": "kimetsu FTS search misses 'deadlocking' when memory says 'deadlock'", + "ranked": [ + "kimetsu-query-stemming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15HE4JNQXJQHVJC6T7CKY", + "id": "01M1X1CNA5YYNAW581ZP3H7FND", + "kind": "memory", + "score": 0.989694595336914, + "summary": "project:fact - [2026-09-07] [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1098.1803000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 781, + "mcp_result_bytes": 878, + "wire_bytes": 915, + "reported_used_tokens": 878, + "working_set_bytes": 946343936, + "peak_working_set_bytes": 947253248 + }, + { + "query": "how does pool size affect retrieval recall and latency in the bench?", + "ranked": [ + "kimetsu-rerank-pool" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15HHDT04DRVEB6FZSV4AA", + "id": "01M1X1CPC8N1H067JT4Q4SDYZE", + "kind": "memory", + "score": 0.999588668346405, + "summary": "project:fact - [tags: kimetsu reranker pool size ann retrieval] kimetsu's retrieval pipeline: ANN (approximate nearest neighbor) retrieves a pool of candidates, then the reranker reorders them, then the top-K are returned. The pool size (default 6 for production, 12 in bench) controls the recall-latency tradeoff: larger pool = higher recall = more reranker calls = more latency. For the jina-tiny reranker, pool 12 adds ~80ms vs pool 6." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1104.1756, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 812, + "mcp_result_bytes": 893, + "wire_bytes": 930, + "reported_used_tokens": 893, + "working_set_bytes": 946343936, + "peak_working_set_bytes": 947257344 + }, + { + "query": "second embedder in a remote bench run gets worse results than the first", + "ranked": [ + "kimetsu-bench-remote-embedder-singleton" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15HJBN2B5ZVHFDEHG37WC", + "id": "01M1X1CQEWGRWSPWY9T3Z9XVBA", + "kind": "memory", + "score": 0.8715754747390747, + "summary": "project:fact - [2026-09-07] [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1231.6047999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 895, + "mcp_result_bytes": 976, + "wire_bytes": 1013, + "reported_used_tokens": 976, + "working_set_bytes": 946343936, + "peak_working_set_bytes": 947257344 + }, + { + "query": "what is the expected JSON schema for kimetsu brain bench dataset files?", + "ranked": [ + "kimetsu-eval-fixture-shape", + "mcp-schema-validation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15HKEKHJZZT0K94TTXRTC", + "id": "01M1X1CRMZD4W7JEGKAZ3SMYH2", + "kind": "memory", + "score": 0.9999712705612184, + "summary": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` \u2014 a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases)." + }, + { + "expansion_handle": "memory:01M1X15GRGE1XMYD5WEEJEMEQ7", + "id": "01M1X1CRMZ4WV4PN873R6GTSNM", + "kind": "memory", + "score": 0.7343910336494446, + "summary": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array \u2014 omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1111.6798000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1390, + "mcp_result_bytes": 1533, + "wire_bytes": 1570, + "reported_used_tokens": 1533, + "working_set_bytes": 946343936, + "peak_working_set_bytes": 947257344 + }, + { + "query": "what does MRR mean and how do I interpret a 0.01 difference between combos?", + "ranked": [ + "kimetsu-mrr-metric" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15HMJDSKVXFGTBE99P00H", + "id": "01M1X1CSR6DHZT1H1GCM5WP0AV", + "kind": "memory", + "score": 0.930732488632202, + "summary": "project:fact - [tags: kimetsu bench mrr recall metrics evaluation] kimetsu bench reports MRR (Mean Reciprocal Rank) and Recall@K. MRR is 1/rank_of_first_relevant_result, averaged across cases; it penalizes models that rank the correct answer 2nd or 3rd. Recall@K is the fraction of cases where at least one relevant answer appears in the top K." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1153.7395999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 719, + "mcp_result_bytes": 800, + "wire_bytes": 837, + "reported_used_tokens": 800, + "working_set_bytes": 946343936, + "peak_working_set_bytes": 947257344 + }, + { + "query": "SQLITE_BUSY keeps appearing even with WAL mode enabled", + "ranked": [ + "sqlite-busy-timeout-wal", + "sqlite-wal-network-drive" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15DVHN86HGNM6VMN30KRH", + "id": "01M1X1CTVVTFYQ7YA8CAD7M94X", + "kind": "memory", + "score": 0.9940937161445618, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + }, + { + "expansion_handle": "memory:01M1X15E0REZ5X10NXK6B8EJM2", + "id": "01M1X1CTVWA2PJBED2KCTGQST7", + "kind": "memory", + "score": 0.7747130393981934, + "summary": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1447.6876, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1423, + "mcp_result_bytes": 1522, + "wire_bytes": 1559, + "reported_used_tokens": 1522, + "working_set_bytes": 946343936, + "peak_working_set_bytes": 947257344 + }, + { + "query": "my brain file got huge again right after I compacted it", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1218.0158000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 946343936, + "peak_working_set_bytes": 947257344 + }, + { + "query": "all my FTS queries stopped returning results after I changed the tokenizer config", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1229.2229, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 946343936, + "peak_working_set_bytes": 947257344 + }, + { + "query": "something is preventing the kimetsu binary from being replaced during update", + "ranked": [ + "kimetsu-daemon-lifecycle" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15H82NFSZJR4K18E3AJP1", + "id": "01M1X1CYNPAY12HC3YQZ4TCYGV", + "kind": "memory", + "score": 0.9216884970664978, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + } + ], + "positive_recall_at_4": 0.3333333333333333, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1072.6956, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 752, + "mcp_result_bytes": 833, + "wire_bytes": 870, + "reported_used_tokens": 833, + "working_set_bytes": 946343936, + "peak_working_set_bytes": 947257344 + }, + { + "query": "tool call results not appearing in the context \u2014 is the semantic floor too high?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1120.9639, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 228, + "mcp_result_bytes": 291, + "wire_bytes": 328, + "reported_used_tokens": 291, + "working_set_bytes": 946343936, + "peak_working_set_bytes": 947261440 + }, + { + "query": "CARGO_INCREMENTAL=0 in CI prevents a class of spurious compilation errors", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15EB0QZ0G0J4SXC242WJ7", + "id": "01M1X1D0TEH5STDA6J5CRRVCMR", + "kind": "memory", + "score": 0.7999841570854187, + "summary": "project:fact - [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1085.6584, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 877, + "mcp_result_bytes": 958, + "wire_bytes": 995, + "reported_used_tokens": 958, + "working_set_bytes": 946343936, + "peak_working_set_bytes": 947261440 + }, + { + "query": "how do I check whether my Cargo workspace respects the MSRV constraint?", + "ranked": [ + "cargo-msrv", + "cargo-patch-section", + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15EDPST4QNV7CFTPT7T1H", + "id": "01M1X1D1WYMVJB54JQBSQQMNRA", + "kind": "memory", + "score": 0.9985359907150269, + "summary": "project:fact - [tags: cargo rust msrv edition compatibility] Set `rust-version` in each `Cargo.toml` to declare the minimum supported Rust version (MSRV). Cargo enforces this with `--check`: `cargo check` fails if the toolchain is older than `rust-version`. Keep MSRV as old as your oldest supported deployment target." + }, + { + "expansion_handle": "memory:01M1X15ECTTF4R566QQ07DFFTA", + "id": "01M1X1D1WYTVPBWR9S9DMQKH2K", + "kind": "memory", + "score": 0.620707631111145, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace \u2014 including transitive deps \u2014 that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1X15E933WNR915BWNY6P1NR", + "id": "01M1X1D1WYP1EADF7QSG19RE29", + "kind": "memory", + "score": 0.6190821528434753, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1138.8915, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1915, + "mcp_result_bytes": 2036, + "wire_bytes": 2073, + "reported_used_tokens": 2036, + "working_set_bytes": 946343936, + "peak_working_set_bytes": 947261440 + }, + { + "query": "rusqlite connection opened but ON DELETE CASCADE cascade never fires", + "ranked": [ + "sqlite-foreign-keys-default-off" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15E3N8MXFVS8YB17MBFKH", + "id": "01M1X1D2ZSZDVE67YJ6QNH38XQ", + "kind": "memory", + "score": 0.9797621369361876, + "summary": "project:fact - [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting \u2014 every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1535.3295, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 735, + "mcp_result_bytes": 816, + "wire_bytes": 853, + "reported_used_tokens": 816, + "working_set_bytes": 946343936, + "peak_working_set_bytes": 947261440 + }, + { + "query": "I cannot connect to kimetsu-remote \u2014 something about TLS cert validation failed", + "ranked": [ + "http-tls-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15G9ERJZ5NKP0NN0RXD3Q", + "id": "01M1X1D4G079YQX75SN40TQRQG", + "kind": "memory", + "score": 0.5578561425209045, + "summary": "project:fact - [tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle \u2014 the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1117.2618, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 779, + "mcp_result_bytes": 860, + "wire_bytes": 897, + "reported_used_tokens": 860, + "working_set_bytes": 1000497152, + "peak_working_set_bytes": 1001410560 + }, + { + "query": "graceful shutdown fails because in-flight SQLite queries are still running when pool closes", + "ranked": [ + "tokio-shutdown-ordering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15G54KE7TFDARK6V4JN17", + "id": "01M1X1D5JRV9S99XW56TTPSNZP", + "kind": "memory", + "score": 0.9991455078125, + "summary": "project:fact - [2026-09-07] [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries \u2014 the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1121.9891, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 944, + "mcp_result_bytes": 1025, + "wire_bytes": 1062, + "reported_used_tokens": 1025, + "working_set_bytes": 1000497152, + "peak_working_set_bytes": 1001410560 + }, + { + "query": "kimetsu-remote response takes 8 seconds \u2014 which stage is slow?", + "ranked": [ + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15GPD31A6FPKDTDB60673", + "id": "01M1X1D6NVYCDZA81HZRRHZETX", + "kind": "memory", + "score": 0.980535328388214, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1267.723, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 858, + "mcp_result_bytes": 939, + "wire_bytes": 976, + "reported_used_tokens": 939, + "working_set_bytes": 1000497152, + "peak_working_set_bytes": 1001410560 + }, + { + "query": "git reflog to rescue accidentally deleted branch", + "ranked": [ + "git-reflog-rescue" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15FT4V885GF8WS7X3HBC8", + "id": "01M1X1D7XEBBH1DDJMR01PP48E", + "kind": "memory", + "score": 0.9931837916374208, + "summary": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone \u2014 they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only \u2014 remote reflog is not accessible via normal git commands." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1162.0448999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 762, + "mcp_result_bytes": 843, + "wire_bytes": 880, + "reported_used_tokens": 843, + "working_set_bytes": 1000509440, + "peak_working_set_bytes": 1001414656 + }, + { + "query": "git submodule --remote advances the pinned SHA unexpectedly", + "ranked": [ + "git-submodule-pinning" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15FS59X3BFZNNCXVKT8T8", + "id": "01M1X1D91QFYHCGKEDDQDV2PHM", + "kind": "memory", + "score": 0.9999607801437378, + "summary": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip \u2014 this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1370.1927999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 752, + "mcp_result_bytes": 833, + "wire_bytes": 870, + "reported_used_tokens": 833, + "working_set_bytes": 1000509440, + "peak_working_set_bytes": 1001414656 + }, + { + "query": "axum SSE streaming drops the last event when client disconnects", + "ranked": [ + "http-streaming-bodies" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15GAEC9TS1JVE2SXPZNZV", + "id": "01M1X1DACTEQVK9AW2Q6Y9N6HZ", + "kind": "memory", + "score": 0.7960996031761169, + "summary": "project:fact - [2026-09-07] [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding \u2014 a chunk may split across frame boundaries." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1570.0714, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 859, + "mcp_result_bytes": 940, + "wire_bytes": 977, + "reported_used_tokens": 940, + "working_set_bytes": 1000509440, + "peak_working_set_bytes": 1001418752 + }, + { + "query": "how do I detect that I am running inside a git worktree vs the main checkout?", + "ranked": [ + "git-worktree-brain-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15FN49X5ZX6NQND34AZTX", + "id": "01M1X1DBXW907J3ZMEP91G7XKH", + "kind": "memory", + "score": 0.9114787578582764, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root \u2014 if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1115.4732000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 881, + "mcp_result_bytes": 962, + "wire_bytes": 999, + "reported_used_tokens": 962, + "working_set_bytes": 1000509440, + "peak_working_set_bytes": 1001422848 + }, + { + "query": "ONNX Runtime intra-op threads causing CPU contention during parallel bench", + "ranked": [ + "onnx-ort-threading" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X15FM575W5FVQQGC9YSPHV", + "id": "01M1X1DD1AFQ23ZR0RJ4B1VSCQ", + "kind": "memory", + "score": 0.9999747276306152, + "summary": "project:fact - [tags: onnx ort thread-pool parallelism cpu] ORT (ONNX Runtime) creates its own inter-op and intra-op thread pools. In a multi-process bench setup, each child inherits these pools and they compete for CPU cores. Set `SessionOptionsBuilder::with_intra_threads(1).with_inter_threads(1)` if you're running many parallel bench processes \u2014 this sacrifices per-inference throughput for lower contention." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1064.9917, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 789, + "mcp_result_bytes": 870, + "wire_bytes": 907, + "reported_used_tokens": 870, + "working_set_bytes": 1000509440, + "peak_working_set_bytes": 1001422848 + }, + { + "query": "what is the right way to supply AWS session token alongside access key and secret?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1354.5109, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 1000509440, + "peak_working_set_bytes": 1001422848 + } + ], + "id": "existing-development-100", + "dimension": "retrieval", + "tier": "hard", + "score": 0.8293650793650794, + "skipped": false, + "detail": "positive-recall@4=0.84 mrr=0.86 stale-hit=n/a resolution=n/a false-injection=0.385 (n=13) positive-n=197 negative-n=13 (210 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 0.8293650793650794, + 1 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 0.8293650793650794, + "n": 1, + "ci95": null + } + }, + "overall_index": 0.8293650793650794, + "scenario_weighted_index": 0.8293650793650794 +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-retrieval/development/comparison.json b/docs/audits/2026-09-07-retrieval/development/comparison.json new file mode 100644 index 0000000..f7ecfb3 --- /dev/null +++ b/docs/audits/2026-09-07-retrieval/development/comparison.json @@ -0,0 +1,189 @@ +{ + "schema_version": 1, + "status": "complete", + "harness": { + "path": "E:\\Kimetsu\\bench\\target\\release\\kbench.exe", + "sha256": "fcb3d2ee52aaa040a1f54eb26a8245fa833a4cd35bed51505ddce76d31e8df9c", + "bytes": 9348096 + }, + "runner": { + "path": "E:\\tmp\\kimetsu-brain-hardening\\bench\\scripts\\compare_brainbench.py", + "sha256": "9b2f79c38a34a13d4f95ef9e19972eb0ccdba9e0e585a8df7a637ad7d06332a8", + "bytes": 23909 + }, + "binaries": { + "baseline": { + "path": "E:\\tmp\\kimetsu-brain-hardening\\tmp-tests\\kimetsu-candidate.exe", + "sha256": "5c87e542907a47917f23fad50ddff1e46789eaae14328ccc662ca748b66b5477", + "bytes": 47080448 + }, + "candidate": { + "path": "E:\\tmp\\kimetsu-brain-hardening\\tmp-tests\\kimetsu-retrieval-candidate.exe", + "sha256": "2445e8adc06c4a6b59d5ee51be46a6f5aa6e777514dfbb60ac4b136b69ba0ba6", + "bytes": 47064064 + } + }, + "datasets": [ + { + "path": "E:\\tmp\\kimetsu-brain-hardening\\tmp-tests\\brainbench-development-100.json", + "sha256": "ff3c78f8b5dab7e9b2f10894af93f07965705502a4f9d3e8c45c529b9b6ca33f", + "bytes": 122806 + } + ], + "settings": { + "budget_tokens": 6000, + "dimensions": [ + "poisoning", + "render-contract", + "retrieval", + "workflow" + ], + "jobs": 1, + "warm_start": false, + "include_ambient": false, + "overrides": { + "KIMETSU_BRAIN_EMBEDDER": "bge-small-en-v1.5", + "KIMETSU_DETECT_CONFLICTS": "0", + "KIMETSU_RESOLVE_CONFLICTS": "0", + "FASTEMBED_CACHE_DIR": "E:/Kimetsu/.fastembed_cache", + "HF_HOME": "E:/tmp/kimetsu-brain-hardening/tmp-tests/hf-home" + }, + "baseline_threads": 0, + "candidate_threads": 0, + "baseline_reranker": "ms-marco-tinybert-l-2-v2", + "candidate_reranker": "mmarco-minilm-l12-v2-int8", + "baseline_rerank_floor": null, + "candidate_rerank_floor": 0.55 + }, + "runs": [ + { + "label": "baseline", + "repeat": 1, + "intra_threads_override": null, + "rerank_floor_override": null, + "reranker_override": "ms-marco-tinybert-l-2-v2", + "wall_seconds": 224.2047969000414, + "report_file": "1-baseline.json" + }, + { + "label": "candidate", + "repeat": 1, + "intra_threads_override": null, + "rerank_floor_override": "0.55", + "reranker_override": "mmarco-minilm-l12-v2-int8", + "wall_seconds": 311.0093465000391, + "report_file": "1-candidate.json" + }, + { + "label": "candidate", + "repeat": 2, + "intra_threads_override": null, + "rerank_floor_override": "0.55", + "reranker_override": "mmarco-minilm-l12-v2-int8", + "wall_seconds": 313.49370250001084, + "report_file": "2-candidate.json" + }, + { + "label": "baseline", + "repeat": 2, + "intra_threads_override": null, + "rerank_floor_override": null, + "reranker_override": "ms-marco-tinybert-l-2-v2", + "wall_seconds": 213.52820460003568, + "report_file": "2-baseline.json" + }, + { + "label": "baseline", + "repeat": 3, + "intra_threads_override": null, + "rerank_floor_override": null, + "reranker_override": "ms-marco-tinybert-l-2-v2", + "wall_seconds": 194.7380774000194, + "report_file": "3-baseline.json" + }, + { + "label": "candidate", + "repeat": 3, + "intra_threads_override": null, + "rerank_floor_override": "0.55", + "reranker_override": "mmarco-minilm-l12-v2-int8", + "wall_seconds": 265.6601367999683, + "report_file": "3-candidate.json" + } + ], + "comparison": { + "measurement_summary": { + "baseline": { + "unique_queries": 210, + "query_observations": 630, + "positive_queries": 197, + "negative_queries": 13, + "stale_queries": 0, + "positive_recall_at_4": 0.8417935702199661, + "positive_hit_at_4": 0.8578680203045685, + "positive_mrr": 0.850253807106599, + "negative_injection_rate": 0.5384615384615384, + "stale_injection_rate": null, + "first_query_mean_ms": 1200.0951333333333, + "subsequent_query_p50_ms": 969.7701, + "subsequent_query_p95_ms": 1106.8446000000001, + "subsequent_observations": 627, + "mean_model_text_bytes": 1167.5904761904762, + "mean_mcp_result_bytes": 1262.4190476190477, + "memory_observations": 630, + "mean_mcp_working_set_bytes": 287675330.2349206, + "max_mcp_peak_working_set_bytes": 295104512, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + }, + "candidate": { + "unique_queries": 210, + "query_observations": 630, + "positive_queries": 197, + "negative_queries": 13, + "stale_queries": 0, + "positive_recall_at_4": 0.8434856175972927, + "positive_hit_at_4": 0.8629441624365483, + "positive_mrr": 0.8604060913705583, + "negative_injection_rate": 0.38461538461538464, + "stale_injection_rate": null, + "first_query_mean_ms": 2614.5614666666665, + "subsequent_query_p50_ms": 1336.2471, + "subsequent_query_p95_ms": 1767.8703, + "subsequent_observations": 627, + "mean_model_text_bytes": 1022.504761904762, + "mean_mcp_result_bytes": 1112.1714285714286, + "memory_observations": 630, + "mean_mcp_working_set_bytes": 938291479.568254, + "max_mcp_peak_working_set_bytes": 1001422848, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + } + }, + "by_dimension": { + "retrieval": { + "n_scenarios": 1, + "baseline": 0.8182539682539681, + "candidate": 0.8293650793650794, + "mean_delta": 0.011111111111111294, + "ci95": null, + "wins": 1, + "ties": 0, + "losses": 0 + } + }, + "scenarios": [ + { + "identity": "retrieval/existing-development-100", + "dimension": "retrieval", + "baseline": 0.8182539682539681, + "candidate": 0.8293650793650794, + "delta": 0.011111111111111294 + } + ], + "unpaired_scenarios": [], + "unpaired_details": [], + "baseline_errors": 0, + "candidate_errors": 0, + "repeats": 3, + "uncertainty_note": "Exploratory paired bootstrap over scenario IDs after averaging repeats; correlated task families require a separate grouped holdout." + } +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-retrieval/model-manifest.json b/docs/audits/2026-09-07-retrieval/model-manifest.json new file mode 100644 index 0000000..4a9a66e --- /dev/null +++ b/docs/audits/2026-09-07-retrieval/model-manifest.json @@ -0,0 +1,31 @@ +{ + "repo": "cross-encoder/mmarco-mMiniLMv2-L12-H384-v1", + "revision": "1427fd652930e4ba29e8149678df786c240d8825", + "files": [ + { + "file": "config.json", + "sha256": "cc2cfe51aa3fd759d21d21acf5dfd6994aa67a3c9210636d22e143699d336c77", + "size": 891 + }, + { + "file": "tokenizer.json", + "sha256": "62c24cdc13d4c9952d63718d6c9fa4c287974249e16b7ade6d5a85e7bbb75626", + "size": 17082660 + }, + { + "file": "tokenizer_config.json", + "sha256": "e7fbfbfa6347b4e414c1cee50d142e2c2f9a895dad68b068ae83a8b564c3837e", + "size": 435 + }, + { + "file": "special_tokens_map.json", + "sha256": "378eb3bf733eb16e65792d7e3fda5b8a4631387ca04d2015199c4d4f22ae554d", + "size": 239 + }, + { + "file": "onnx/model_quint8_avx2.onnx", + "sha256": "6c2513767fb63d008a4377bef7a7a3555433d9436342bb53e35a3a72ffc52d4b", + "size": 118620016 + } + ] +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-retrieval/retrieval_probe.rs b/docs/audits/2026-09-07-retrieval/retrieval_probe.rs new file mode 100644 index 0000000..19b01de --- /dev/null +++ b/docs/audits/2026-09-07-retrieval/retrieval_probe.rs @@ -0,0 +1,89 @@ +//! Temporary investigation harness. No production policy changes. +use kimetsu_brain::{context::ContextRequest, embeddings::{Embedder, EmbedderError, Reranker}, eval::EvalFixture, project::{self, BrainSession}, serving::ServingPolicy}; +use kimetsu_core::memory::{MemoryKind, MemoryScope}; +use serde_json::json; +use std::collections::HashMap; +struct FixedVector { vector: Vec, model: String } +impl Embedder for FixedVector { + fn embed(&self, _: &str) -> Result, EmbedderError> { Ok(self.vector.clone()) } + fn dim(&self) -> usize { self.vector.len() } + fn model_id(&self) -> &str { &self.model } +} +struct Scores(HashMap); +struct LocalReranker(std::sync::Mutex); +impl Reranker for LocalReranker { + fn rerank(&self, q: &str, docs: &[&str]) -> Result, EmbedderError> { + let raw = self.0.lock().unwrap().rerank(q, docs, false, None).map_err(|e| EmbedderError::EmbedFailed(e.to_string()))?; + let mut scores = vec![f32::NAN; docs.len()]; + for x in raw { scores[x.index] = 1.0 / (1.0 + (-x.score).exp()); } + assert!(scores.iter().all(|v| v.is_finite())); Ok(scores) + } + fn model_id(&self) -> &str { "mmarco-multilingual-avx2-diagnostic" } +} +impl Reranker for Scores { + fn rerank(&self, _: &str, docs: &[&str]) -> Result, EmbedderError> { + docs.iter().map(|d| self.0.get(*d).copied().ok_or_else(|| EmbedderError::EmbedFailed("missing diagnostic score".into()))).collect() + } + fn model_id(&self) -> &str { "cached-diagnostic-scores" } +} +fn main() -> kimetsu_core::KimetsuResult<()> { + let args: Vec = std::env::args().collect(); + let fixture: EvalFixture = serde_json::from_str(&std::fs::read_to_string(&args[1])?)?; + let emb = kimetsu_brain::embeddings::open_embedder_for_model("bge-small-en-v1.5"); + assert!(!emb.is_noop()); + let rr: Box = if let Ok(path) = std::env::var("PROBE_RERANKER_PATH") { + let p = std::path::Path::new(&path); + let files = fastembed::TokenizerFiles { + tokenizer_file: std::fs::read(p.join("tokenizer.json"))?, + config_file: std::fs::read(p.join("config.json"))?, + tokenizer_config_file: std::fs::read(p.join("tokenizer_config.json"))?, + special_tokens_map_file: std::fs::read(p.join("special_tokens_map.json"))?, + }; + let model = fastembed::UserDefinedRerankingModel::new(fastembed::OnnxSource::File(p.join("onnx/model_quint8_avx2.onnx")), files); + Box::new(LocalReranker(std::sync::Mutex::new(fastembed::TextRerank::try_new_from_user_defined(model, Default::default())?))) + } else { kimetsu_brain::embeddings::open_reranker_checked("ms-marco-tinybert-l-2-v2")?.unwrap() }; + let tmp = tempfile::tempdir()?; + kimetsu_core::paths::git_init_boundary(tmp.path()); + project::init_project(tmp.path(), true)?; + let mut keys = HashMap::new(); + for mem in &fixture.memories { + let id = project::add_memory(tmp.path(), MemoryScope::Project, MemoryKind::Fact, &mem.text)?; + keys.insert(format!("memory:{id}"), mem.key.clone()); + } + let session = BrainSession::open_readonly(tmp.path())?; + let mut results = Vec::new(); + for (i, case) in fixture.cases.iter().enumerate() { + let fixed = FixedVector { vector: emb.embed(&case.query)?, model: emb.model_id().into() }; + let mut req = ContextRequest { query: case.query.clone(), stage: "localization".into(), ..Default::default() }; + session.resolve_request_floors(&mut req); + let mut variants = Vec::new(); + let production_only = std::env::var_os("PROBE_PRODUCTION_ONLY").is_some(); + for pool in if production_only { vec![6] } else { vec![6,16,32] } { + let policy = ServingPolicy { pool, cap: 4, ..Default::default() }; + variants.push((format!("pool-{pool}"), policy, session.retrieve_context_with_injected_embedder(policy.prepare(req.clone(), true), &fixed)?)); + } + let mut raw = req.clone(); + raw.min_semantic_score_override = Some(0.0); raw.min_lexical_coverage_override = Some(0.0); raw.abstain_evidence_override = Some(0.0); + raw.budget_tokens = 64_000; raw.max_capsules = 32; + if !production_only { variants.push(("ungated-32".into(), ServingPolicy { pool:32, cap:4, ..Default::default() }, session.retrieve_context_with_injected_embedder(raw, &fixed)?)); } + let mut docs: Vec = variants.iter().flat_map(|(_,_,b)| b.capsules.iter().chain(b.excluded.iter().filter(|_| !production_only)).map(|c| c.summary.clone())).collect(); + docs.sort(); docs.dedup(); + let values = rr.rerank(&case.query, &docs.iter().map(String::as_str).collect::>())?; + assert_eq!(docs.len(), values.len()); + let scores = Scores(docs.into_iter().zip(values).collect()); + let mut stages = Vec::new(); + for (name, policy, bundle) in variants { + let candidates: Vec<_> = bundle.capsules.iter().map(|c| json!({"key":keys.get(&c.expansion_handle),"ce":scores.0[&c.summary],"rank_score":c.score,"text":c.summary})).collect(); + let excluded_gold: Vec<_> = bundle.excluded.iter().filter(|c| keys.get(&c.expansion_handle).is_some_and(|k| case.relevant.contains(k))).map(|c| json!({"key":keys.get(&c.expansion_handle),"ce":scores.0.get(&c.summary)})).collect(); + let top = bundle.top_abs_evidence; + let skipped = bundle.skipped; + let out = policy.arbitrate(&case.query, bundle, Some(&scores), req.abstain_evidence); + let delivered = policy.render(out, session.config().broker.compress_capsules, kimetsu_brain::serving::EVAL_EXPOSURE_ID); + stages.push(json!({"name":name,"top_cosine":top,"pre_skipped":skipped,"candidates":candidates,"excluded_gold":excluded_gold,"delivered":delivered.capsules.iter().map(|c|keys.get(&c.expansion_handle)).collect::>()})); + } + results.push(json!({"query":case.query,"relevant":case.relevant,"floors":{"semantic":req.min_semantic_score,"lexical":req.min_lexical_coverage,"abstain":req.abstain_evidence},"stages":stages})); + std::fs::write(&args[2], serde_json::to_vec_pretty(&results)?)?; + eprintln!("probe {}/{}", i+1, fixture.cases.len()); + } + Ok(()) +} diff --git a/docs/audits/2026-09-07-retrieval/run-comparisons.ps1 b/docs/audits/2026-09-07-retrieval/run-comparisons.ps1 new file mode 100644 index 0000000..0534fab --- /dev/null +++ b/docs/audits/2026-09-07-retrieval/run-comparisons.ps1 @@ -0,0 +1,39 @@ +param( + [Parameter(Mandatory=$true)][string]$Baseline, + [Parameter(Mandatory=$true)][string]$Candidate, + [Parameter(Mandatory=$true)][string]$Harness, + [Parameter(Mandatory=$true)][string]$ModelCache, + [Parameter(Mandatory=$true)][string]$HfHome, + [Parameter(Mandatory=$true)][string]$OutputRoot +) +$ErrorActionPreference = 'Stop' +$baselineBinary = (Resolve-Path -LiteralPath $Baseline).Path +$candidateBinary = (Resolve-Path -LiteralPath $Candidate).Path +$harnessBinary = (Resolve-Path -LiteralPath $Harness).Path +if (Test-Path -LiteralPath $OutputRoot) { throw 'Choose a new output directory to preserve existing evidence.' } +New-Item -ItemType Directory -Path $OutputRoot | Out-Null +$outputDirectory = (Resolve-Path -LiteralPath $OutputRoot).Path +$auditRoot = (Resolve-Path (Join-Path $PSScriptRoot '../../..')).Path +$env:FASTEMBED_CACHE_DIR = (Resolve-Path -LiteralPath $ModelCache).Path +$env:HF_HOME = (Resolve-Path -LiteralPath $HfHome).Path +$env:HF_HUB_OFFLINE = '1' +$env:KIMETSU_USER_BRAIN = '0' +$env:KIMETSU_BRAIN_EMBEDDER = 'bge-small-en-v1.5' +$env:KIMETSU_DETECT_CONFLICTS = '0' +$env:KIMETSU_RESOLVE_CONFLICTS = '0' +Remove-Item Env:KBENCH_RERANK_FLOOR -ErrorAction SilentlyContinue +Remove-Item Env:KBENCH_RERANKER -ErrorAction SilentlyContinue +Remove-Item Env:KIMETSU_ABSTAIN_EVIDENCE -ErrorAction SilentlyContinue +$validation = "$PSScriptRoot/validation-frozen.json" +if ((Get-FileHash -LiteralPath $validation -Algorithm SHA256).Hash.ToLowerInvariant() -ne '1a09270e09a9521c38f9dca14dfebe4349fed0e10b1537a92857eb18d3f6bb6f') { throw 'Frozen validation changed' } +$experiments = @( + @{name='multilingual-contract'; dataset="$PSScriptRoot/agent-memory-contract.json"; budget=2048}, + @{name='multilingual-development'; dataset="$PSScriptRoot/development-100.json"; budget=6000}, + @{name='multilingual-validation'; dataset=$validation; budget=6000} +) +foreach ($experiment in $experiments) { + python "$auditRoot/bench/scripts/compare_brainbench.py" --kbench $harnessBinary --baseline $baselineBinary --candidate $candidateBinary --dataset $experiment.dataset --budget-tokens $experiment.budget --repeats 3 --out "$outputDirectory/verified-$($experiment.name)" --baseline-threads 0 --candidate-threads 0 --baseline-reranker ms-marco-tinybert-l-2-v2 --candidate-reranker mmarco-minilm-l12-v2-int8 --candidate-rerank-floor 0.55 + if ($LASTEXITCODE -ne 0) { throw "Comparison failed: $($experiment.name)" } + $result = Get-Content -Raw -LiteralPath "$outputDirectory/verified-$($experiment.name)/comparison.json" | ConvertFrom-Json + if ($result.status -ne 'complete' -or $result.comparison.baseline_errors -ne 0 -or $result.comparison.candidate_errors -ne 0 -or $result.comparison.unpaired_scenarios.Count -ne 0) { throw 'Comparison contains errors or unpaired scenarios' } +} diff --git a/docs/audits/2026-09-07-retrieval/strict-contract-earlier-harness.json b/docs/audits/2026-09-07-retrieval/strict-contract-earlier-harness.json new file mode 100644 index 0000000..e31687b --- /dev/null +++ b/docs/audits/2026-09-07-retrieval/strict-contract-earlier-harness.json @@ -0,0 +1,237 @@ +{ + "schema_version": 1, + "status": "complete", + "harness": { + "path": "E:\\Kimetsu\\bench\\target\\release\\kbench.exe", + "sha256": "27f5f6bac28d2f912c32c13ab4214c50bfd400630553c3da4cd6cd15e3cedf11", + "bytes": 9339904 + }, + "runner": { + "path": "E:\\tmp\\kimetsu-brain-hardening\\bench\\scripts\\compare_brainbench.py", + "sha256": "9b2f79c38a34a13d4f95ef9e19972eb0ccdba9e0e585a8df7a637ad7d06332a8", + "bytes": 23909 + }, + "binaries": { + "baseline": { + "path": "E:\\tmp\\kimetsu-brain-hardening\\tmp-tests\\kimetsu-candidate.exe", + "sha256": "5c87e542907a47917f23fad50ddff1e46789eaae14328ccc662ca748b66b5477", + "bytes": 47080448 + }, + "candidate": { + "path": "E:\\tmp\\kimetsu-brain-hardening\\tmp-tests\\kimetsu-retrieval-candidate.exe", + "sha256": "2445e8adc06c4a6b59d5ee51be46a6f5aa6e777514dfbb60ac4b136b69ba0ba6", + "bytes": 47064064 + } + }, + "datasets": [ + { + "path": "E:\\tmp\\kimetsu-brain-hardening\\bench\\datasets\\brainbench\\agent-memory-contract.json", + "sha256": "ef30967945d2230f84539f2c81d275f00c48765b5f8850a44068948f6825ace4", + "bytes": 8300 + } + ], + "settings": { + "budget_tokens": 2048, + "dimensions": [ + "poisoning", + "render-contract", + "retrieval", + "workflow" + ], + "jobs": 1, + "warm_start": false, + "include_ambient": false, + "overrides": { + "KIMETSU_BRAIN_EMBEDDER": "bge-small-en-v1.5", + "KIMETSU_DETECT_CONFLICTS": "0", + "KIMETSU_RESOLVE_CONFLICTS": "0", + "FASTEMBED_CACHE_DIR": "E:/Kimetsu/.fastembed_cache", + "HF_HOME": "E:/tmp/kimetsu-brain-hardening/tmp-tests/hf-home" + }, + "baseline_threads": 0, + "candidate_threads": 0, + "baseline_reranker": "ms-marco-tinybert-l-2-v2", + "candidate_reranker": "mmarco-minilm-l12-v2-int8", + "baseline_rerank_floor": null, + "candidate_rerank_floor": 0.9 + }, + "runs": [ + { + "label": "baseline", + "repeat": 1, + "intra_threads_override": null, + "rerank_floor_override": null, + "reranker_override": "ms-marco-tinybert-l-2-v2", + "wall_seconds": 18.409975899965502, + "report_file": "1-baseline.json" + }, + { + "label": "candidate", + "repeat": 1, + "intra_threads_override": null, + "rerank_floor_override": "0.9", + "reranker_override": "mmarco-minilm-l12-v2-int8", + "wall_seconds": 33.57585199997993, + "report_file": "1-candidate.json" + }, + { + "label": "candidate", + "repeat": 2, + "intra_threads_override": null, + "rerank_floor_override": "0.9", + "reranker_override": "mmarco-minilm-l12-v2-int8", + "wall_seconds": 32.38627810002072, + "report_file": "2-candidate.json" + }, + { + "label": "baseline", + "repeat": 2, + "intra_threads_override": null, + "rerank_floor_override": null, + "reranker_override": "ms-marco-tinybert-l-2-v2", + "wall_seconds": 19.08131079998566, + "report_file": "2-baseline.json" + }, + { + "label": "baseline", + "repeat": 3, + "intra_threads_override": null, + "rerank_floor_override": null, + "reranker_override": "ms-marco-tinybert-l-2-v2", + "wall_seconds": 17.480375199986156, + "report_file": "3-baseline.json" + }, + { + "label": "candidate", + "repeat": 3, + "intra_threads_override": null, + "rerank_floor_override": "0.9", + "reranker_override": "mmarco-minilm-l12-v2-int8", + "wall_seconds": 34.61685310001485, + "report_file": "3-candidate.json" + } + ], + "comparison": { + "measurement_summary": { + "baseline": { + "unique_queries": 22, + "query_observations": 66, + "positive_queries": 11, + "negative_queries": 11, + "stale_queries": 2, + "positive_recall_at_4": 0.7272727272727273, + "positive_hit_at_4": 0.7272727272727273, + "positive_mrr": 0.7272727272727273, + "negative_injection_rate": 0.18181818181818182, + "stale_injection_rate": 0, + "first_query_mean_ms": 731.5282555555556, + "subsequent_query_p50_ms": 353.87039999999996, + "subsequent_query_p95_ms": 382.7792, + "subsequent_observations": 48, + "mean_model_text_bytes": 344.90909090909093, + "mean_mcp_result_bytes": 416.90909090909093, + "memory_observations": 66, + "mean_mcp_working_set_bytes": 214308864, + "max_mcp_peak_working_set_bytes": 248958976, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + }, + "candidate": { + "unique_queries": 22, + "query_observations": 66, + "positive_queries": 11, + "negative_queries": 11, + "stale_queries": 2, + "positive_recall_at_4": 0.9545454545454546, + "positive_hit_at_4": 1, + "positive_mrr": 1.0, + "negative_injection_rate": 0, + "stale_injection_rate": 0, + "first_query_mean_ms": 2551.380416666667, + "subsequent_query_p50_ms": 380.7314, + "subsequent_query_p95_ms": 633.8355, + "subsequent_observations": 48, + "mean_model_text_bytes": 349.27272727272725, + "mean_mcp_result_bytes": 421.27272727272725, + "memory_observations": 66, + "mean_mcp_working_set_bytes": 630371607.2727273, + "max_mcp_peak_working_set_bytes": 685080576, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + } + }, + "by_dimension": { + "retrieval": { + "n_scenarios": 5, + "baseline": 0.7, + "candidate": 0.95, + "mean_delta": 0.25, + "ci95": [ + -0.05, + 0.55 + ], + "wins": 3, + "ties": 1, + "losses": 1 + }, + "workflow": { + "n_scenarios": 1, + "baseline": 1.0, + "candidate": 1.0, + "mean_delta": 0.0, + "ci95": null, + "wins": 0, + "ties": 1, + "losses": 0 + } + }, + "scenarios": [ + { + "identity": "retrieval/cross-language-code", + "dimension": "retrieval", + "baseline": 0.25, + "candidate": 1.0, + "delta": 0.75 + }, + { + "identity": "retrieval/exact-code-evidence", + "dimension": "retrieval", + "baseline": 1.0, + "candidate": 1.0, + "delta": 0.0 + }, + { + "identity": "retrieval/live-temporal-applicability", + "dimension": "retrieval", + "baseline": 0.5, + "candidate": 1.0, + "delta": 0.5 + }, + { + "identity": "retrieval/multi-fact-retrieval", + "dimension": "retrieval", + "baseline": 1.0, + "candidate": 0.75, + "delta": -0.25 + }, + { + "identity": "retrieval/related-but-unanswerable", + "dimension": "retrieval", + "baseline": 0.75, + "candidate": 1.0, + "delta": 0.25 + }, + { + "identity": "workflow/persistent-mcp-observes-new-writes", + "dimension": "workflow", + "baseline": 1.0, + "candidate": 1.0, + "delta": 0.0 + } + ], + "unpaired_scenarios": [], + "unpaired_details": [], + "baseline_errors": 0, + "candidate_errors": 0, + "repeats": 3, + "uncertainty_note": "Exploratory paired bootstrap over scenario IDs after averaging repeats; correlated task families require a separate grouped holdout." + } +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-retrieval/summarize.py b/docs/audits/2026-09-07-retrieval/summarize.py new file mode 100644 index 0000000..f0cc713 --- /dev/null +++ b/docs/audits/2026-09-07-retrieval/summarize.py @@ -0,0 +1,37 @@ +"""Summarize saved paired reports without running inference.""" +import json +from pathlib import Path +ROOT=Path(__file__).parent +out={} +for name in ["contract","development","validation"]: + folder=ROOT/name + if not (folder/"comparison.json").exists(): + continue + comparison=json.loads((folder/"comparison.json").read_text(encoding="utf-8")) + assert comparison["status"]=="complete" + info=comparison["comparison"] + assert not info["baseline_errors"] and not info["candidate_errors"] and not info["unpaired_scenarios"] + rows={} + variation={} + for side in ["baseline","candidate"]: + repeats=[] + for i in [1,2,3]: + report=json.loads((folder/f"{i}-{side}.json").read_text(encoding="utf-8")) + repeats.append({(s["id"],q["query"]):q for s in report["scenarios"] for q in s.get("observations",[])}) + rows[side]=repeats[0] + variation[side]=sum(any(r[k]["ranked"]!=repeats[0][k]["ranked"] for r in repeats[1:]) for k in repeats[0]) + assert rows["baseline"].keys()==rows["candidate"].keys() + gains=sum(not b["positive_hit_at_4"] and rows["candidate"][k]["positive_hit_at_4"] for k,b in rows["baseline"].items() if b["positive_hit_at_4"] is not None) + losses=sum(b["positive_hit_at_4"] and not rows["candidate"][k]["positive_hit_at_4"] for k,b in rows["baseline"].items() if b["positive_hit_at_4"] is not None) + out[name]=dict(measurements=info["measurement_summary"],positive_gains=gains,positive_losses=losses,queries_with_ranking_variation=variation) + if name=="validation": + fixture=json.loads((ROOT/"validation-frozen.json").read_text(encoding="utf-8")) + metadata={(s["id"],q["query"]):q for s in fixture["scenarios"] for q in s["queries"]} + cohorts={} + for side,observations in rows.items(): + cohorts[side]={} + for lang in ["en","es"]: + selected=[q for k,q in observations.items() if metadata[k]["language"]==lang] + cohorts[side][lang]=dict(positive_hits=sum(q["positive_hit_at_4"] is True for q in selected),positive_queries=sum(q["positive_hit_at_4"] is not None for q in selected),negative_injections=sum(q["negative_injection"] is True for q in selected),negative_queries=sum(q["negative_injection"] is not None for q in selected)) + out[name]["language_cohorts"]=cohorts +print(json.dumps(out,indent=2)) diff --git a/docs/audits/2026-09-07-retrieval/summary.json b/docs/audits/2026-09-07-retrieval/summary.json new file mode 100644 index 0000000..091fd6e --- /dev/null +++ b/docs/audits/2026-09-07-retrieval/summary.json @@ -0,0 +1,194 @@ +{ + "contract": { + "measurements": { + "baseline": { + "unique_queries": 22, + "query_observations": 66, + "positive_queries": 11, + "negative_queries": 11, + "stale_queries": 2, + "positive_recall_at_4": 0.7272727272727273, + "positive_hit_at_4": 0.7272727272727273, + "positive_mrr": 0.7272727272727273, + "negative_injection_rate": 0.18181818181818182, + "stale_injection_rate": 0, + "first_query_mean_ms": 1018.891488888889, + "subsequent_query_p50_ms": 428.7726, + "subsequent_query_p95_ms": 801.7253, + "subsequent_observations": 48, + "mean_model_text_bytes": 344.90909090909093, + "mean_mcp_result_bytes": 416.90909090909093, + "memory_observations": 66, + "mean_mcp_working_set_bytes": 214227812.84848484, + "max_mcp_peak_working_set_bytes": 248709120, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + }, + "candidate": { + "unique_queries": 22, + "query_observations": 66, + "positive_queries": 11, + "negative_queries": 11, + "stale_queries": 2, + "positive_recall_at_4": 0.9545454545454546, + "positive_hit_at_4": 1, + "positive_mrr": 1.0, + "negative_injection_rate": 0.09090909090909091, + "stale_injection_rate": 0, + "first_query_mean_ms": 2163.8191444444446, + "subsequent_query_p50_ms": 444.8203, + "subsequent_query_p95_ms": 470.4541, + "subsequent_observations": 48, + "mean_model_text_bytes": 359.1363636363636, + "mean_mcp_result_bytes": 431.95454545454544, + "memory_observations": 66, + "mean_mcp_working_set_bytes": 629898457.2121212, + "max_mcp_peak_working_set_bytes": 685191168, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + } + }, + "positive_gains": 3, + "positive_losses": 0, + "queries_with_ranking_variation": { + "baseline": 0, + "candidate": 0 + } + }, + "development": { + "measurements": { + "baseline": { + "unique_queries": 210, + "query_observations": 630, + "positive_queries": 197, + "negative_queries": 13, + "stale_queries": 0, + "positive_recall_at_4": 0.8417935702199661, + "positive_hit_at_4": 0.8578680203045685, + "positive_mrr": 0.850253807106599, + "negative_injection_rate": 0.5384615384615384, + "stale_injection_rate": null, + "first_query_mean_ms": 1200.0951333333333, + "subsequent_query_p50_ms": 969.7701, + "subsequent_query_p95_ms": 1106.8446000000001, + "subsequent_observations": 627, + "mean_model_text_bytes": 1167.5904761904762, + "mean_mcp_result_bytes": 1262.4190476190477, + "memory_observations": 630, + "mean_mcp_working_set_bytes": 287675330.2349206, + "max_mcp_peak_working_set_bytes": 295104512, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + }, + "candidate": { + "unique_queries": 210, + "query_observations": 630, + "positive_queries": 197, + "negative_queries": 13, + "stale_queries": 0, + "positive_recall_at_4": 0.8434856175972927, + "positive_hit_at_4": 0.8629441624365483, + "positive_mrr": 0.8604060913705583, + "negative_injection_rate": 0.38461538461538464, + "stale_injection_rate": null, + "first_query_mean_ms": 2614.5614666666665, + "subsequent_query_p50_ms": 1336.2471, + "subsequent_query_p95_ms": 1767.8703, + "subsequent_observations": 627, + "mean_model_text_bytes": 1022.504761904762, + "mean_mcp_result_bytes": 1112.1714285714286, + "memory_observations": 630, + "mean_mcp_working_set_bytes": 938291479.568254, + "max_mcp_peak_working_set_bytes": 1001422848, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + } + }, + "positive_gains": 6, + "positive_losses": 5, + "queries_with_ranking_variation": { + "baseline": 0, + "candidate": 0 + } + }, + "validation": { + "measurements": { + "baseline": { + "unique_queries": 72, + "query_observations": 216, + "positive_queries": 48, + "negative_queries": 24, + "stale_queries": 8, + "positive_recall_at_4": 0.5, + "positive_hit_at_4": 0.5, + "positive_mrr": 0.5, + "negative_injection_rate": 0.5, + "stale_injection_rate": 0, + "first_query_mean_ms": 663.7127916666666, + "subsequent_query_p50_ms": 324.1918, + "subsequent_query_p95_ms": 354.77180000000004, + "subsequent_observations": 204, + "mean_model_text_bytes": 368.9861111111111, + "mean_mcp_result_bytes": 441.7361111111111, + "memory_observations": 216, + "mean_mcp_working_set_bytes": 217263729.7777778, + "max_mcp_peak_working_set_bytes": 248737792, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + }, + "candidate": { + "unique_queries": 72, + "query_observations": 216, + "positive_queries": 48, + "negative_queries": 24, + "stale_queries": 8, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": 1, + "positive_mrr": 1.0, + "negative_injection_rate": 0.7916666666666666, + "stale_injection_rate": 0, + "first_query_mean_ms": 1821.885925, + "subsequent_query_p50_ms": 368.83050000000003, + "subsequent_query_p95_ms": 401.2908, + "subsequent_observations": 204, + "mean_model_text_bytes": 486.7083333333333, + "mean_mcp_result_bytes": 567.4583333333334, + "memory_observations": 216, + "mean_mcp_working_set_bytes": 639348717.037037, + "max_mcp_peak_working_set_bytes": 685043712, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + } + }, + "positive_gains": 24, + "positive_losses": 0, + "queries_with_ranking_variation": { + "baseline": 0, + "candidate": 0 + }, + "language_cohorts": { + "baseline": { + "en": { + "positive_hits": 24, + "positive_queries": 24, + "negative_injections": 12, + "negative_queries": 12 + }, + "es": { + "positive_hits": 0, + "positive_queries": 24, + "negative_injections": 0, + "negative_queries": 12 + } + }, + "candidate": { + "en": { + "positive_hits": 24, + "positive_queries": 24, + "negative_injections": 8, + "negative_queries": 12 + }, + "es": { + "positive_hits": 24, + "positive_queries": 24, + "negative_injections": 11, + "negative_queries": 12 + } + } + } + } +} diff --git a/docs/audits/2026-09-07-retrieval/sweep.py b/docs/audits/2026-09-07-retrieval/sweep.py new file mode 100644 index 0000000..fe5f502 --- /dev/null +++ b/docs/audits/2026-09-07-retrieval/sweep.py @@ -0,0 +1,17 @@ +"""Development-only score sweep; excludes final byte rendering and latency.""" +import json +from pathlib import Path +rows=json.loads(Path(__file__).with_name("development-scores.json").read_text(encoding="utf-8")) +results=[] +for cutoff in [0,.1,.2,.3,.4,.5,.55,.6,.7,.8,.9,.95]: + hits=noise=pos=neg=0; recall=mrr=0. + for row in rows: + stage=row["stages"][0]; gold=set(row["relevant"]) + candidates=sorted(stage["candidates"],key=lambda c:c["ce"],reverse=True) + admitted=[] if stage["pre_skipped"] else [c["key"] for c in candidates if c["ce"]>=max(cutoff,.9 if stage["top_cosine"] Date: Mon, 7 Sep 2026 02:44:02 -0300 Subject: [PATCH 25/34] Add opt-in explicit-fact admission guard for agent memory --- crates/kimetsu-brain/src/answerability.rs | 558 ++++++++++++++++++ crates/kimetsu-brain/src/lib.rs | 1 + crates/kimetsu-brain/src/serving.rs | 101 +++- crates/kimetsu-chat/src/mcp_server.rs | 36 ++ crates/kimetsu-cli/src/commands/hooks.rs | 27 +- crates/kimetsu-cli/src/embed_daemon/server.rs | 1 + crates/kimetsu-core/src/config.rs | 20 + 7 files changed, 736 insertions(+), 8 deletions(-) create mode 100644 crates/kimetsu-brain/src/answerability.rs diff --git a/crates/kimetsu-brain/src/answerability.rs b/crates/kimetsu-brain/src/answerability.rs new file mode 100644 index 0000000..bd8ffdb --- /dev/null +++ b/crates/kimetsu-brain/src/answerability.rs @@ -0,0 +1,558 @@ +//! A bounded, local check for explicit configuration facts, not an entailment model. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum FactEvidence { + Unrecognized, + ValuePresent, + MissingValue, +} +struct Rule { + query: regex::Regex, + value: regex::Regex, + secret: bool, +} +fn folded(text: &str) -> String { + text.to_lowercase() + .chars() + .map(|c| match c { + 'á' => 'a', + 'é' => 'e', + 'í' => 'i', + 'ó' => 'o', + 'ú' | 'ü' => 'u', + 'ñ' => 'n', + _ => c, + }) + .collect() +} +fn rules() -> &'static [Rule] { + static RULES: std::sync::OnceLock> = std::sync::OnceLock::new(); + RULES.get_or_init(|| { + [ + (r"\b(?:password|passphrase|contrasena)\b", r#"\b(?:password|passphrase|contrasena)\s*(?:is|es|=|:)\s*[`"']?(?P[a-z0-9_+./-]+)"#, true), + (r"\b(?:encryption key|clave de cifrado|clave de encriptacion)\b", r#"\b(?:encryption key|clave de cifrado|clave de encriptacion)\s*(?:is|es|=|:)\s*[`"']?(?P[a-z0-9_+./-]+)"#, true), + (r"\b(?:version|release number)\b", r"\b(?:version|release)\s*(?:is\s+|es\s+|=\s*|:\s*)?v?\d+(?:\.\d+)*\b", false), + (r"\b(?:replica|replicas)\b", r"\b(?:\d+\s+(?:production\s+)?replicas?|replicas?\s*(?:count\s*)?(?:is\s+|are\s+|son\s+|=\s*|:\s*)?\d+)\b", false), + (r"\b(?:retention|retencion)\b|\b(?:days|dias|weeks|semanas)\b.*\b(?:keep|kept|retain\w*|conserv\w*)\b|\bhow long\b.*\b(?:keep|kept|retain\w*)\b", r"\b(?:retention|retencion|retained|retain|keep|kept|conserv\w*)\b[^.;\n]{0,64}\b\d+\s*(?:seconds?|segundos?|minutes?|minutos?|hours?|horas?|days?|dias?|weeks?|semanas?|months?|meses|years?|anos?)\b", false), + (r"\b(?:port|puerto)\b", r"\b(?:port|puerto)\s*(?:is\s+|es\s+|=\s*|:\s*)?\d{1,5}\b", false), + (r"\b(?:timeout|tiempo de espera)\b", r"\b(?:timeout|tiempo de espera)\s*(?:is\s+|es\s+|=\s*|:\s*)?\d+(?:\.\d+)?\s*(?:ms|s|seconds?|segundos?|minutes?|minutos?)\b", false), + (r"\b(?:retries|retry count|reintentos)\b", r"\b(?:retries|retry count|reintentos)\s*(?:is\s+|are\s+|son\s+|=\s*|:\s*)?\d+\b|\b\d+\s+(?:retries|reintentos)\b", false), + (r"\b(?:memory limit|limite de memoria)\b", r"\b(?:memory limit|limite de memoria)\s*(?:is\s+|es\s+|=\s*|:\s*)?\d+\s*(?:kib|mib|gib|kb|mb|gb|bytes)\b", false), + ].into_iter().map(|(query,value,secret)| Rule { + query: regex::Regex::new(query).expect("constant query pattern"), + value: regex::Regex::new(value).expect("constant evidence pattern"), secret, + }).collect() + }) +} +fn concrete(value: &str) -> bool { + !matches!( + value.trim_end_matches('.'), + "unknown" + | "redacted" + | "missing" + | "unavailable" + | "not" + | "stored" + | "configured" + | "required" + | "managed" + | "generated" + | "hidden" + | "provided" + | "set" + | "secret" + | "desconocida" + | "desconocido" + | "configurada" + | "configurado" + ) +} +fn clause_supports(query: &str, body: &str, start: usize, end: usize) -> bool { + static ENTITIES: std::sync::OnceLock> = std::sync::OnceLock::new(); + let entities = ENTITIES.get_or_init(|| { + [ + r"\b(?:database|db|base de datos|sqlite|postgresql|postgres)\b", + r"\b(?:gateway|puerta de enlace)\b", + r"\b(?:client|cliente)\b", + r"\b(?:server|servidor|listener)\b", + r"\b(?:logs?|registros)\b", + r"\b(?:backups?|copias de seguridad)\b", + r"\b(?:workers?|trabajadores)\b", + r"\bsqlite\b", + r"\b(?:postgres|postgresql)\b", + r"\bopenssl\b", + r"\bredis\b", + r"\bpython\b", + r"\bnode\b", + r"\brust\b", + ] + .into_iter() + .map(|p| regex::Regex::new(p).unwrap()) + .collect() + }); + let boundary = |i: usize, c: char| { + c == ';' + || c == ',' + || c == '\n' + || (c == '.' && body[i + 1..].starts_with(char::is_whitespace)) + }; + let left = body[..start] + .char_indices() + .filter(|(i, c)| boundary(*i, *c)) + .map(|(i, _)| i + 1) + .last() + .unwrap_or(0); + let right = body[end..] + .char_indices() + .find(|(i, c)| boundary(end + *i, *c)) + .map(|(i, _)| end + i) + .unwrap_or(body.len()); + let clause = &body[left..right]; + if [ + "example", + "ejemplo", + "hypothetical", + "hipotetic", + "unknown", + "redacted", + "not configured", + "not installed", + "no longer", + ] + .iter() + .any(|word| clause.contains(word)) + { + return false; + } + // Explicit absence is handled separately; ordinary values under negation + // are not affirmative evidence. Keep this conservative within one clause. + static NEGATED: std::sync::OnceLock = std::sync::OnceLock::new(); + let negated = NEGATED.get_or_init(|| { + regex::Regex::new(r"\b(?:not|never|no|nunca|sin|isn't|isnt|don't|doesn't)\b").unwrap() + }); + let matched = &body[start..end]; + let explicit_absence = matched.contains("no password") + || matched.contains("not required") + || matched.contains("requiere contrasena"); + if negated.is_match(clause) && !explicit_absence { + return false; + } + entities + .iter() + .all(|entity| !entity.is_match(query) || entity.is_match(clause)) +} +/// Check explicit configuration questions only. Unrecognized questions retain +/// normal retrieval. ValuePresent means syntactic evidence, not verified truth, +/// entity identity, freshness or general entailment; other retrieval gates remain. +pub fn assess(query: &str, text: &str) -> FactEvidence { + let q = folded(query); + let q = q.trim_start_matches(['¿', ' ', '\t', '\n']); + if ![ + "what ", + "what's ", + "which ", + "how many ", + "how much ", + "how long ", + "que ", + "cual ", + "cuantos ", + "cuantas ", + "cuanto ", + "tell me ", + "dime ", + ] + .iter() + .any(|prefix| q.starts_with(prefix)) + { + return FactEvidence::Unrecognized; + } + if q.split_whitespace() + .any(|w| matches!(w, "should" | "could" | "would" | "deberia" | "debo")) + { + return FactEvidence::Unrecognized; + } + let normalized = folded(text); + let mut body = normalized.as_str(); + if let Some((prefix, rest)) = body.split_once(" - ") { + if prefix.contains(':') && !prefix.contains(' ') { + body = rest; + } + } + while body.starts_with('[') { + if let Some((_, rest)) = body.split_once(']') { + body = rest.trim_start(); + } else { + break; + } + } + // Literal keys are only gated for direct value questions with one target. + // Explanatory and coordinated key questions retain normal retrieval. + if q.contains('`') + && (q.matches('`').count() != 2 + || !["what is ", "what's ", "cual es ", "que valor "] + .iter() + .any(|p| q.starts_with(p))) + { + return FactEvidence::Unrecognized; + } + // Exact literal config keys generalize beyond the curated natural-language + // attributes. Do not treat a different key's value as an answer. + if let Some(key) = q.split('`').nth(1).filter(|key| { + key.contains(['.', '_']) + && key + .chars() + .all(|c| c.is_ascii_alphanumeric() || "_.-".contains(c)) + }) { + static ASSIGNMENT: std::sync::OnceLock = std::sync::OnceLock::new(); + let pattern = ASSIGNMENT.get_or_init(|| { + regex::Regex::new( + r#"\b(?P[a-z_][a-z0-9_.-]*)`?\s*(?:=|:)\s*[`"']?(?P[a-z0-9_+./-]+)"#, + ) + .unwrap() + }); + return if pattern.captures_iter(body).any(|c| { + &c["key"] == key + && concrete(&c["value"]) + && clause_supports(q, body, c.get(0).unwrap().start(), c.get(0).unwrap().end()) + }) { + FactEvidence::ValuePresent + } else { + FactEvidence::MissingValue + }; + } + // A single-attribute guard cannot adjudicate compound questions. Leave + // those to normal retrieval so complementary capsules remain available. + let matches: Vec<_> = rules() + .iter() + .filter_map(|r| r.query.find(q).map(|m| (m.start(), r))) + .collect(); + if matches.len() != 1 { + return FactEvidence::Unrecognized; + } + let (offset, rule) = matches[0]; + // Only accept a constrained noun phrase before the attribute. Unknown + // wording passes through rather than suppressing troubleshooting evidence. + let attribute = rule.query.find(q).unwrap(); + let suffix = q[attribute.end()..].trim_start(); + if [ + "control", + "hashing", + "policy", + "rotation", + "conflict", + "file", + "algorithm", + "management", + ] + .iter() + .any(|word| suffix.starts_with(word)) + { + return FactEvidence::Unrecognized; + } + let prefix = &q[..offset]; + let proper_names: Vec<_> = query + .split_whitespace() + .filter(|w| w.chars().next().is_some_and(char::is_uppercase)) + .map(folded) + .collect(); + if !prefix.split_whitespace().all(|word| { + matches!( + word, + "what" + | "what's" + | "which" + | "is" + | "are" + | "the" + | "a" + | "an" + | "how" + | "many" + | "much" + | "long" + | "que" + | "cual" + | "es" + | "la" + | "el" + | "cuantos" + | "cuantas" + | "cuanto" + | "tell" + | "me" + | "dime" + | "current" + | "configured" + | "required" + | "authentication" + | "request" + | "tcp" + | "http" + | "database" + | "db" + | "sqlite" + | "postgresql" + | "postgres" + | "gateway" + | "client" + | "server" + | "listener" + | "worker" + | "memory" + | "production" + | "staging" + | "backup" + | "log" + | "logs" + | "base" + | "de" + | "datos" + | "del" + ) || proper_names.iter().any(|name| name == word) + }) { + return FactEvidence::Unrecognized; + } + if rule.secret { + static ABSENT: std::sync::OnceLock = std::sync::OnceLock::new(); + let absent = ABSENT.get_or_init(|| regex::Regex::new(r"\b(?:no password (?:is )?required|password (?:is )?not required|no (?:se )?requiere contrasena)\b").unwrap()); + if rule.query.as_str().contains("password") + && absent + .find_iter(body) + .any(|m| clause_supports(q, body, m.start(), m.end())) + { + return FactEvidence::ValuePresent; + } + } + if rule.value.captures_iter(body).any(|c| { + (!rule.secret || concrete(&c["value"])) + && clause_supports(q, body, c.get(0).unwrap().start(), c.get(0).unwrap().end()) + }) { + FactEvidence::ValuePresent + } else { + FactEvidence::MissingValue + } +} + +/// Apply the same explicit-fact policy to MCP and the lightweight hook. +pub fn filter_bundle(query: &str, bundle: &mut crate::context::ContextBundle) { + for capsule in std::mem::take(&mut bundle.capsules) { + if assess(query, &capsule.summary) == FactEvidence::MissingValue { + bundle.excluded.push(capsule); + } else { + bundle.capsules.push(capsule); + } + } + bundle.used_tokens = bundle.capsules.iter().map(|c| c.token_estimate).sum(); + bundle.top_score = bundle + .capsules + .iter() + .map(|c| c.score) + .fold(0.0_f32, f32::max); + if bundle.capsules.is_empty() { + bundle.skipped = true; + bundle.evidence_coverage = 0.0; + } +} +/// Compression must not hide the value used for admission. Final byte budgeting +/// still decides whether the complete evidence fits. +pub fn compress_preserving_evidence(query: &str, summary: &str, sentences: usize) -> String { + let short = crate::context::compress_for_render(summary, sentences); + if assess(query, summary) == FactEvidence::ValuePresent + && assess(query, &short) == FactEvidence::MissingValue + { + summary.to_owned() + } else { + short + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn related_topics_do_not_supply_missing_configuration_values() { + for (q, text) in [ + ( + "What password does the staging listener require?", + "The staging listener binds port 6319.", + ), + ( + "¿Qué versión de SQLite requiere el proyecto?", + "The project uses SQLite WAL mode.", + ), + ( + "How many replicas run in production?", + "Production runs in eu-north-1.", + ), + ( + "¿Cuántos días se conservan las copias?", + "Backups run daily at 02:40 UTC.", + ), + ( + "What is the encryption key?", + "Encryption is enabled for the database.", + ), + ( + "What is the request timeout?", + "Requests are logged every 30 seconds.", + ), + ("What is `cache.max_entries`?", "cache.min_entries = 200"), + ] { + assert_eq!(assess(q, text), FactEvidence::MissingValue, "{q}"); + } + } + #[test] + fn explicit_values_survive_in_both_languages() { + for (q, text) in [ + ( + "What password is configured?", + "password = `sample-only-value`", + ), + ( + "¿Qué versión de SQLite requiere el proyecto?", + "SQLite version 3.46 is required.", + ), + ( + "How many replicas run in production?", + "Production runs 4 replicas.", + ), + ( + "¿Cuántos días se conservan las copias?", + "Backups are retained for 21 days.", + ), + ( + "What is the encryption key?", + "encryption key = `sample-only-key`", + ), + ( + "What is the request timeout?", + "The request timeout is 30 seconds.", + ), + ("What is `cache.max_entries`?", "cache.max_entries = 200"), + ( + "Which TCP port is configured?", + "The listener binds TCP port 6319.", + ), + ] { + assert_eq!(assess(q, text), FactEvidence::ValuePresent, "{q}"); + } + } + #[test] + fn mentions_and_unknown_values_are_not_answers() { + for text in [ + "The password is unknown.", + "Set a password before starting.", + "password = [REDACTED]", + "[tags: password] The listener port is 6319.", + ] { + assert_eq!( + assess("What is the password?", text), + FactEvidence::MissingValue, + "{text}" + ); + } + } + #[test] + fn explicit_absence_and_short_retention_are_useful_answers() { + assert_eq!( + assess( + "What password is required?", + "No password is required for this listener." + ), + FactEvidence::ValuePresent + ); + assert_eq!( + assess("How long are logs kept?", "Logs are kept for 12 hours."), + FactEvidence::ValuePresent + ); + assert_eq!( + assess( + "What should I do about a version conflict?", + "Read the migration notes." + ), + FactEvidence::Unrecognized + ); + } + #[test] + fn a_value_for_another_component_is_not_an_answer() { + for (q, text) in [ + ( + "What is the database timeout?", + "Gateway timeout is 30 seconds. The database stores state.", + ), + ( + "What is the SQLite version?", + "PostgreSQL version 16 is installed.", + ), + ( + "¿Qué contraseña requiere la base de datos?", + "The gateway password is `example-value-only`.", + ), + ( + "How long are logs retained?", + "Backups are retained for 21 days.", + ), + ( + "What is the password?", + "Example: password = `demo-only-value`", + ), + ] { + assert_eq!(assess(q, text), FactEvidence::MissingValue, "{q}"); + } + } + #[test] + fn review_scope_regressions() { + for q in [ + "What causes a version conflict?", + "What version control system do we use?", + "What password hashing algorithm do we use?", + "What does `cache.max_entries` control?", + "What are `cache.max_entries` and `cache.ttl`?", + "Which files configure the port?", + "What are the database port and password?", + ] { + assert_eq!( + assess(q, "See configuration notes."), + FactEvidence::Unrecognized, + "{q}" + ); + } + } + #[test] + fn review_negated_values() { + for text in [ + "We do not use SQLite version 3.45.", + "No usamos SQLite version 3.45.", + ] { + assert_eq!( + assess("What is the SQLite version?", text), + FactEvidence::MissingValue, + "{text}" + ); + } + } + #[test] + fn review_competing_clause() { + assert_eq!( + assess( + "What is the database timeout?", + "Gateway timeout is 30 seconds, while the database stores state." + ), + FactEvidence::MissingValue + ); + } + #[test] + fn broad_tasks_remain_outside_this_bounded_guard() { + for q in [ + "How do I configure passwords safely?", + "Fix the SQLite migration failure", + "Explain memory retrieval", + "Why does my test hang?", + ] { + assert_eq!( + assess(q, "Relevant troubleshooting advice."), + FactEvidence::Unrecognized + ); + } + } +} diff --git a/crates/kimetsu-brain/src/lib.rs b/crates/kimetsu-brain/src/lib.rs index 496672d..6b3152d 100644 --- a/crates/kimetsu-brain/src/lib.rs +++ b/crates/kimetsu-brain/src/lib.rs @@ -2,6 +2,7 @@ pub mod ambient; pub mod analytics; #[cfg(feature = "embeddings")] pub mod ann; +pub mod answerability; /// S5.1: retrieval backend trait + FlatBackend implementation. pub(crate) mod backend; /// S5.4: cross-backend benchmark harness (flat / graph-lite / petgraph). diff --git a/crates/kimetsu-brain/src/serving.rs b/crates/kimetsu-brain/src/serving.rs index f2ccbe6..efe5a48 100644 --- a/crates/kimetsu-brain/src/serving.rs +++ b/crates/kimetsu-brain/src/serving.rs @@ -54,6 +54,7 @@ pub struct ServingPolicy { pub cap: usize, pub pool: usize, pub rerank_floor: f32, + pub explicit_fact_guard: bool, } impl Default for ServingPolicy { fn default() -> Self { @@ -62,6 +63,7 @@ impl Default for ServingPolicy { cap: DEFAULT_CAP, pool: RERANK_POOL, rerank_floor: RERANK_FLOOR, + explicit_fact_guard: false, } } } @@ -69,6 +71,7 @@ impl ServingPolicy { pub fn from_config(config: &kimetsu_core::config::ProjectConfig) -> Self { Self { rerank_floor: config.broker.rerank_min_score, + explicit_fact_guard: config.broker.explicit_fact_guard, ..Self::default() } } @@ -98,13 +101,43 @@ impl ServingPolicy { reranker, abstain, self.rerank_floor, - self.cap, + if self.explicit_fact_guard { + 0 + } else { + self.cap + }, ); + if self.explicit_fact_guard { + crate::answerability::filter_bundle(query, &mut bundle); + } if self.cap > 0 { - bundle.capsules.truncate(self.cap) + bundle.capsules.truncate(self.cap); + } + bundle.used_tokens = bundle.capsules.iter().map(|c| c.token_estimate).sum(); + if bundle.capsules.is_empty() { + bundle.skipped = true; + bundle.evidence_coverage = 0.0; } bundle } + pub fn render_for_query( + &self, + query: &str, + mut bundle: ContextBundle, + compress: bool, + exposure_id: &str, + ) -> Delivery { + if compress { + for capsule in &mut bundle.capsules { + capsule.summary = if self.explicit_fact_guard { + crate::answerability::compress_preserving_evidence(query, &capsule.summary, 3) + } else { + crate::context::compress_for_render(&capsule.summary, 3) + }; + } + } + self.render(bundle, false, exposure_id) + } pub fn render(&self, mut bundle: ContextBundle, compress: bool, exposure_id: &str) -> Delivery { if compress { for c in &mut bundle.capsules { @@ -182,7 +215,8 @@ impl ServingPolicy { checked_scores.as_ref().map(|r| r as &dyn Reranker), abstain, ); - Ok(self.render( + Ok(self.render_for_query( + &query, bundle, session.config().broker.compress_capsules, exposure_id, @@ -348,6 +382,67 @@ mod tests { assert!(matches!(request.min_semantic_score, 0.35 | 0.0)); }); } + #[test] + fn compression_keeps_the_value_that_justified_admission() { + let capsule = ContextCapsule::wire_minimal( + "Background one. Background two. Background three. password = `test-value-only`".into(), + "memory".into(), + 0.99, + ); + let bundle = ContextBundle { + stage: "localization".into(), + budget_tokens: 6000, + used_tokens: 0, + capsules: vec![capsule], + excluded: vec![], + skipped: false, + top_score: 0.99, + top_abs_evidence: 0.99, + evidence_coverage: 1.0, + uncovered_terms: vec![], + chronological: false, + }; + let policy = ServingPolicy { + explicit_fact_guard: true, + ..Default::default() + }; + let delivered = + policy.render_for_query("What is the password?", bundle, true, EVAL_EXPOSURE_ID); + assert!(delivered.payload.to_string().contains("test-value-only")); + } + #[test] + fn explicit_fact_guard_excludes_topic_match_before_output_cap() { + let capsules = [ + "The listener binds port 6319.", + "password = `test-value-only`", + ] + .into_iter() + .map(|text| ContextCapsule::wire_minimal(text.into(), "memory".into(), 0.99)) + .collect(); + let bundle = ContextBundle { + stage: "localization".into(), + budget_tokens: 6000, + used_tokens: 0, + capsules, + excluded: vec![], + skipped: false, + top_score: 0.99, + top_abs_evidence: 0.99, + evidence_coverage: 1.0, + uncovered_terms: vec![], + chronological: false, + }; + let policy = ServingPolicy { + cap: 1, + explicit_fact_guard: true, + ..Default::default() + }; + let selected = policy.arbitrate("What password is required?", bundle, None, 0.0); + assert_eq!(selected.capsules.len(), 1); + assert!(selected.capsules[0].summary.contains("test-value-only")); + assert_eq!(selected.excluded.len(), 1); + } + #[test] fn reranker_floor_and_final_serialization_reject_candidates_before_measurement() { let capsules = [("hit", "wal checkpoint"), ("noise", "remote network")] diff --git a/crates/kimetsu-chat/src/mcp_server.rs b/crates/kimetsu-chat/src/mcp_server.rs index 75a7847..f115e76 100644 --- a/crates/kimetsu-chat/src/mcp_server.rs +++ b/crates/kimetsu-chat/src/mcp_server.rs @@ -3118,6 +3118,42 @@ mod tests { }); } + #[test] + fn mcp_fact_guard_rejects_high_scoring_topic_and_respects_opt_out() { + struct HighScore; + impl kimetsu_brain::embeddings::Reranker for HighScore { + fn model_id(&self) -> &str { + "fixed-high-score" + } + fn rerank( + &self, + _: &str, + docs: &[&str], + ) -> Result, kimetsu_brain::embeddings::EmbedderError> { + Ok(vec![0.99; docs.len()]) + } + } + kimetsu_brain::user_brain::with_user_brain_disabled(|| { + let root = temp_root("mcp-fact-guard"); + fs::create_dir_all(&root).unwrap(); + project::init_project(&root, false).unwrap(); + project::add_memory(&root,MemoryScope::Project,MemoryKind::Fact,"The staging listener password is managed in configuration. The staging listener binds port 6319.").unwrap(); + let paths = kimetsu_core::paths::ProjectPaths::discover(&root).unwrap(); + let original = fs::read_to_string(&paths.project_toml).unwrap(); + for (enabled, count) in [(true, 0), (false, 1)] { + let mut config: toml::Value = toml::from_str(&original).unwrap(); + config["broker"] + .as_table_mut() + .unwrap() + .insert("explicit_fact_guard".into(), toml::Value::Boolean(enabled)); + fs::write(&paths.project_toml, toml::to_string(&config).unwrap()).unwrap(); + let result=brain_context_tool(&root,&json!({"query":"What password does the staging listener require?","include_ambient":false,"min_score":0.0,"min_lexical_coverage":0.0,"abstain_evidence":0.0,"budget_tokens":6000}),Some(&HighScore)).unwrap(); + assert_eq!(result["capsule_count"], count, "guard={enabled}: {result}"); + } + fs::remove_dir_all(root).unwrap(); + }); + } + #[test] fn stdio_uses_configured_reranker_off_and_initialization_error_explicitly() { kimetsu_brain::user_brain::with_user_brain_disabled(|| { diff --git a/crates/kimetsu-cli/src/commands/hooks.rs b/crates/kimetsu-cli/src/commands/hooks.rs index 7ab476e..b0882bc 100644 --- a/crates/kimetsu-cli/src/commands/hooks.rs +++ b/crates/kimetsu-cli/src/commands/hooks.rs @@ -100,7 +100,7 @@ pub(crate) fn brain_context_hook(args: ContextHookArgs) -> KimetsuResult<()> { // Retrieval: try the warm daemon first (semantic); fall back to // floored-FTS on any miss (daemon disabled / unreachable / cold). - let (bundle, retrieval_path) = match try_daemon_retrieve(&workspace, &request) { + let (mut bundle, retrieval_path) = match try_daemon_retrieve(&workspace, &request) { Some(b) => (b, "daemon"), None => match project::retrieve_context_lexical_readonly(&workspace, request.clone()) { Ok(b) => (b, "fts_fallback"), @@ -108,6 +108,15 @@ pub(crate) fn brain_context_hook(args: ContextHookArgs) -> KimetsuResult<()> { }, }; + let explicit_fact_guard = kimetsu_core::paths::ProjectPaths::discover(&workspace) + .ok() + .and_then(|paths| project::load_config(&paths).ok()) + .map(|cfg| cfg.broker.explicit_fact_guard) + .unwrap_or(false); + if explicit_fact_guard { + kimetsu_brain::answerability::filter_bundle(&request.query, &mut bundle); + } + // C7: emit a context.served event BEFORE the early-return so misses are // logged. Best-effort (let _ =) — telemetry must never break the hook. // Gate behind KIMETSU_BRAIN_LOG_RETRIEVAL=0 opt-out (default ON). @@ -272,15 +281,23 @@ pub(crate) fn brain_context_hook(args: ContextHookArgs) -> KimetsuResult<()> { // v1.5 (Story 2.1): render-time compression — runs AFTER retrieval and // reranking, purely on the injected text. Full summary untouched in DB. let rendered: String = if compress_capsules { - kimetsu_brain::context::compress_for_render(&capsule.summary, 3) + if explicit_fact_guard { + kimetsu_brain::answerability::compress_preserving_evidence( + &request.query, + &capsule.summary, + 3, + ) + } else { + kimetsu_brain::context::compress_for_render(&capsule.summary, 3) + } } else { capsule.summary.clone() }; // Strip the "scope:kind - " prefix from the summary for readability let text = rendered - .split(" - ") - .nth(1) - .map(str::to_string) + .split_once(" - ") + .filter(|(prefix, _)| prefix.contains(':') && !prefix.contains(' ')) + .map(|(_, text)| text.to_owned()) .unwrap_or(rendered); additional_context.push('\n'); // F3 Pass B (3.3): prepend the answer-grade marker to the first capsule diff --git a/crates/kimetsu-cli/src/embed_daemon/server.rs b/crates/kimetsu-cli/src/embed_daemon/server.rs index fca933b..e07cf45 100644 --- a/crates/kimetsu-cli/src/embed_daemon/server.rs +++ b/crates/kimetsu-cli/src/embed_daemon/server.rs @@ -71,6 +71,7 @@ impl DaemonState { cap, pool: RERANK_POOL, rerank_floor: session.config().broker.rerank_min_score, + explicit_fact_guard: session.config().broker.explicit_fact_guard, }; let request = ContextRequest { stage: if args.stage.is_empty() { diff --git a/crates/kimetsu-core/src/config.rs b/crates/kimetsu-core/src/config.rs index 11eac11..ea902db 100644 --- a/crates/kimetsu-core/src/config.rs +++ b/crates/kimetsu-core/src/config.rs @@ -882,6 +882,11 @@ pub struct BrokerSection { deserialize_with = "deserialize_rerank_min_score" )] pub rerank_min_score: f32, + /// Require visible value evidence for recognized explicit configuration + /// questions. Experimental, opt-in English/Spanish rules; not a general + /// entailment check. Unsupported wording retains normal retrieval. + #[serde(default)] + pub explicit_fact_guard: bool, /// F3: floor for the adaptive per-stage brain budget. Small tasks /// receive at least this many tokens so the brain is never starved. /// `#[serde(default)]` keeps pre-F3 project.toml files loading cleanly. @@ -1063,6 +1068,7 @@ impl Default for BrokerSection { normalization: default_normalization(), abstain_min_score: default_abstain_min_score(), rerank_min_score: default_rerank_min_score(), + explicit_fact_guard: false, budget_floor_tokens: default_budget_floor_tokens(), budget_run_cap_tokens: default_budget_run_cap_tokens(), ambient: default_true(), @@ -1453,6 +1459,20 @@ impl Default for LifecycleSection { #[cfg(test)] mod tests { + #[test] + fn explicit_fact_guard_is_opt_in_and_round_trips() { + let default = ProjectConfig::default_for_project("guard"); + assert!(!default.broker.explicit_fact_guard); + for enabled in [false, true] { + let mut value = serde_json::to_value(&default).unwrap(); + value["broker"]["explicit_fact_guard"] = serde_json::json!(enabled); + let config: ProjectConfig = serde_json::from_value(value).unwrap(); + assert_eq!( + serde_json::to_value(config).unwrap()["broker"]["explicit_fact_guard"], + enabled + ); + } + } #[test] fn rerank_cutoff_survives_configuration_roundtrip_and_rejects_invalid_values() { let mut value = serde_json::to_value(ProjectConfig::default_for_project("cutoff")).unwrap(); From 9d893f9f267d8378bd223ca1b7074a8c5069eeac Mon Sep 17 00:00:00 2001 From: RodCor Date: Mon, 7 Sep 2026 03:00:58 -0300 Subject: [PATCH 26/34] Record answerability quality and performance comparisons --- .gitattributes | 1 + docs/audits/2026-09-07-answerability.md | 81 + .../2026-09-07-answerability/check-hook.py | 34 + .../2026-09-07-answerability/manifest.json | 58 + .../results/development/1-baseline.json | 6811 +++++++++++++++++ .../results/development/1-candidate.json | 6811 +++++++++++++++++ .../results/development/comparison.json | 155 + .../results/development/comparison.md | 26 + .../missing-fact-development/1-baseline.json | 2130 ++++++ .../missing-fact-development/1-candidate.json | 1921 +++++ .../missing-fact-development/comparison.json | 179 + .../missing-fact-development/comparison.md | 26 + .../1-baseline.json | 2130 ++++++ .../1-candidate.json | 1921 +++++ .../2-baseline.json | 2130 ++++++ .../2-candidate.json | 1921 +++++ .../comparison.json | 199 + .../comparison.md | 26 + .../results/validation/1-baseline.json | 1241 +++ .../results/validation/1-candidate.json | 1101 +++ .../results/validation/comparison.json | 165 + .../results/validation/comparison.md | 26 + .../run-comparisons.ps1 | 40 + .../2026-09-07-answerability/summarize.py | 36 + .../2026-09-07-answerability/summary.json | 270 + .../validation-frozen.json | 365 + .../validation-preflight-invalid.json | 365 + 27 files changed, 30169 insertions(+) create mode 100644 docs/audits/2026-09-07-answerability.md create mode 100644 docs/audits/2026-09-07-answerability/check-hook.py create mode 100644 docs/audits/2026-09-07-answerability/manifest.json create mode 100644 docs/audits/2026-09-07-answerability/results/development/1-baseline.json create mode 100644 docs/audits/2026-09-07-answerability/results/development/1-candidate.json create mode 100644 docs/audits/2026-09-07-answerability/results/development/comparison.json create mode 100644 docs/audits/2026-09-07-answerability/results/development/comparison.md create mode 100644 docs/audits/2026-09-07-answerability/results/missing-fact-development/1-baseline.json create mode 100644 docs/audits/2026-09-07-answerability/results/missing-fact-development/1-candidate.json create mode 100644 docs/audits/2026-09-07-answerability/results/missing-fact-development/comparison.json create mode 100644 docs/audits/2026-09-07-answerability/results/missing-fact-development/comparison.md create mode 100644 docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/1-baseline.json create mode 100644 docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/1-candidate.json create mode 100644 docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/2-baseline.json create mode 100644 docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/2-candidate.json create mode 100644 docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/comparison.json create mode 100644 docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/comparison.md create mode 100644 docs/audits/2026-09-07-answerability/results/validation/1-baseline.json create mode 100644 docs/audits/2026-09-07-answerability/results/validation/1-candidate.json create mode 100644 docs/audits/2026-09-07-answerability/results/validation/comparison.json create mode 100644 docs/audits/2026-09-07-answerability/results/validation/comparison.md create mode 100644 docs/audits/2026-09-07-answerability/run-comparisons.ps1 create mode 100644 docs/audits/2026-09-07-answerability/summarize.py create mode 100644 docs/audits/2026-09-07-answerability/summary.json create mode 100644 docs/audits/2026-09-07-answerability/validation-frozen.json create mode 100644 docs/audits/2026-09-07-answerability/validation-preflight-invalid.json diff --git a/.gitattributes b/.gitattributes index b5f5b65..967055e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ # Preserve the exact bytes of reproducible audit artifacts. docs/audits/2026-09-07-retrieval/** -text whitespace=cr-at-eol +docs/audits/2026-09-07-answerability/** -text whitespace=cr-at-eol diff --git a/docs/audits/2026-09-07-answerability.md b/docs/audits/2026-09-07-answerability.md new file mode 100644 index 0000000..130e86b --- /dev/null +++ b/docs/audits/2026-09-07-answerability.md @@ -0,0 +1,81 @@ +# Explicit-fact answerability experiment — 2026-09-07 + +Status: implementation, review and paired comparisons complete. The guard is opt-in; it is not a general answerability classifier. + +## Problem and implementation + +A relevance reranker can give a high score to a memory about the requested subject even when it lacks the requested fact. A listener port does not supply an authentication password, and a backup schedule does not supply retention duration. + +`broker.explicit_fact_guard` adds a local, deterministic admission check for selected English/Spanish configuration questions: passwords, encryption keys, versions, replicas, retention, ports, timeouts, retries, memory limits, and direct questions about literal configuration keys. It requires visible syntactic value evidence in the capsule. It makes no additional model or API call. Injected context still consumes the receiving agent's context tokens. + +The shared serving path applies the check after relevance arbitration and before the final capsule cap. The context hook also applies it to lexical fallback. Compression preserves the value that justified admission; the final serving byte budget still applies. Hook prefix stripping now preserves text after a second separator. This does not extend answerability checks to general proactive tool context or warm-start project orientation. + +The check returns `Unrecognized`, `ValuePresent`, or `MissingValue`. Only the last removes a capsule. Broad tasks, unsupported wording, and coordinated attribute questions retain normal retrieval. Negation and some competing-component clauses are rejected; explicit absence such as “No password is required” can be useful evidence. + +## Why opt-in + +Finite language rules cannot establish general entailment, entity identity, completeness, or truth. Review exposed ordinary false negatives in broad questions and literal-key explanations, which now have regression coverage. More paraphrases, languages, indirect evidence, and component relationships remain outside the grammar. Unknown or missed wording can still inject irrelevant memory. Passing synthetic tests would not establish default-on suitability. + +To enable in a test project: + +```powershell +kimetsu config set broker.explicit_fact_guard true +kimetsu config get broker.explicit_fact_guard +``` + +Set it to `false` to restore ordinary admission. No live user configuration or installed daemon was changed by this experiment. + +## Evaluation protocol + +BrainBenchmark compares **the same candidate binary and model**, with guard false versus true, in isolated temporary projects. Each side explicitly configures the guard and checks effective configuration readback. A binary that silently ignores the setting causes a failure. Overrides and binary hashes are recorded in each comparison. + +- Existing 210-query development set: TinyBERT, raw rerank floor 0.30. +- Previously inspected 72-query missing-fact set: multilingual mMARCO, floor 0.55. This is development evidence for this phase, not held-out validation. +- Fresh frozen 44-query synthetic validation: multilingual mMARCO, floor 0.55. Includes 24 answerable and 20 missing-fact queries, broad-question controls, Spanish paraphrases, and an unsupported region request. SHA256: `ea4452872956beed1030ec071572db89435780474180b17a7bf7fe8f481e5d7e`. + +Before fixture inference, a CLI preflight showed that ingestion redacts literal passwords. The two affected memories were changed to explicit password absence, and the original is retained as `validation-preflight-invalid.json`. No query results from either fixture informed this change. + +Each comparison uses budget 6000, one worker, warm-start and ambient context off, and one paired repetition. Rankings are measured on delivered text. One repetition limits latency conclusions; two synthetic validation project families do not provide an independent real-agent population estimate. No policy tuning is permitted on the fresh fixture results. + +Positive hit rate = answerable queries with a delivered relevant memory / answerable queries. Negative injection rate = unanswerable queries receiving any memory / unanswerable queries. Both must be reported: reducing injection by discarding useful evidence is not a free improvement. + +## Verification + +- Main workspace: 1,386 passed, 0 failed, 5 ignored. +- Benchmark harness: 131 Rust tests and 18 Python tests passed. +- Release build with embeddings passed (the Windows linker emitted its informational import-library message). +- Actual isolated CLI hook probes passed: suppress missing password, restore normal retrieval with guard off, and preserve an admitted port after a second text separator. +- Review regressions cover broad questions, mixed attributes, negation, competing clauses, redacted values, and compression. Final review found no blockers for opt-in evaluation. + +Production source: `06b2946`. Benchmark source: `1b8fc33`. Candidate binary SHA256: `405d3483fe320e76b0ec776bf9ada3b7771852b04a73f70f3b5da377a43d31c3`. + +## Results + +All three comparisons completed with zero scenario errors or unpaired scenarios: 326 unique queries, 652 observations across the two conditions. + +| Set | Useful hits, guard off → on | Unanswerable queries receiving memory, off → on | +| --- | --- | --- | +| Development (TinyBERT) | 169/197 → 169/197 | 7/13 → 7/13 | +| Inspected missing-fact development (mMARCO) | 48/48 → 48/48 | 19/24 → 0/24 | +| Fresh synthetic validation (mMARCO) | 24/24 → 24/24 | 12/20 → 0/20 | + +There were **no per-query positive hit losses**. Development rankings were unchanged. The guard removed all 31 unwanted injections observed across the two targeted sets, while the broader development set's seven unwanted injections remained. That is evidence of a scoped improvement, not general answerability being solved. Fresh validation's injection rate fell from 60% to 0% (60 percentage points); the inspected set fell from 79.2% to 0%. Zero observed failures in a small synthetic set is not an estimated zero production failure rate. + +| Initial paired timing | p50 off → on | p95 off → on | Peak working set off → on | +| --- | --- | --- | --- | +| Development | 915 → 912 ms | 980 → 989 ms | 279.6 → 282.6 MiB | +| Inspected missing-fact development | 359 → 365 ms | 382 → 662 ms | 653.4 → 653.2 MiB | +| Fresh validation | 356 → 359 ms | 379 → 381 ms | 653.2 → 653.4 MiB | + +The inspected set's tail spike clustered in one project, including unrecognized questions such as a production-region request. A two-repeat follow-up with alternating run order reproduced the quality result exactly (48/48 useful hits on each side; 19/24 unwanted injections off and 0/24 on). Its pooled p50 was 366 → 363 ms and p95 was 517 → 407 ms, off → on; peak working set remained about 654 MiB. Thus the initial tail slowdown did not repeat consistently. Its cause is unproven; these runs do not establish zero overhead or a speedup. The follow-up adds 288 observations, for **940 total observations over 326 unique queries**. + +Fresh validation's mean delivered model-text bytes fell from 401.7 to 340.5 (15.2%), mostly by omitting unanswerable context. This is a byte reduction, not a measured tokenizer count or generated-answer accuracy improvement. + +Reproducible scripts, raw reports, verification logs and the frozen fixture are in [the artifact directory](2026-09-07-answerability/). + + +## Reproduction + +Run `run-comparisons.ps1` with `-Binary`, `-Harness`, and a new `-OutputRoot`. Specify `-ModelCache` and `-HfHome` for cached embedding/reranker artifacts on another machine; the defaults point to this audit workspace. Add `-TimingFollowup` to include the two follow-up repetitions. Run `summarize.py` against the retained `results` directory to regenerate `summary.json`. `check-hook.py ` runs the isolated hook regressions without model inference. + +The guard remains disabled by default. The next evidence needed for promotion is a broader real-project set containing paraphrases, indirect facts, multiple subjects and multi-part questions; this experiment does not cover those sufficiently. diff --git a/docs/audits/2026-09-07-answerability/check-hook.py b/docs/audits/2026-09-07-answerability/check-hook.py new file mode 100644 index 0000000..56570b5 --- /dev/null +++ b/docs/audits/2026-09-07-answerability/check-hook.py @@ -0,0 +1,34 @@ +"""Isolated CLI hook regression; no embedding model or API calls.""" +import json +import os +from pathlib import Path +import subprocess +import sys +import tempfile + +binary = str(Path(sys.argv[1]).resolve()) +env = dict(os.environ, KIMETSU_USER_BRAIN="0", KIMETSU_BRAIN_EMBEDDER="noop", + KIMETSU_EMBED_DAEMON="0", KIMETSU_TIER="free") +for enabled, memory, expected, query in [ + (True, "The staging listener password is managed in configuration. The staging listener binds port 6319.", None, "What password does the staging listener require?"), + (False, "The staging listener password is managed in configuration. The staging listener binds port 6319.", "managed", "What password does the staging listener require?"), + (True, "Staging listener connection settings - the listener port is 6319.", "6319", "What port does the staging listener use?"), +]: + with tempfile.TemporaryDirectory(prefix="answerability-hook-") as folder: + def run(*args, input=None): + result = subprocess.run([binary, *args], cwd=folder, env=env, input=input, + text=True, encoding="utf-8", capture_output=True) + assert result.returncode == 0, result.stderr + return result.stdout + subprocess.run(["git", "init", "--quiet"], cwd=folder, check=True) + run("init") + for key, value in [("broker.explicit_fact_guard", str(enabled).lower()), + ("broker.warm_start", "false"), ("broker.min_lexical_coverage", "0.0"), + ("broker.abstain_min_score", "0.0")]: + run("config", "set", key, value) + assert run("config", "get", "broker.explicit_fact_guard").strip() == str(enabled).lower() + run("brain", "memory", "add", "--scope", "project", "--kind", "fact", memory) + result = run("brain", "context-hook", input=json.dumps({"session_id": "guard-check", + "prompt": query})) + assert (not result.strip()) if expected is None else (expected in result), result + print(f"PASS: guard={enabled}, expected={expected!r}") diff --git a/docs/audits/2026-09-07-answerability/manifest.json b/docs/audits/2026-09-07-answerability/manifest.json new file mode 100644 index 0000000..475c1e8 --- /dev/null +++ b/docs/audits/2026-09-07-answerability/manifest.json @@ -0,0 +1,58 @@ +{ + "source_commit": "06b2946a58a5edbfc3ce6e5ed9ef08dbab367b6a", + "benchmark_commit": "1b8fc3373be92dd9db40ac7967966417a6e4b77c", + "binary_sha256": "405d3483fe320e76b0ec776bf9ada3b7771852b04a73f70f3b5da377a43d31c3", + "files": { + "check-hook.py": "0a7646a8e0f049c164ac228456f22aad9912ad623d5affc26cb8a473046c49fa", + "results/development/1-baseline.json": "4868a4862e3e30171eb895129460691e496a9837c8579666581944cdf8f9ac25", + "results/development/1-baseline.stderr.log": "8880b73f78a01e576b788c916a9feebb5333a394503dd0911987f9ab0bae6855", + "results/development/1-baseline.stdout.log": "972824b01d6cf52da627774528e0a2805ade629ca58794a14fd99f3a3d9ebbd1", + "results/development/1-candidate.json": "7fc557a71fed419d7f4fe2e928de40fb3594af309bec86c19e7aff1b9eb3e100", + "results/development/1-candidate.stderr.log": "32e16396e7f76a42309437865e20ae059dc95a2d1903fa337be5d47a6f7aeabd", + "results/development/1-candidate.stdout.log": "2006da608cd3e2f066ba9079bd90020cc2bec03d628ca0b48b72682f55456f30", + "results/development/comparison.json": "55c99044c9d165d80a79c37bc3cabe0f56cceed82a3c5b8391c83ed1fc437246", + "results/development/comparison.md": "46e74c56f6857c3ef9e75b4f9b65713b82117aa7861d978dbbac41ef1e469d95", + "results/missing-fact-development/1-baseline.json": "d68f6729e84b334506a6bf53567c96ffdbf1c349ccbbbeb0f5805c72c92fb3e9", + "results/missing-fact-development/1-baseline.stderr.log": "d5a02e99f9ce349afa46e6344eb934883b6f5a7b384384bfd9c35a13183d136c", + "results/missing-fact-development/1-baseline.stdout.log": "18d9e83f68af104eb62c9a9fa0e50c704607c6e702a3630a8aec9d3f75899d49", + "results/missing-fact-development/1-candidate.json": "0d755ecd14099bbfa6c55e6a09350fb4633a7506dd34a70ccca83ab187406616", + "results/missing-fact-development/1-candidate.stderr.log": "109009ca165846a72171b801fb348365608d99dff7718a904d15f86017fd3000", + "results/missing-fact-development/1-candidate.stdout.log": "b12d33643ff58224494a2ec432d7d3798e5daa268d5aee09a7c9b0045e6262d8", + "results/missing-fact-development/comparison.json": "ef0d5e7b84fb7b0ec205e2ababf261ca13d989644f1262a86c71be246397c62f", + "results/missing-fact-development/comparison.md": "1a4acb47b4db136d4eaeb8521210a288f79a36748cd3c8c2441cfa85760cb51d", + "results/missing-fact-timing-followup/1-baseline.json": "f28f4803ea87b4d4eeceddfc672188a5bf70bbe3e67a64b10e55697d922e55b3", + "results/missing-fact-timing-followup/1-baseline.stderr.log": "f84dcd6412671f676869d09981aa5aac7e6e848155d2fc5b3f4a1915b46d3a10", + "results/missing-fact-timing-followup/1-baseline.stdout.log": "e7319007044cf0ec603afdcc17154c943dda476fcaf8781c1db57f37c1af642d", + "results/missing-fact-timing-followup/1-candidate.json": "4e002112e86ec6af85e56b7eb367d9332c722bcf44fcdde3e3534eaeae89e971", + "results/missing-fact-timing-followup/1-candidate.stderr.log": "d76e2979c00fcb0920362fbc396198908b55188414b42bdc0688777c6f0fd494", + "results/missing-fact-timing-followup/1-candidate.stdout.log": "380a09d6e90a0dc53a27892174e8b5fb100ae7082002b7f81f44cd5920f95f02", + "results/missing-fact-timing-followup/2-baseline.json": "ac2b070ac395f3c305c31f62820aa91498a4b2ccbeea4235cda4674e34333ca8", + "results/missing-fact-timing-followup/2-baseline.stderr.log": "eefa93ff24e6fd19edf1d12d6dbb60ef236bf02ce2b78ae08cd48b92532327ad", + "results/missing-fact-timing-followup/2-baseline.stdout.log": "7aa597eb49317869f92a1f0f25b3bc31185777f12b6067978005275d567a0a67", + "results/missing-fact-timing-followup/2-candidate.json": "13bc2a9c0d9dc2e6848fbed77a87b764e98b1cc47750833178e5ff3a4dd8c104", + "results/missing-fact-timing-followup/2-candidate.stderr.log": "ad2a0df6cf4032a45f263108fe800db8985b342fade0a044c7bffece3b534c6e", + "results/missing-fact-timing-followup/2-candidate.stdout.log": "0948b019945173839dc7b28c4e1f88d63aea7f43c34eab3fc1a10e314163321a", + "results/missing-fact-timing-followup/comparison.json": "6fa6711b704b44557d932f5e0a56a69d867aeb303949d5c38a5525b05a02cf2e", + "results/missing-fact-timing-followup/comparison.md": "bf3bc7a133605b562fbef06897fddf52cd21c33572a2484ab442c8382e4b2e10", + "results/validation/1-baseline.json": "4a6c2512def31b7b3273d40f8524db3a40f2467910ebf8714a3de10480406640", + "results/validation/1-baseline.stderr.log": "62e3ed1e2cb7766da2c03718419ed652c612e76f5be28b7adb44999076b2a22e", + "results/validation/1-baseline.stdout.log": "1007c7ace4cdef4363d9873d4cbaa13d2d0401c6b56acf6ea9d99dfeb2e09f51", + "results/validation/1-candidate.json": "9e2873b07a313d1713a101681d10463c62ae4b8be698a7033d0880142129f000", + "results/validation/1-candidate.stderr.log": "c24c45ca53cc5ce02464946765e778209cc687e8184fe85ba2274e87293096b1", + "results/validation/1-candidate.stdout.log": "549f46e25cc3fd03d924c6a75f56cc8fdba22266b3786743dcb2d658a182d0a9", + "results/validation/comparison.json": "c050de0ec6d23dbc5fe455321dbeb693617649f416c8cfa6132e1c9307b8a669", + "results/validation/comparison.md": "24392cfc4c37ae8df10a0d524bdc704c04708878644182ad3533ec02dd67ba62", + "run-comparisons.ps1": "18a6c3f55ebf560341de1ac5c5bfda6765577fb98efd17bc40d4e212ff7c9605", + "summarize.py": "88a8e5f15f7fb0c063205ce3d54211f54187e73a690a0bc7ea2131d1fce55983", + "summary.json": "a0a3036c158ef13e1e0b00485062a9ac180099040f372e4b34e1043b61cbbaba", + "validation-frozen.json": "ea4452872956beed1030ec071572db89435780474180b17a7bf7fe8f481e5d7e", + "validation-preflight-invalid.json": "5fb2229fafb222a9780fb0405fe4ff07517e19157e9003211536486e58b88949", + "verification/benchmark-python.log": "e4841613ba7022ff31279bbe1ea331cd30275a1e00f19001815ec781c41d7634", + "verification/benchmark-rust.log": "29b7e6379a814072b5acc8a3bdb263cf91313523ce2ebae57fce15f6d13fe09f", + "verification/hook.log": "73462f8dbe9e583f98e33fe4d4eb632de5beac0d5c5e3cadbc7588a21c95f078", + "verification/release-build.log": "f9219fa5ffce242fd6468a170dc38ad15ffb225d0c8abfad2b43ba2dc54fa01a", + "verification/review-regressions-red.log": "d5799f27a073837cf9c93188297b422909a70e6ac1f32766153608cf722ddbd1", + "verification/scope-green.log": "909e917654185d5c4f240dd31d11002cdb109da018610c5a8a78c6acac1df3c7", + "verification/workspace.log": "4b0ca740717a132d639aecdd7e601a4b96fc7381809c836303e3fad79f0a3db9" + } +} diff --git a/docs/audits/2026-09-07-answerability/results/development/1-baseline.json b/docs/audits/2026-09-07-answerability/results/development/1-baseline.json new file mode 100644 index 0000000..6392348 --- /dev/null +++ b/docs/audits/2026-09-07-answerability/results/development/1-baseline.json @@ -0,0 +1,6811 @@ +{ + "generated_at": "2026-09-07T05:50:26.125556Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-retrieval\\development-100.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "test_env_lock inside with_user_brain_disabled deadlock", + "ranked": [ + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBK795VCKKWK7JEKPT4J", + "id": "01M1X6G6HT1E3191G8DDZ1BB0G", + "kind": "memory", + "score": 0.9999488592147828, + "summary": "project:fact - [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure \u2014 `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1064.0558, + "first_query": true, + "server_startup_ms": 77.69460000000001, + "model_text_bytes": 796, + "mcp_result_bytes": 877, + "wire_bytes": 912, + "reported_used_tokens": 877, + "working_set_bytes": 227401728, + "peak_working_set_bytes": 248041472 + }, + { + "query": "why does my test hang after calling with_user_brain_disabled when I also lock test_env_lock?", + "ranked": [ + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBK795VCKKWK7JEKPT4J", + "id": "01M1X6G78VAMQM1FA042FM8J79", + "kind": "memory", + "score": 0.9990190267562866, + "summary": "project:fact - [2026-09-07] [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure \u2014 `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 831.6073, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 808, + "mcp_result_bytes": 889, + "wire_bytes": 924, + "reported_used_tokens": 889, + "working_set_bytes": 229339136, + "peak_working_set_bytes": 248041472 + }, + { + "query": "ingest_repo_at_root brain_root files_root kimetsu remote", + "ranked": [ + "remote-ingest-split-roots", + "kimetsu-write-tools-gate", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBMAHT4JHT1S9T1YAQCN", + "id": "01M1X6G82SZDATB3KJDX7DYTAR", + "kind": "memory", + "score": 0.999886393547058, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1X6G5C7NRN6660DTHDS58ZS", + "id": "01M1X6G82S70W5ZDFWB37ZRPK2", + "kind": "memory", + "score": 0.8439717888832092, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level \u2014 disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1X6FBP8RS75FNREKQZ0WD3Q", + "id": "01M1X6G82S92TJKRHCGAK1R3K4", + "kind": "memory", + "score": 0.8363722562789917, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 950.3385, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2726, + "mcp_result_bytes": 2891, + "wire_bytes": 2926, + "reported_used_tokens": 2891, + "working_set_bytes": 252162048, + "peak_working_set_bytes": 253071360 + }, + { + "query": "why does the remote server index the wrong directory when I run kimetsu brain ingest?", + "ranked": [ + "remote-ingest-split-roots", + "onnx-dim-mismatch" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBMAHT4JHT1S9T1YAQCN", + "id": "01M1X6G90VD0EBFVR54J94YG9M", + "kind": "memory", + "score": 0.9836117625236512, + "summary": "project:fact - [2026-09-07] [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1X6FG3R3G9YXMC4MA14S64D", + "id": "01M1X6G90VJ386CN1GSSWEG69P", + "kind": "memory", + "score": 0.3657674789428711, + "summary": "project:fact - [2026-09-07] [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results \u2014 the ANN index shape mismatch isn't always caught at runtime." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 934.1104, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1800, + "mcp_result_bytes": 1899, + "wire_bytes": 1934, + "reported_used_tokens": 1899, + "working_set_bytes": 257900544, + "peak_working_set_bytes": 258822144 + }, + { + "query": "kimetsu plugin install --remote mcp.json authorization bearer token", + "ranked": [ + "remote-mcp-host-wiring", + "mcp-stdout-protocol" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBP8RS75FNREKQZ0WD3Q", + "id": "01M1X6G9XRBEYXA378PCFE9JR1", + "kind": "memory", + "score": 0.999605119228363, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + }, + { + "expansion_handle": "memory:01M1X6G1W0ZBD86TBRPQESXKTX", + "id": "01M1X6G9XSA4W90E9T0B4ZRZX3", + "kind": "memory", + "score": 0.3375842869281769, + "summary": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 855.5303, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1472, + "mcp_result_bytes": 1619, + "wire_bytes": 1654, + "reported_used_tokens": 1619, + "working_set_bytes": 258347008, + "peak_working_set_bytes": 259272704 + }, + { + "query": "how do I wire a remote kimetsu brain into Claude Code without storing the token in the config file?", + "ranked": [ + "remote-mcp-host-wiring", + "mcp-tool-naming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBP8RS75FNREKQZ0WD3Q", + "id": "01M1X6GARJ0ZZ2KQKV4NBD7JY8", + "kind": "memory", + "score": 0.9963359832763672, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + }, + { + "expansion_handle": "memory:01M1X6G20244N0FWHDXK4YPMG6", + "id": "01M1X6GARJB67WWWJB2B5V42HK", + "kind": "memory", + "score": 0.831425666809082, + "summary": "project:fact - [tags: mcp tool naming convention kimetsu] MCP tool names must be valid identifiers for all host agents. Claude Code restricts tool names to `[a-zA-Z0-9_-]` and max 64 chars. Use `snake_case` (kimetsu_brain_context, kimetsu_brain_record) \u2014 hyphen is technically allowed but some hosts reject it." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 856.1008, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1454, + "mcp_result_bytes": 1601, + "wire_bytes": 1636, + "reported_used_tokens": 1601, + "working_set_bytes": 258940928, + "peak_working_set_bytes": 259862528 + }, + { + "query": "cargo feature unification kimetsu-brain embeddings fastembed test failure", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-profile-override", + "clap-version-build-flavor" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBR4K1EDGA6RZKFR1659", + "id": "01M1X6GBKD5FEW1YV6R296225T", + "kind": "memory", + "score": 0.9996790885925292, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X6FCT0SQT9SH5HPBEB6HH8", + "id": "01M1X6GBKDXWMC9W6PXCR80FK8", + "kind": "memory", + "score": 0.9923595786094666, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1X6FC2JTJ2W47SX7Q8Z01AV", + "id": "01M1X6GBKD828YFJ51YSH10K58", + "kind": "memory", + "score": 0.585203230381012, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 843.476, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2387, + "mcp_result_bytes": 2524, + "wire_bytes": 2559, + "reported_used_tokens": 2524, + "working_set_bytes": 260677632, + "peak_working_set_bytes": 261599232 + }, + { + "query": "my integration tests pass in isolation but break when I run cargo test --workspace \u2014 embedder changed?", + "ranked": [ + "cargo-feature-unification-embeddings", + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBR4K1EDGA6RZKFR1659", + "id": "01M1X6GCE4MN580GYYAZCSFFD5", + "kind": "memory", + "score": 0.9943140745162964, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X6FC1PYTV8EEQVV2FCY4FV", + "id": "01M1X6GCE4M20JYF5C76SVEFEH", + "kind": "memory", + "score": 0.31398114562034607, + "summary": "project:fact - [2026-09-07] [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 916.6398999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1714, + "mcp_result_bytes": 1817, + "wire_bytes": 1852, + "reported_used_tokens": 1817, + "working_set_bytes": 261357568, + "peak_working_set_bytes": 262279168 + }, + { + "query": "build_anthropic_body bedrock-2023-05-31 InvokeModel blocking reqwest", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBSFGZG953DWGDC5CQJC", + "id": "01M1X6GDB0PCK1CSQR0JT69G9P", + "kind": "memory", + "score": 0.9973788261413574, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X6FBYZ5JT46Z87176E5AER", + "id": "01M1X6GDB0KCR0TJ644574Y4T2", + "kind": "memory", + "score": 0.6916899085044861, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 718.4043, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2193, + "mcp_result_bytes": 2320, + "wire_bytes": 2356, + "reported_used_tokens": 2320, + "working_set_bytes": 261681152, + "peak_working_set_bytes": 262594560 + }, + { + "query": "how do I add AWS Bedrock as a model provider in Kimetsu without pulling in the aws-sdk?", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-region-resolution", + "aws-credentials-chain", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBSFGZG953DWGDC5CQJC", + "id": "01M1X6GE0XBEQD8EFDJ70T25J2", + "kind": "memory", + "score": 0.9998898506164552, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X6G23CEN8Y7G8DV4SNBYQB", + "id": "01M1X6GE0XF34XFNAMYWJKWKFZ", + "kind": "memory", + "score": 0.995676338672638, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X6G225TRC4EVAFGXT9RHRX", + "id": "01M1X6GE0XH08FWA6GZ18S7P2T", + "kind": "memory", + "score": 0.987064242362976, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + }, + { + "expansion_handle": "memory:01M1X6FBYZ5JT46Z87176E5AER", + "id": "01M1X6GE0XCXNMCF0Q6F7PX9H5", + "kind": "memory", + "score": 0.9493880867958068, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 857.4794999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3455, + "mcp_result_bytes": 3618, + "wire_bytes": 3654, + "reported_used_tokens": 3618, + "working_set_bytes": 269643776, + "peak_working_set_bytes": 270561280 + }, + { + "query": "BridgeTarget enum seams plugin_install_inner plugin_status_inner resolve_setup_hosts", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBV74YJ9W6XP288GKM7H", + "id": "01M1X6GEVT1H2XCTD5KYY4WCV4", + "kind": "memory", + "score": 0.9997583031654358, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 722.1896, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1060, + "mcp_result_bytes": 1141, + "wire_bytes": 1177, + "reported_used_tokens": 1141, + "working_set_bytes": 279629824, + "peak_working_set_bytes": 280543232 + }, + { + "query": "I added a new host to the bridge enum but cargo gives me compile errors in five different match arms \u2014 what did I miss?", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBV74YJ9W6XP288GKM7H", + "id": "01M1X6GFJ9S8K6PT5W0A9P8GW4", + "kind": "memory", + "score": 0.9977060556411744, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 931.4042999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1059, + "mcp_result_bytes": 1140, + "wire_bytes": 1176, + "reported_used_tokens": 1140, + "working_set_bytes": 280158208, + "peak_working_set_bytes": 281071616 + }, + { + "query": "Pi extension factory defineExtension agent_end session_shutdown kimetsu.ts", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBWBQK6S520BBWHP1EHF", + "id": "01M1X6GGFHDE82W1470TQHWVA1", + "kind": "memory", + "score": 0.9990354776382446, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1113.4763, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 804, + "mcp_result_bytes": 893, + "wire_bytes": 929, + "reported_used_tokens": 893, + "working_set_bytes": 280326144, + "peak_working_set_bytes": 281239552 + }, + { + "query": "how does Pi (earendil-works/pi) load plugins and what lifecycle hooks does it expose?", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBWBQK6S520BBWHP1EHF", + "id": "01M1X6GHMEX0WDHBJYKZWWHBDG", + "kind": "memory", + "score": 0.9934834837913512, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1071.9474, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 803, + "mcp_result_bytes": 892, + "wire_bytes": 928, + "reported_used_tokens": 892, + "working_set_bytes": 280891392, + "peak_working_set_bytes": 281800704 + }, + { + "query": "aws-sigv4 SigningParams apply_to_request_http1x reqwest sign-http", + "ranked": [ + "aws-sigv4-bedrock-blocking", + "aws-presigned-urls", + "bedrock-kimetsu-provider", + "aws-credentials-chain" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBYZ5JT46Z87176E5AER", + "id": "01M1X6GJMFQER8GQC6M0J71NQH", + "kind": "memory", + "score": 0.9995608925819396, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1X6G25DB3HRGVQYHSTK90X7", + "id": "01M1X6GJMF26JM262MXET2Q0TB", + "kind": "memory", + "score": 0.984916627407074, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + }, + { + "expansion_handle": "memory:01M1X6FBSFGZG953DWGDC5CQJC", + "id": "01M1X6GJMFHS3VA39V00AXNQ92", + "kind": "memory", + "score": 0.983895778656006, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X6G225TRC4EVAFGXT9RHRX", + "id": "01M1X6GJMFY3KRJ14NHX8Y09NF", + "kind": "memory", + "score": 0.8592692017555237, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 759.3734, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3507, + "mcp_result_bytes": 3670, + "wire_bytes": 3706, + "reported_used_tokens": 3670, + "working_set_bytes": 280977408, + "peak_working_set_bytes": 281878528 + }, + { + "query": "how do I sign a Bedrock InvokeModel request with aws-sigv4 in blocking Rust?", + "ranked": [ + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider", + "aws-region-resolution", + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBYZ5JT46Z87176E5AER", + "id": "01M1X6GKC3ANKVW4TM679NFEBZ", + "kind": "memory", + "score": 0.9998323917388916, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1X6FBSFGZG953DWGDC5CQJC", + "id": "01M1X6GKC3JDBDGVVM0EMDRMTK", + "kind": "memory", + "score": 0.9970844388008118, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X6G23CEN8Y7G8DV4SNBYQB", + "id": "01M1X6GKC34VZT92HZ7QZ4WE7Z", + "kind": "memory", + "score": 0.9468621611595154, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X6G25DB3HRGVQYHSTK90X7", + "id": "01M1X6GKC34E3915P40FRVTMF0", + "kind": "memory", + "score": 0.9210098385810852, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 883.6748, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3434, + "mcp_result_bytes": 3597, + "wire_bytes": 3633, + "reported_used_tokens": 3597, + "working_set_bytes": 281382912, + "peak_working_set_bytes": 282300416 + }, + { + "query": "KIMETSU_RUNS_GC env opt-out TraceWriter create gc_old_runs caller", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC0TBZ3RC6PJTY71XESF", + "id": "01M1X6GM7DHM43VG3HCEA15089", + "kind": "memory", + "score": 0.999936580657959, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 840.2226, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 761, + "mcp_result_bytes": 842, + "wire_bytes": 878, + "reported_used_tokens": 842, + "working_set_bytes": 281440256, + "peak_working_set_bytes": 282353664 + }, + { + "query": "where should I put the KIMETSU_RUNS_GC=0 guard \u2014 inside the GC function or at the call site?", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC0TBZ3RC6PJTY71XESF", + "id": "01M1X6GN1XYCW7NMZXDB2X5SQ6", + "kind": "memory", + "score": 0.9971211552619934, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 930.0327000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 762, + "mcp_result_bytes": 843, + "wire_bytes": 879, + "reported_used_tokens": 843, + "working_set_bytes": 281997312, + "peak_working_set_bytes": 282918912 + }, + { + "query": "git_init_boundary ProjectPaths::discover temp dir user brain isolation", + "ranked": [ + "init-project-git-boundary", + "git-worktree-brain-isolation", + "testing-temp-dirs-ci", + "kimetsu-memory-scopes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC1PYTV8EEQVV2FCY4FV", + "id": "01M1X6GNYHH5SW7K5GB679CXPW", + "kind": "memory", + "score": 0.9997712969779968, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + }, + { + "expansion_handle": "memory:01M1X6FSA7MZHYF9BQ9B8J0E8G", + "id": "01M1X6GNYHYWNAJ781GYHWY48J", + "kind": "memory", + "score": 0.9962491393089294, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root \u2014 if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + }, + { + "expansion_handle": "memory:01M1X6FXWMDSNDYDFJ8T7ES5D5", + "id": "01M1X6GNYHT0FFP2NRYF78M81R", + "kind": "memory", + "score": 0.9682154655456544, + "summary": "project:fact - [tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure." + }, + { + "expansion_handle": "memory:01M1X6G596BN9AAJMY7GJTXTHF", + "id": "01M1X6GNYHFRP23A0NGN4P6QMV", + "kind": "memory", + "score": 0.3057229816913605, + "summary": "project:fact - [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available \u2014 if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 784.2253000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2580, + "mcp_result_bytes": 2715, + "wire_bytes": 2751, + "reported_used_tokens": 2715, + "working_set_bytes": 282034176, + "peak_working_set_bytes": 282947584 + }, + { + "query": "my test calls init_project but it writes to the real ~/.kimetsu instead of the temp folder \u2014 why?", + "ranked": [ + "init-project-git-boundary", + "cargo-feature-unification-embeddings", + "testing-fixture-drift", + "tokio-runtime-in-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC1PYTV8EEQVV2FCY4FV", + "id": "01M1X6GPQ42YVCEKA03DWXDCHA", + "kind": "memory", + "score": 0.9995088577270508, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + }, + { + "expansion_handle": "memory:01M1X6FBR4K1EDGA6RZKFR1659", + "id": "01M1X6GPQ4BMNHP31MS9EP7PRK", + "kind": "memory", + "score": 0.7287850975990295, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X6G1V5C0B2FRN6AHK9JQJW", + "id": "01M1X6GPQ5Q5WMPT9Q6HGB1TS9", + "kind": "memory", + "score": 0.6596062183380127, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + }, + { + "expansion_handle": "memory:01M1X6FSH3ZJYWBSJKZKZYY4Z4", + "id": "01M1X6GPQ4ED1432NCG867HTPJ", + "kind": "memory", + "score": 0.3297702968120575, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 933.5029999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2833, + "mcp_result_bytes": 2980, + "wire_bytes": 3016, + "reported_used_tokens": 2980, + "working_set_bytes": 282288128, + "peak_working_set_bytes": 283201536 + }, + { + "query": "clap command version KIMETSU_VERSION_DISPLAY cfg feature embeddings", + "ranked": [ + "clap-version-build-flavor", + "cargo-feature-unification-embeddings" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC2JTJ2W47SX7Q8Z01AV", + "id": "01M1X6GQN2GQK76N77G7T6KE2H", + "kind": "memory", + "score": 0.9996613264083862, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + }, + { + "expansion_handle": "memory:01M1X6FBR4K1EDGA6RZKFR1659", + "id": "01M1X6GQN2PPQCZ3H4YART30XC", + "kind": "memory", + "score": 0.3973360061645508, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 741.758, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1922, + "mcp_result_bytes": 2041, + "wire_bytes": 2077, + "reported_used_tokens": 2041, + "working_set_bytes": 282505216, + "peak_working_set_bytes": 283410432 + }, + { + "query": "how do I show the build flavor (lean vs embeddings) in the kimetsu --version output?", + "ranked": [ + "clap-version-build-flavor", + "cargo-feature-unification-embeddings", + "onnx-quantization-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC2JTJ2W47SX7Q8Z01AV", + "id": "01M1X6GRBV1V2S65H7SDY8V6B8", + "kind": "memory", + "score": 0.9978312849998474, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + }, + { + "expansion_handle": "memory:01M1X6FBR4K1EDGA6RZKFR1659", + "id": "01M1X6GRBVRV6MM3SPFX3HDAA7", + "kind": "memory", + "score": 0.8926984667778015, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X6FFZW8Z92NY5W7JM4TSR9", + "id": "01M1X6GRBVWZXX9F4W6F5ZXCN3", + "kind": "memory", + "score": 0.8877003192901611, + "summary": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals \u2014 cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 945.3303, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2672, + "mcp_result_bytes": 2809, + "wire_bytes": 2845, + "reported_used_tokens": 2809, + "working_set_bytes": 282533888, + "peak_working_set_bytes": 283451392 + }, + { + "query": "Harbor pyiceberg os.getcwd stale WSL2 DrvFs worker-result subprocess re-exec", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC3TT3SRMESXFHKJXD7R", + "id": "01M1X6GS939YQF5CJ90F7YADFF", + "kind": "memory", + "score": 0.9998155236244202, + "summary": "project:fact - [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 931.061, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1026, + "mcp_result_bytes": 1107, + "wire_bytes": 1143, + "reported_used_tokens": 1107, + "working_set_bytes": 282628096, + "peak_working_set_bytes": 283537408 + }, + { + "query": "why does my kbench sweep crash after the first trial with 'result.json missing' on WSL2?", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC3TT3SRMESXFHKJXD7R", + "id": "01M1X6GT68KSJFRSGYWNEP0RK6", + "kind": "memory", + "score": 0.998451828956604, + "summary": "project:fact - [2026-09-07] [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 944.5233, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1038, + "mcp_result_bytes": 1119, + "wire_bytes": 1155, + "reported_used_tokens": 1119, + "working_set_bytes": 282644480, + "peak_working_set_bytes": 283566080 + }, + { + "query": "rusqlite VACUUM transaction WAL checkpoint wal_checkpoint TRUNCATE", + "ranked": [ + "sqlite-vacuum-wal-checkpoint", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC54FN6W8DZFC2Z4V0V4", + "id": "01M1X6GV3RMQK6ZK8AEPBM486G", + "kind": "memory", + "score": 0.9996871948242188, + "summary": "project:fact - [tags: rust sqlite vacuum rusqlite windows] When implementing SQLite VACUUM in rusqlite: VACUUM cannot run inside a transaction. rusqlite's Connection does not hold an implicit transaction, so `conn.execute_batch(\"VACUUM;\")` works directly. After VACUUM, run `PRAGMA wal_checkpoint(TRUNCATE);` before measuring file size \u2014 on Windows the WAL file can hold significant space that isn't reflected in the main db file until the checkpoint runs." + }, + { + "expansion_handle": "memory:01M1X6FCB5829A6ZF2C5FB9VZW", + "id": "01M1X6GV3RW3TAP86M1Q639T4Y", + "kind": "memory", + "score": 0.5274003744125366, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 723.9436000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1507, + "mcp_result_bytes": 1610, + "wire_bytes": 1646, + "reported_used_tokens": 1610, + "working_set_bytes": 282648576, + "peak_working_set_bytes": 283566080 + }, + { + "query": "my SQLite VACUUM reports the file shrank but the disk usage stayed the same \u2014 Windows WAL?", + "ranked": [ + "sqlite-vacuum-wal-checkpoint", + "sqlite-wal-network-drive" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC54FN6W8DZFC2Z4V0V4", + "id": "01M1X6GVTKT26CCH3SKKT94C7R", + "kind": "memory", + "score": 0.9155893921852112, + "summary": "project:fact - [tags: rust sqlite vacuum rusqlite windows] When implementing SQLite VACUUM in rusqlite: VACUUM cannot run inside a transaction. rusqlite's Connection does not hold an implicit transaction, so `conn.execute_batch(\"VACUUM;\")` works directly. After VACUUM, run `PRAGMA wal_checkpoint(TRUNCATE);` before measuring file size \u2014 on Windows the WAL file can hold significant space that isn't reflected in the main db file until the checkpoint runs." + }, + { + "expansion_handle": "memory:01M1X6FCDYEQS880XHFGF7HHKN", + "id": "01M1X6GVTK26A8V6FM1XJ8T317", + "kind": "memory", + "score": 0.902395486831665, + "summary": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 985.3858, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1357, + "mcp_result_bytes": 1460, + "wire_bytes": 1496, + "reported_used_tokens": 1460, + "working_set_bytes": 282660864, + "peak_working_set_bytes": 283578368 + }, + { + "query": "add_memory import dedup seen_ids snapshot pre-existing active memory IDs", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC5XF447XNVWDTA1KX30", + "id": "01M1X6GWSAXJCVQKX0AVYRHDAW", + "kind": "memory", + "score": 0.9999133348464966, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount \u2014 both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 820.4101, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 966, + "mcp_result_bytes": 1047, + "wire_bytes": 1083, + "reported_used_tokens": 1047, + "working_set_bytes": 282714112, + "peak_working_set_bytes": 283623424 + }, + { + "query": "brain import re-imports the same JSON file but the deduplication counter is wrong \u2014 why?", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC5XF447XNVWDTA1KX30", + "id": "01M1X6GXJWPBMSAPSEFY7380FC", + "kind": "memory", + "score": 0.9254016876220704, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount \u2014 both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 860.1193999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 965, + "mcp_result_bytes": 1046, + "wire_bytes": 1082, + "reported_used_tokens": 1046, + "working_set_bytes": 282865664, + "peak_working_set_bytes": 283783168 + }, + { + "query": "toml::from_str Value parse document unexpected content str.parse", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC706HRRZF8YZH8DDCVM", + "id": "01M1X6GYDSRWXNGN2CJ8WD1P8M", + "kind": "memory", + "score": 0.9991866946220398, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 749.0263, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 734, + "mcp_result_bytes": 815, + "wire_bytes": 851, + "reported_used_tokens": 815, + "working_set_bytes": 282951680, + "peak_working_set_bytes": 283865088 + }, + { + "query": "how do I parse a TOML configuration file into a toml::Value in toml 0.9?", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC706HRRZF8YZH8DDCVM", + "id": "01M1X6GZ5ARC8XFTH8HAWNHVY0", + "kind": "memory", + "score": 0.9992641806602478, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 886.9189, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 733, + "mcp_result_bytes": 814, + "wire_bytes": 850, + "reported_used_tokens": 814, + "working_set_bytes": 282959872, + "peak_working_set_bytes": 283873280 + }, + { + "query": "CIM CreationDate DMTF WMI ps etimes started_at assess_mcp_skew", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC7XP8VRJ8NHE6MZGS3H", + "id": "01M1X6H015QVVFHBY1GF76Z2GY", + "kind": "memory", + "score": 0.9957948923110962, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 695.6131, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 924, + "mcp_result_bytes": 1013, + "wire_bytes": 1049, + "reported_used_tokens": 1013, + "working_set_bytes": 283045888, + "peak_working_set_bytes": 283951104 + }, + { + "query": "how do I read a process start time on both Windows and Linux in pure Rust?", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC7XP8VRJ8NHE6MZGS3H", + "id": "01M1X6H0PWEF03DSGGDX6EGJJS", + "kind": "memory", + "score": 0.99687659740448, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 901.9759, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 921, + "mcp_result_bytes": 1010, + "wire_bytes": 1046, + "reported_used_tokens": 1010, + "working_set_bytes": 283074560, + "peak_working_set_bytes": 284000256 + }, + { + "query": "processes_locking_target decide_preflight_action BufRead Write update.rs", + "ranked": [ + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC93AQB8S92QXHHFQGQ9", + "id": "01M1X6H1JZ0MCP8YH21MP1A33N", + "kind": "memory", + "score": 0.9995336532592772, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 755.7758, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1133, + "mcp_result_bytes": 1214, + "wire_bytes": 1250, + "reported_used_tokens": 1214, + "working_set_bytes": 283107328, + "peak_working_set_bytes": 284024832 + }, + { + "query": "how should I reuse the existing process enumerator in the update preflight check to avoid a second PowerShell query?", + "ranked": [ + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC93AQB8S92QXHHFQGQ9", + "id": "01M1X6H2APTS5AQZDCK7MQMTKQ", + "kind": "memory", + "score": 0.9973384737968444, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 946.4978, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1132, + "mcp_result_bytes": 1213, + "wire_bytes": 1249, + "reported_used_tokens": 1213, + "working_set_bytes": 283414528, + "peak_working_set_bytes": 284332032 + }, + { + "query": "cfg_attr windows allow dead_code parse_unix_ps cross-platform tests", + "ranked": [ + "cfg-cross-platform-dead-code", + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCA9873DBA4C4PTQZN4Q", + "id": "01M1X6H38BVNBPKPBDJEFXVC3J", + "kind": "memory", + "score": 0.9999476671218872, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + }, + { + "expansion_handle": "memory:01M1X6FC7XP8VRJ8NHE6MZGS3H", + "id": "01M1X6H38BD5RGM1PK0M04RNPT", + "kind": "memory", + "score": 0.9764312505722046, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 739.3448000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1518, + "mcp_result_bytes": 1625, + "wire_bytes": 1661, + "reported_used_tokens": 1625, + "working_set_bytes": 283713536, + "peak_working_set_bytes": 284626944 + }, + { + "query": "how do I keep a function that is only called on Unix from triggering dead_code warnings on Windows?", + "ranked": [ + "cfg-cross-platform-dead-code" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCA9873DBA4C4PTQZN4Q", + "id": "01M1X6H3ZV9S13TCNS0KJ4DJT8", + "kind": "memory", + "score": 0.9988092184066772, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 896.6256999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 939, + "reported_used_tokens": 903, + "working_set_bytes": 283844608, + "peak_working_set_bytes": 284758016 + }, + { + "query": "deadlocking a Rust mutex in integration tests", + "ranked": [ + "mutex-deadlock-user-brain-disabled", + "testing-serial-vs-parallel", + "kimetsu-query-stemming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBK795VCKKWK7JEKPT4J", + "id": "01M1X6H4V99SV6VXYMXNYHXKH3", + "kind": "memory", + "score": 0.9997490048408508, + "summary": "project:fact - [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure \u2014 `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + }, + { + "expansion_handle": "memory:01M1X6FXZM3KNGPDANSTPNKTQR", + "id": "01M1X6H4VA0M70KPARD3EQKYQ5", + "kind": "memory", + "score": 0.9057517647743224, + "summary": "project:fact - [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`)." + }, + { + "expansion_handle": "memory:01M1X6G5KQ8DP47BPMHQSWGBGS", + "id": "01M1X6H4VA0AYRHCAVJXJ15R85", + "kind": "memory", + "score": 0.4889622032642365, + "summary": "project:fact - [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 842.9918, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1930, + "mcp_result_bytes": 2063, + "wire_bytes": 2099, + "reported_used_tokens": 2063, + "working_set_bytes": 283959296, + "peak_working_set_bytes": 284872704 + }, + { + "query": "benchmarking retrieval quality across embedders", + "ranked": [ + "kimetsu-bench-remote-embedder-singleton", + "onnx-quantization-drift", + "cargo-feature-unification-embeddings" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G5NP21NSKR0TBJPHB80K", + "id": "01M1X6H5NNY518CEM55NHAMSXB", + "kind": "memory", + "score": 0.988014280796051, + "summary": "project:fact - [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval." + }, + { + "expansion_handle": "memory:01M1X6FFZW8Z92NY5W7JM4TSR9", + "id": "01M1X6H5NNV5V9ZD9RJZHTD95B", + "kind": "memory", + "score": 0.985597550868988, + "summary": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals \u2014 cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + }, + { + "expansion_handle": "memory:01M1X6FBR4K1EDGA6RZKFR1659", + "id": "01M1X6H5NN1SDQ4B8NN25Q9587", + "kind": "memory", + "score": 0.5341982841491699, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 714.5283000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2537, + "mcp_result_bytes": 2658, + "wire_bytes": 2694, + "reported_used_tokens": 2658, + "working_set_bytes": 284327936, + "peak_working_set_bytes": 285237248 + }, + { + "query": "process memory working set RSS peak measurement Windows", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 913.0407, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 284942336, + "peak_working_set_bytes": 285835264 + }, + { + "query": "cloning a git repository server-side into a managed checkout", + "ranked": [ + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBMAHT4JHT1S9T1YAQCN", + "id": "01M1X6H78VAZZNS7W7ZPYW7SG4", + "kind": "memory", + "score": 0.9466677904129028, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 754.2299999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1261, + "mcp_result_bytes": 1342, + "wire_bytes": 1378, + "reported_used_tokens": 1342, + "working_set_bytes": 285155328, + "peak_working_set_bytes": 286060544 + }, + { + "query": "SigV4 signing HTTP requests in Rust", + "ranked": [ + "aws-presigned-urls", + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G25DB3HRGVQYHSTK90X7", + "id": "01M1X6H804WRQ6AB1Z7C4R3MWG", + "kind": "memory", + "score": 0.9992632269859314, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + }, + { + "expansion_handle": "memory:01M1X6FBYZ5JT46Z87176E5AER", + "id": "01M1X6H80476X9H746D7FNQJMJ", + "kind": "memory", + "score": 0.9991399049758912, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1X6FBSFGZG953DWGDC5CQJC", + "id": "01M1X6H804HPHAYJ84PAZDTV4M", + "kind": "memory", + "score": 0.9803794622421264, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 0.5, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 831.2456000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2840, + "mcp_result_bytes": 2985, + "wire_bytes": 3021, + "reported_used_tokens": 2985, + "working_set_bytes": 285597696, + "peak_working_set_bytes": 286494720 + }, + { + "query": "cargo test --workspace feature flag changes broke my unit tests", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-dev-dep-leak", + "ci-flaky-quarantine" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBR4K1EDGA6RZKFR1659", + "id": "01M1X6H8T7GP5NBPPWY50TBV7D", + "kind": "memory", + "score": 0.997899889945984, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X6FCQ2KKER2KYG7B2280XC", + "id": "01M1X6H8T7W48TZ7F3JH4C4241", + "kind": "memory", + "score": 0.9901249408721924, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + }, + { + "expansion_handle": "memory:01M1X6G569HH9Q1FNPCPH6GQV2", + "id": "01M1X6H8T772FJZS77V0AT51A8", + "kind": "memory", + "score": 0.835382342338562, + "summary": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal \u2014 a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 745.3519, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2383, + "mcp_result_bytes": 2504, + "wire_bytes": 2540, + "reported_used_tokens": 2504, + "working_set_bytes": 285638656, + "peak_working_set_bytes": 286552064 + }, + { + "query": "how do I make pasta carbonara?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 785.6972, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 285655040, + "peak_working_set_bytes": 286568448 + }, + { + "query": "what is the offside rule in football?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 980.4788, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 285659136, + "peak_working_set_bytes": 286568448 + }, + { + "query": "best way to train for a half marathon", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 993.1735, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 285663232, + "peak_working_set_bytes": 286584832 + }, + { + "query": "my test passes when I run it alone but fails under cargo test --workspace", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBR4K1EDGA6RZKFR1659", + "id": "01M1X6HC7T7YE5M5KC8S6KBCYT", + "kind": "memory", + "score": 0.9907942414283752, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X6FCQ2KKER2KYG7B2280XC", + "id": "01M1X6HC7TRDATY8ZNVNHXEP3Z", + "kind": "memory", + "score": 0.986136794090271, + "summary": "project:fact - [2026-09-07] [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 915.4825, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1863, + "mcp_result_bytes": 1966, + "wire_bytes": 2002, + "reported_used_tokens": 1966, + "working_set_bytes": 286117888, + "peak_working_set_bytes": 287035392 + }, + { + "query": "all the project tests started hanging forever after I added my new test", + "ranked": [ + "cargo-feature-unification-embeddings", + "tokio-runtime-in-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBR4K1EDGA6RZKFR1659", + "id": "01M1X6HD4FGZBY2SJ7F73QJQMF", + "kind": "memory", + "score": 0.774284839630127, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X6FSH3ZJYWBSJKZKZYY4Z4", + "id": "01M1X6HD4FQCJHNCZRCVC3MCTH", + "kind": "memory", + "score": 0.33030807971954346, + "summary": "project:fact - [2026-09-07] [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 866.3612, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1763, + "mcp_result_bytes": 1874, + "wire_bytes": 1910, + "reported_used_tokens": 1874, + "working_set_bytes": 286273536, + "peak_working_set_bytes": 287182848 + }, + { + "query": "my integration test silently wrote memories into my real home brain instead of the temp workspace", + "ranked": [ + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC1PYTV8EEQVV2FCY4FV", + "id": "01M1X6HDZJFCD8J0BX1X61SH8X", + "kind": "memory", + "score": 0.9922831654548644, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 868.7071, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 780, + "mcp_result_bytes": 861, + "wire_bytes": 897, + "reported_used_tokens": 861, + "working_set_bytes": 286330880, + "peak_working_set_bytes": 287248384 + }, + { + "query": "where should the env-var opt-out check live for a cleanup feature triggered from a hot code path", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC0TBZ3RC6PJTY71XESF", + "id": "01M1X6HETSN1YHJCHKNESYX2FP", + "kind": "memory", + "score": 0.9952055215835572, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 931.7883999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 761, + "mcp_result_bytes": 842, + "wire_bytes": 878, + "reported_used_tokens": 842, + "working_set_bytes": 286371840, + "peak_working_set_bytes": 287289344 + }, + { + "query": "the brain database file stays huge on Windows even after deleting most rows", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 975.6314, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 286408704, + "peak_working_set_bytes": 287330304 + }, + { + "query": "re-importing the same exported memories file counts them as new instead of deduplicated", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC5XF447XNVWDTA1KX30", + "id": "01M1X6HGPSD7GEP0672904MTA4", + "kind": "memory", + "score": 0.9878425598144532, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount \u2014 both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 914.585, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 965, + "mcp_result_bytes": 1046, + "wire_bytes": 1082, + "reported_used_tokens": 1046, + "working_set_bytes": 286429184, + "peak_working_set_bytes": 287346688 + }, + { + "query": "a helper function only called on Unix at runtime fails the dead-code lint on the Windows build", + "ranked": [ + "cfg-cross-platform-dead-code", + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCA9873DBA4C4PTQZN4Q", + "id": "01M1X6HHK5YM472SMNNVDP00XY", + "kind": "memory", + "score": 0.9971064925193788, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + }, + { + "expansion_handle": "memory:01M1X6FC93AQB8S92QXHHFQGQ9", + "id": "01M1X6HHK56GCNCKN9Q5D9P8EB", + "kind": "memory", + "score": 0.427912950515747, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 882.0010000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1728, + "mcp_result_bytes": 1827, + "wire_bytes": 1863, + "reported_used_tokens": 1827, + "working_set_bytes": 286470144, + "peak_working_set_bytes": 287391744 + }, + { + "query": "the second Terminal-Bench trial always crashes even though the first one passes", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC3TT3SRMESXFHKJXD7R", + "id": "01M1X6HJER5XAK1037FRV7YF3P", + "kind": "memory", + "score": 0.9963042736053468, + "summary": "project:fact - [2026-09-07] [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 920.6225000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1038, + "mcp_result_bytes": 1119, + "wire_bytes": 1155, + "reported_used_tokens": 1119, + "working_set_bytes": 286498816, + "peak_working_set_bytes": 287412224 + }, + { + "query": "how does doctor tell a running MCP server process is older than the kimetsu binary on disk", + "ranked": [ + "kimetsu-daemon-lifecycle", + "process-start-time-cross-platform", + "mcp-env-propagation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G57BG9P4RZW0F69BHBK0", + "id": "01M1X6HKC6SCB6HE44J2SSM71Z", + "kind": "memory", + "score": 0.9985345602035522, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1X6FC7XP8VRJ8NHE6MZGS3H", + "id": "01M1X6HKC6PWVZN6PDXK0V0KKC", + "kind": "memory", + "score": 0.9438157677650452, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + }, + { + "expansion_handle": "memory:01M1X6G1Y3K75Y1EDB0JHXK3KF", + "id": "01M1X6HKC6Y8BWVN3HMBA8H1VV", + "kind": "memory", + "score": 0.33611738681793213, + "summary": "project:fact - [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment \u2014 changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 0.5, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 942.8453999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1936, + "mcp_result_bytes": 2061, + "wire_bytes": 2097, + "reported_used_tokens": 2061, + "working_set_bytes": 286527488, + "peak_working_set_bytes": 287444992 + }, + { + "query": "the self-update preflight needs the list of running kimetsu processes without re-running the OS query", + "ranked": [ + "windows-update-process-locking", + "kimetsu-daemon-lifecycle" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC93AQB8S92QXHHFQGQ9", + "id": "01M1X6HM8XRCT09CQQVTQM6SCJ", + "kind": "memory", + "score": 0.9972410202026368, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + }, + { + "expansion_handle": "memory:01M1X6G57BG9P4RZW0F69BHBK0", + "id": "01M1X6HM8XVMDGWVDB0HFMH7PV", + "kind": "memory", + "score": 0.8902595043182373, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 877.5347, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1658, + "mcp_result_bytes": 1757, + "wire_bytes": 1793, + "reported_used_tokens": 1757, + "working_set_bytes": 286531584, + "peak_working_set_bytes": 287444992 + }, + { + "query": "parsing the WMI DMTF CreationDate timestamp into epoch seconds without extra crates", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC7XP8VRJ8NHE6MZGS3H", + "id": "01M1X6HN4AE4RFYJ1XG878AB0F", + "kind": "memory", + "score": 0.9258026480674744, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 931.8521, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 924, + "mcp_result_bytes": 1013, + "wire_bytes": 1049, + "reported_used_tokens": 1013, + "working_set_bytes": 286564352, + "peak_working_set_bytes": 287477760 + }, + { + "query": "calling Bedrock InvokeModel from blocking reqwest without the aws sdk", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking", + "aws-region-resolution", + "aws-retry-throttling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBSFGZG953DWGDC5CQJC", + "id": "01M1X6HP1G7PE64KKD8BAA1EWV", + "kind": "memory", + "score": 0.9991798996925354, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X6FBYZ5JT46Z87176E5AER", + "id": "01M1X6HP1GFPF1X4BP9B3BHM9X", + "kind": "memory", + "score": 0.999082326889038, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1X6G23CEN8Y7G8DV4SNBYQB", + "id": "01M1X6HP1GTPB9SNR05J5Z7058", + "kind": "memory", + "score": 0.8391201496124268, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X6G24BKQF5H1P4JPDZJ0GV", + "id": "01M1X6HP1GBK6P69RXER575KAQ", + "kind": "memory", + "score": 0.4906356632709503, + "summary": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with \u00b125% jitter." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 899.4729, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3330, + "mcp_result_bytes": 3509, + "wire_bytes": 3545, + "reported_used_tokens": 3509, + "working_set_bytes": 286646272, + "peak_working_set_bytes": 287563776 + }, + { + "query": "how do I rotate the encryption key protecting the kimetsu brain database", + "ranked": [ + "kimetsu-eval-fixture-shape" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G5PR2N167V5D1K95D9WP", + "id": "01M1X6HPY54XNEG505QWNTAXGS", + "kind": "memory", + "score": 0.8046634197235107, + "summary": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` \u2014 a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases)." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 930.4038, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 817, + "mcp_result_bytes": 942, + "wire_bytes": 978, + "reported_used_tokens": 942, + "working_set_bytes": 286703616, + "peak_working_set_bytes": 287612928 + }, + { + "query": "which tokio runtime worker-thread settings does the kimetsu MCP server use", + "ranked": [ + "tokio-blocking-in-async", + "tokio-runtime-in-tests", + "mcp-stdout-protocol" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FSG1SR2HKPVJWCKTW0XB", + "id": "01M1X6HQTQ58XRTRCAH5A1VZXN", + "kind": "memory", + "score": 0.9973159432411194, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + }, + { + "expansion_handle": "memory:01M1X6FSH3ZJYWBSJKZKZYY4Z4", + "id": "01M1X6HQTQSKJ9JYRW9KD8MRPQ", + "kind": "memory", + "score": 0.8583173155784607, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + }, + { + "expansion_handle": "memory:01M1X6G1W0ZBD86TBRPQESXKTX", + "id": "01M1X6HQTQ2E9YAPEFKN312MG3", + "kind": "memory", + "score": 0.8141786456108093, + "summary": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 926.1415, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1847, + "mcp_result_bytes": 1972, + "wire_bytes": 2008, + "reported_used_tokens": 1972, + "working_set_bytes": 286994432, + "peak_working_set_bytes": 287907840 + }, + { + "query": "how does kimetsu sync memories between two machines over the network", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 862.3269, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 287379456, + "peak_working_set_bytes": 288292864 + }, + { + "query": "recovering a corrupted usearch ANN index after a power loss", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 820.8115, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 287379456, + "peak_working_set_bytes": 288292864 + }, + { + "query": "what postgres schema should I use to store kimetsu memories", + "ranked": [ + "kimetsu-memory-scopes", + "testing-fixture-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G596BN9AAJMY7GJTXTHF", + "id": "01M1X6HTCF8BTHDDFVA47CJ94A", + "kind": "memory", + "score": 0.9890244603157043, + "summary": "project:fact - [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available \u2014 if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope." + }, + { + "expansion_handle": "memory:01M1X6G1V5C0B2FRN6AHK9JQJW", + "id": "01M1X6HTCF1KYZHVTQSD7M9DPT", + "kind": "memory", + "score": 0.8922504782676697, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 906.2177, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1389, + "mcp_result_bytes": 1488, + "wire_bytes": 1524, + "reported_used_tokens": 1488, + "working_set_bytes": 287420416, + "peak_working_set_bytes": 288321536 + }, + { + "query": "the whole CI job just froze forever with no failure output after my latest test PR", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 918.714, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 287436800, + "peak_working_set_bytes": 288354304 + }, + { + "query": "running the test suite left junk state in my home directory", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 919.5084999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 287444992, + "peak_working_set_bytes": 288362496 + }, + { + "query": "I deleted a bunch of old rows but the file on disk is still the same size", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 885.6174, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 287547392, + "peak_working_set_bytes": 288464896 + }, + { + "query": "adding one new crate quietly changed how the whole workspace builds", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-lockfile-drift", + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBR4K1EDGA6RZKFR1659", + "id": "01M1X6HXXSKYJGW8YM7F0G8H2Q", + "kind": "memory", + "score": 0.9941080808639526, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X6FCN11BZ03K13K0EZ96KQ", + "id": "01M1X6HXXSWZ2A955VE052ZYG6", + "kind": "memory", + "score": 0.9717232584953308, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this \u2014 it errors on any lockfile diff." + }, + { + "expansion_handle": "memory:01M1X6FCQ2KKER2KYG7B2280XC", + "id": "01M1X6HXXSVAK689FBGG1HNQQ9", + "kind": "memory", + "score": 0.9183088541030884, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 914.9649, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2374, + "mcp_result_bytes": 2495, + "wire_bytes": 2531, + "reported_used_tokens": 2495, + "working_set_bytes": 287559680, + "peak_working_set_bytes": 288481280 + }, + { + "query": "we cannot pull an async runtime into the agent just to talk to AWS", + "ranked": [ + "tokio-blocking-in-async", + "tokio-runtime-in-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FSG1SR2HKPVJWCKTW0XB", + "id": "01M1X6HYTGQZ1X9E4X277WTF6D", + "kind": "memory", + "score": 0.7520647644996643, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + }, + { + "expansion_handle": "memory:01M1X6FSH3ZJYWBSJKZKZYY4Z4", + "id": "01M1X6HYTG3VR7EJM29VKJDQRA", + "kind": "memory", + "score": 0.7233642935752869, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 929.2574999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1369, + "mcp_result_bytes": 1476, + "wire_bytes": 1512, + "reported_used_tokens": 1476, + "working_set_bytes": 287580160, + "peak_working_set_bytes": 288489472 + }, + { + "query": "users should be able to tell which build variant they installed from the version output", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 958.9667, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 287916032, + "peak_working_set_bytes": 288833536 + }, + { + "query": "what gotchas should I expect writing process-inspection code that works on both Windows and Unix?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 865.5604999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 287985664, + "peak_working_set_bytes": 288907264 + }, + { + "query": "why might tests behave differently on my machine than in the full CI run?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 875.4398, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288100352, + "peak_working_set_bytes": 289017856 + }, + { + "query": "what do I need to know before wiring kimetsu into a brand new host agent?", + "ranked": [ + "bridge-target-enum-seams", + "kimetsu-daemon-lifecycle", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBV74YJ9W6XP288GKM7H", + "id": "01M1X6J2C5NS3C5EANNAQ44AAT", + "kind": "memory", + "score": 0.9741999506950378, + "summary": "project:fact - [2026-09-07] [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + }, + { + "expansion_handle": "memory:01M1X6G57BG9P4RZW0F69BHBK0", + "id": "01M1X6J2C5KMEW2QC3V7BE4F2E", + "kind": "memory", + "score": 0.9637662768363952, + "summary": "project:fact - [2026-09-07] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1X6FBP8RS75FNREKQZ0WD3Q", + "id": "01M1X6J2C5MGTSVNE22WSATRS9", + "kind": "memory", + "score": 0.4149944484233856, + "summary": "project:fact - [2026-09-07] [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 0.6666666666666666, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 934.7820999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2390, + "mcp_result_bytes": 2555, + "wire_bytes": 2591, + "reported_used_tokens": 2555, + "working_set_bytes": 288124928, + "peak_working_set_bytes": 289042432 + }, + { + "query": "tell me everything relevant to running kimetsu against AWS", + "ranked": [ + "kimetsu-mrr-metric", + "aws-credentials-chain", + "cargo-feature-unification-embeddings", + "kimetsu-eval-fixture-shape" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G5QTTFD19FAJ8MZX4ZPC", + "id": "01M1X6J3A2YJS39ZM3XDA74KK2", + "kind": "memory", + "score": 0.984548270702362, + "summary": "project:fact - [tags: kimetsu bench mrr recall metrics evaluation] kimetsu bench reports MRR (Mean Reciprocal Rank) and Recall@K. MRR is 1/rank_of_first_relevant_result, averaged across cases; it penalizes models that rank the correct answer 2nd or 3rd. Recall@K is the fraction of cases where at least one relevant answer appears in the top K." + }, + { + "expansion_handle": "memory:01M1X6G225TRC4EVAFGXT9RHRX", + "id": "01M1X6J3A27V7JB55BC2TCC126", + "kind": "memory", + "score": 0.9737622141838074, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + }, + { + "expansion_handle": "memory:01M1X6FBR4K1EDGA6RZKFR1659", + "id": "01M1X6J3A22EEJ3N0YNEHHNCC9", + "kind": "memory", + "score": 0.9726329445838928, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X6G5PR2N167V5D1K95D9WP", + "id": "01M1X6J3A26YA79ZCZNS854CRP", + "kind": "memory", + "score": 0.9641559720039368, + "summary": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` \u2014 a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases)." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 947.1441000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2883, + "mcp_result_bytes": 3066, + "wire_bytes": 3102, + "reported_used_tokens": 3066, + "working_set_bytes": 288325632, + "peak_working_set_bytes": 289234944 + }, + { + "query": "ingesting a cloned repo when the brain lives under a different root", + "ranked": [ + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBMAHT4JHT1S9T1YAQCN", + "id": "01M1X6J46V18VGX2C1N1QYCW76", + "kind": "memory", + "score": 0.9995300769805908, + "summary": "project:fact - [2026-09-07] [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 866.3001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1274, + "mcp_result_bytes": 1355, + "wire_bytes": 1391, + "reported_used_tokens": 1355, + "working_set_bytes": 288415744, + "peak_working_set_bytes": 289325056 + }, + { + "query": "streamable-http transport entry for openclaw.json with a bearer token", + "ranked": [ + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBP8RS75FNREKQZ0WD3Q", + "id": "01M1X6J5299E37P91S64JF8QFK", + "kind": "memory", + "score": 0.9921918511390686, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 904.1683, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 996, + "mcp_result_bytes": 1125, + "wire_bytes": 1161, + "reported_used_tokens": 1125, + "working_set_bytes": 288481280, + "peak_working_set_bytes": 289398784 + }, + { + "query": "serializing ingests with a tokio mutex to avoid checkout races", + "ranked": [ + "remote-ingest-split-roots", + "testing-serial-vs-parallel", + "tokio-select-cancellation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBMAHT4JHT1S9T1YAQCN", + "id": "01M1X6J5YDE3ZBJ4G8K5Q65FT6", + "kind": "memory", + "score": 0.9795480966567992, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1X6FXZM3KNGPDANSTPNKTQR", + "id": "01M1X6J5YDW14K5TJN3X4V4X6F", + "kind": "memory", + "score": 0.9425267577171326, + "summary": "project:fact - [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`)." + }, + { + "expansion_handle": "memory:01M1X6FSJ8K553R8HRDXZWVYCK", + "id": "01M1X6J5YDWPG04X183XDXPVPP", + "kind": "memory", + "score": 0.5619664192199707, + "summary": "project:fact - [tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 929.1194, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2376, + "mcp_result_bytes": 2493, + "wire_bytes": 2529, + "reported_used_tokens": 2493, + "working_set_bytes": 288518144, + "peak_working_set_bytes": 289439744 + }, + { + "query": "percent-encoding the colon in the bedrock model id for the invoke URL", + "ranked": [ + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBSFGZG953DWGDC5CQJC", + "id": "01M1X6J6VBQ34GZ0Q3A23EDNY5", + "kind": "memory", + "score": 0.8341025710105896, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 868.0025, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1204, + "mcp_result_bytes": 1293, + "wire_bytes": 1329, + "reported_used_tokens": 1293, + "working_set_bytes": 288518144, + "peak_working_set_bytes": 289439744 + }, + { + "query": "deduplicating re-imported memories against pre-existing ids", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC5XF447XNVWDTA1KX30", + "id": "01M1X6J7PNJ6T6GP32XYW836W1", + "kind": "memory", + "score": 0.9991393089294434, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount \u2014 both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 940.609, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 966, + "mcp_result_bytes": 1047, + "wire_bytes": 1083, + "reported_used_tokens": 1047, + "working_set_bytes": 288567296, + "peak_working_set_bytes": 289464320 + }, + { + "query": "parsing DMTF datetimes", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC7XP8VRJ8NHE6MZGS3H", + "id": "01M1X6J8KWZPQNB848PV2JP8V0", + "kind": "memory", + "score": 0.9934942126274108, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 689.5605, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 924, + "mcp_result_bytes": 1013, + "wire_bytes": 1049, + "reported_used_tokens": 1013, + "working_set_bytes": 288641024, + "peak_working_set_bytes": 289521664 + }, + { + "query": "how should install derive a stable identifier from the git remote URL?", + "ranked": [ + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBP8RS75FNREKQZ0WD3Q", + "id": "01M1X6J99S18EGF93ZNNMH4WXB", + "kind": "memory", + "score": 0.98285174369812, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 901.1454, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 995, + "mcp_result_bytes": 1124, + "wire_bytes": 1160, + "reported_used_tokens": 1124, + "working_set_bytes": 288641024, + "peak_working_set_bytes": 289554432 + }, + { + "query": "the secret token must not end up written into the host config file", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 944.4225, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288673792, + "peak_working_set_bytes": 289591296 + }, + { + "query": "keep the cleanup logic unit-testable without touching environment variables", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 937.7613, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288673792, + "peak_working_set_bytes": 289591296 + }, + { + "query": "how do we stop the server from cloning arbitrary repos clients request?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 876.9009, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288690176, + "peak_working_set_bytes": 289607680 + }, + { + "query": "make sure a wrong guess about a host plugin API never breaks that host", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBWBQK6S520BBWHP1EHF", + "id": "01M1X6JCVVWR9N9F3XPMPTE6KE", + "kind": "memory", + "score": 0.928434193134308, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 783.2337, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 803, + "mcp_result_bytes": 892, + "wire_bytes": 928, + "reported_used_tokens": 892, + "working_set_bytes": 288694272, + "peak_working_set_bytes": 289615872 + }, + { + "query": "which wire-format trick lets us reuse the existing Anthropic request builder for AWS?", + "ranked": [ + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBSFGZG953DWGDC5CQJC", + "id": "01M1X6JDMV5P4YTHYBN2ZCHXFK", + "kind": "memory", + "score": 0.9748817682266236, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 979.3254999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1203, + "mcp_result_bytes": 1292, + "wire_bytes": 1328, + "reported_used_tokens": 1292, + "working_set_bytes": 288808960, + "peak_working_set_bytes": 289726464 + }, + { + "query": "the self-update froze because something was still holding the executable", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 902.888, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288837632, + "peak_working_set_bytes": 289746944 + }, + { + "query": "our notes about the extension API turned out wrong once we read the actual repo", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 904.628, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288862208, + "peak_working_set_bytes": 289771520 + }, + { + "query": "half the benchmark trials die right after the first one finishes", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 863.1566, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288878592, + "peak_working_set_bytes": 289796096 + }, + { + "query": "I need this parser visible to tests on every OS even though only one OS calls it", + "ranked": [ + "cfg-cross-platform-dead-code" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCA9873DBA4C4PTQZN4Q", + "id": "01M1X6JH6XPK0JVG1WK82QXGGS", + "kind": "memory", + "score": 0.36490198969841, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 889.8193, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 939, + "reported_used_tokens": 903, + "working_set_bytes": 288927744, + "peak_working_set_bytes": 289832960 + }, + { + "query": "the config file content refuses to parse even though the TOML looks valid", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC706HRRZF8YZH8DDCVM", + "id": "01M1X6JJ2HYGNDG6G6AMP0QFF9", + "kind": "memory", + "score": 0.6614054441452026, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 934.3593999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 733, + "mcp_result_bytes": 814, + "wire_bytes": 850, + "reported_used_tokens": 814, + "working_set_bytes": 288964608, + "peak_working_set_bytes": 289882112 + }, + { + "query": "the remote server must refresh its checkout before answering file queries", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 948.8135, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288968704, + "peak_working_set_bytes": 289886208 + }, + { + "query": "tests must not climb to a parent git repository when resolving project paths", + "ranked": [ + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC1PYTV8EEQVV2FCY4FV", + "id": "01M1X6JKXAHQ1CERZ068GJRB0E", + "kind": "memory", + "score": 0.9839988350868224, + "summary": "project:fact - [2026-09-07] [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 922.1287, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 794, + "mcp_result_bytes": 875, + "wire_bytes": 911, + "reported_used_tokens": 875, + "working_set_bytes": 289009664, + "peak_working_set_bytes": 289918976 + }, + { + "query": "how do I test request signing deterministically when timestamps change every run?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 904.8479, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 289091584, + "peak_working_set_bytes": 290004992 + }, + { + "query": "adding a new variant to the host target enum - which places will I forget to update?", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBV74YJ9W6XP288GKM7H", + "id": "01M1X6JNPEYPJZRYCJJPQWRNPY", + "kind": "memory", + "score": 0.885076105594635, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 944.1534, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1058, + "mcp_result_bytes": 1139, + "wire_bytes": 1175, + "reported_used_tokens": 1139, + "working_set_bytes": 289091584, + "peak_working_set_bytes": 290004992 + }, + { + "query": "how do I enable GPU acceleration for kimetsu embedding inference", + "ranked": [ + "mcp-tool-timeouts", + "kimetsu-proactive-hooks" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G1X1XGE9MM05BANVE5DQ", + "id": "01M1X6JPKYC1HJS7MHVY7DBYXZ", + "kind": "memory", + "score": 0.9826309084892272, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + }, + { + "expansion_handle": "memory:01M1X6G5B7FXBAABGSVRFWFCX6", + "id": "01M1X6JPKYWKH4CB1T9JF4BXSJ", + "kind": "memory", + "score": 0.8807981610298157, + "summary": "project:fact - [tags: kimetsu proactive hooks context injection] kimetsu's proactive context injection runs before each agent turn (pre-turn hook) and injects relevant memories into the system prompt prefix. The hook invocation adds latency to the first token: embedding inference + vector search + reranking + context formatting. On a cold start, this can be 1-3 seconds." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 927.5293999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1377, + "mcp_result_bytes": 1476, + "wire_bytes": 1512, + "reported_used_tokens": 1476, + "working_set_bytes": 289161216, + "peak_working_set_bytes": 290054144 + }, + { + "query": "how do I throttle kimetsu API spend per month", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 877.8575, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 289173504, + "peak_working_set_bytes": 290091008 + }, + { + "query": "can the kimetsu brain database be stored in S3 instead of on disk", + "ranked": [ + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G25DB3HRGVQYHSTK90X7", + "id": "01M1X6JRCGWY24H2KBGYMMT6K3", + "kind": "memory", + "score": 0.38596054911613464, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 828.8815000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 875, + "mcp_result_bytes": 956, + "wire_bytes": 992, + "reported_used_tokens": 956, + "working_set_bytes": 289177600, + "peak_working_set_bytes": 290095104 + }, + { + "query": "how do I plug a custom tokenizer into the FTS index", + "ranked": [ + "sqlite-fts5-tokenizer" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCF4MJWVBEQZXZS69JE5", + "id": "01M1X6JS69MNRAG771GHNP8MQS", + "kind": "memory", + "score": 0.9691632390022278, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 909.2773, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 671, + "mcp_result_bytes": 756, + "wire_bytes": 792, + "reported_used_tokens": 756, + "working_set_bytes": 289206272, + "peak_working_set_bytes": 290111488 + }, + { + "query": "what should I check when kimetsu behaves differently on Windows than on Linux?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 902.5798000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 289452032, + "peak_working_set_bytes": 290365440 + }, + { + "query": "what are the moving parts of the kimetsu remote deployment story?", + "ranked": [ + "kimetsu-write-tools-gate", + "ci-secrets-masking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G5C7NRN6660DTHDS58ZS", + "id": "01M1X6JTZ6A266PDJ0ZKJ9QJQM", + "kind": "memory", + "score": 0.9729357361793518, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level \u2014 disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1X6G54DHRYVMZCDFFVTEDFC", + "id": "01M1X6JTZ6PZ11E70J0RCTVBYK", + "kind": "memory", + "score": 0.8412115573883057, + "summary": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output \u2014 but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 915.7452000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1410, + "mcp_result_bytes": 1509, + "wire_bytes": 1546, + "reported_used_tokens": 1509, + "working_set_bytes": 289816576, + "peak_working_set_bytes": 290734080 + }, + { + "query": "which lessons cover guarding behavior behind environment variables?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 813.6737, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 289861632, + "peak_working_set_bytes": 290766848 + }, + { + "query": "SQLite BUSY error under concurrent writes", + "ranked": [ + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCB5829A6ZF2C5FB9VZW", + "id": "01M1X6JWN02H6MVWFR8T0SSTYW", + "kind": "memory", + "score": 0.9978362917900084, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 771.9989, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 898, + "mcp_result_bytes": 979, + "wire_bytes": 1016, + "reported_used_tokens": 979, + "working_set_bytes": 289902592, + "peak_working_set_bytes": 290795520 + }, + { + "query": "SQLite WAL mode breaks when the database is on a network share", + "ranked": [ + "sqlite-wal-network-drive", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCDYEQS880XHFGF7HHKN", + "id": "01M1X6JXD48TPYTJXDTF6V0C3R", + "kind": "memory", + "score": 0.999302864074707, + "summary": "project:fact - [2026-09-07] [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + }, + { + "expansion_handle": "memory:01M1X6FCB5829A6ZF2C5FB9VZW", + "id": "01M1X6JXD4C70EYCFDSRPV0SBB", + "kind": "memory", + "score": 0.9966553449630736, + "summary": "project:fact - [2026-09-07] [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 929.3546, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1448, + "mcp_result_bytes": 1547, + "wire_bytes": 1584, + "reported_used_tokens": 1547, + "working_set_bytes": 289923072, + "peak_working_set_bytes": 290836480 + }, + { + "query": "my SQLite WAL database causes SQLITE_IOERR_LOCK on a mapped drive", + "ranked": [ + "sqlite-wal-network-drive" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCDYEQS880XHFGF7HHKN", + "id": "01M1X6JYA9XQPAQZRX0AA359H8", + "kind": "memory", + "score": 0.99892657995224, + "summary": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 951.6091, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 748, + "mcp_result_bytes": 829, + "wire_bytes": 866, + "reported_used_tokens": 829, + "working_set_bytes": 289996800, + "peak_working_set_bytes": 290906112 + }, + { + "query": "FTS5 tokenizer configuration for Rust identifiers with underscores", + "ranked": [ + "sqlite-fts5-tokenizer", + "kimetsu-query-stemming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCF4MJWVBEQZXZS69JE5", + "id": "01M1X6JZ87V6R64T0AVMFQ6CSP", + "kind": "memory", + "score": 0.998104453086853, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + }, + { + "expansion_handle": "memory:01M1X6G5KQ8DP47BPMHQSWGBGS", + "id": "01M1X6JZ873WVD73RAH18B00K0", + "kind": "memory", + "score": 0.7023860812187195, + "summary": "project:fact - [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 895.6216999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1212, + "mcp_result_bytes": 1331, + "wire_bytes": 1368, + "reported_used_tokens": 1331, + "working_set_bytes": 289996800, + "peak_working_set_bytes": 290906112 + }, + { + "query": "I switched the FTS5 tokenizer but search stopped returning results", + "ranked": [ + "sqlite-fts5-tokenizer" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCF4MJWVBEQZXZS69JE5", + "id": "01M1X6K045WH0BE4XB90XC3F5W", + "kind": "memory", + "score": 0.8194089531898499, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 917.5824, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 670, + "mcp_result_bytes": 755, + "wire_bytes": 792, + "reported_used_tokens": 755, + "working_set_bytes": 290025472, + "peak_working_set_bytes": 290934784 + }, + { + "query": "optimal SQLite page size for storing embedding vectors", + "ranked": [ + "sqlite-page-size", + "onnx-dim-mismatch", + "onnx-cosine-vs-dot" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCG8Q282R1BJVYTVPP5K", + "id": "01M1X6K10MQVNTPPSAR8KK1857", + "kind": "memory", + "score": 0.9990121126174928, + "summary": "project:fact - [tags: sqlite page_size performance rusqlite] SQLite's default page_size is 4096 bytes. For a write-heavy brain database with large BLOB payloads (embedding vectors), raising page_size to 16384 reduces fragmentation and improves sequential scan throughput. `PRAGMA page_size = 16384;` must be set BEFORE the first table is created \u2014 changing it on an existing database requires a VACUUM afterward to rebuild all pages." + }, + { + "expansion_handle": "memory:01M1X6FG3R3G9YXMC4MA14S64D", + "id": "01M1X6K10NDPPSAQP3P9T7B18J", + "kind": "memory", + "score": 0.9881643056869508, + "summary": "project:fact - [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results \u2014 the ANN index shape mismatch isn't always caught at runtime." + }, + { + "expansion_handle": "memory:01M1X6FG2RYQFQGDVARXZ18F3S", + "id": "01M1X6K10N01MM8MHCZ6RQ4Y1W", + "kind": "memory", + "score": 0.9425415992736816, + "summary": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing \u2014 double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 846.2722, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1860, + "mcp_result_bytes": 1977, + "wire_bytes": 2014, + "reported_used_tokens": 1977, + "working_set_bytes": 290025472, + "peak_working_set_bytes": 290934784 + }, + { + "query": "ON DELETE CASCADE in SQLite does nothing \u2014 foreign keys not enforced", + "ranked": [ + "sqlite-foreign-keys-default-off" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCH5BG9VNPYFKJ7JGXMQ", + "id": "01M1X6K1VAMH16YRSSZDJVZKZ3", + "kind": "memory", + "score": 0.9996858835220336, + "summary": "project:fact - [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting \u2014 every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 943.2256, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 736, + "mcp_result_bytes": 817, + "wire_bytes": 854, + "reported_used_tokens": 817, + "working_set_bytes": 290025472, + "peak_working_set_bytes": 290934784 + }, + { + "query": "indexing a JSON metadata column in SQLite without a schema migration", + "ranked": [ + "sqlite-json1-extract", + "testing-fixture-drift", + "onnx-dim-mismatch", + "sqlite-partial-index" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCJ2T9Y1Q3VZYZTX8SGK", + "id": "01M1X6K2RSYA1MK74K00E9D49M", + "kind": "memory", + "score": 0.9955366849899292, + "summary": "project:fact - [tags: sqlite json1 json_extract rusqlite] SQLite's json1 extension (built in since 3.38.0) lets you index and query JSONB columns with `json_extract(col, '$.field')`. To create a partial index over a JSON field: `CREATE INDEX idx ON memories (json_extract(metadata, '$.scope')) WHERE json_extract(metadata, '$.scope') IS NOT NULL;`. Use `json_each` for array fields." + }, + { + "expansion_handle": "memory:01M1X6G1V5C0B2FRN6AHK9JQJW", + "id": "01M1X6K2RSTA7939KFYWF157TQ", + "kind": "memory", + "score": 0.8227390646934509, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + }, + { + "expansion_handle": "memory:01M1X6FG3R3G9YXMC4MA14S64D", + "id": "01M1X6K2RSYRAJ7KBWHDVNDWMH", + "kind": "memory", + "score": 0.38374292850494385, + "summary": "project:fact - [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results \u2014 the ANN index shape mismatch isn't always caught at runtime." + }, + { + "expansion_handle": "memory:01M1X6FCM3T8Z791CCFJARVY6N", + "id": "01M1X6K2RTQ9VNZR2RJCX40N91", + "kind": "memory", + "score": 0.3276048004627228, + "summary": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query \u2014 the planner uses the partial index only when the WHERE clause matches." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 893.6457, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2381, + "mcp_result_bytes": 2516, + "wire_bytes": 2553, + "reported_used_tokens": 2516, + "working_set_bytes": 290025472, + "peak_working_set_bytes": 290934784 + }, + { + "query": "prepare() vs prepare_cached() in rusqlite hot insert loop", + "ranked": [ + "sqlite-prepared-stmt-cache" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCK6KS0DJ66B33EVXK0C", + "id": "01M1X6K3N1B30BQ5TPMJE46C4P", + "kind": "memory", + "score": 0.9993672966957092, + "summary": "project:fact - [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 888.5539, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 689, + "mcp_result_bytes": 770, + "wire_bytes": 807, + "reported_used_tokens": 770, + "working_set_bytes": 290033664, + "peak_working_set_bytes": 290938880 + }, + { + "query": "speed up bulk memory ingest by caching SQL statements", + "ranked": [ + "sqlite-prepared-stmt-cache" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCK6KS0DJ66B33EVXK0C", + "id": "01M1X6K4GBBZ2W2DY73AH8GDKY", + "kind": "memory", + "score": 0.9823396801948548, + "summary": "project:fact - [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 872.8126000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 688, + "mcp_result_bytes": 769, + "wire_bytes": 806, + "reported_used_tokens": 769, + "working_set_bytes": 290037760, + "peak_working_set_bytes": 290942976 + }, + { + "query": "partial index on deleted_at IS NULL for faster active memory queries", + "ranked": [ + "sqlite-partial-index" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCM3T8Z791CCFJARVY6N", + "id": "01M1X6K5BPZ9AQPF7F6HQFAQJ7", + "kind": "memory", + "score": 0.9988954067230223, + "summary": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query \u2014 the planner uses the partial index only when the WHERE clause matches." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 890.4542, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 794, + "mcp_result_bytes": 875, + "wire_bytes": 912, + "reported_used_tokens": 875, + "working_set_bytes": 290037760, + "peak_working_set_bytes": 290942976 + }, + { + "query": "the brain query is slow because it scans all rows including soft-deleted ones", + "ranked": [ + "sqlite-partial-index" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCM3T8Z791CCFJARVY6N", + "id": "01M1X6K67J2358BDB92BY6GVJH", + "kind": "memory", + "score": 0.5760471224784851, + "summary": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query \u2014 the planner uses the partial index only when the WHERE clause matches." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 924.0066, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 793, + "mcp_result_bytes": 874, + "wire_bytes": 911, + "reported_used_tokens": 874, + "working_set_bytes": 290037760, + "peak_working_set_bytes": 290955264 + }, + { + "query": "Cargo.lock changed unexpectedly after adding a new workspace crate", + "ranked": [ + "cargo-lockfile-drift", + "cargo-feature-unification-embeddings", + "cargo-target-dir-sharing", + "cargo-patch-section" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCN11BZ03K13K0EZ96KQ", + "id": "01M1X6K74MA1H2KN3KEZYMNJCN", + "kind": "memory", + "score": 0.9991374015808104, + "summary": "project:fact - [2026-09-07] [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this \u2014 it errors on any lockfile diff." + }, + { + "expansion_handle": "memory:01M1X6FBR4K1EDGA6RZKFR1659", + "id": "01M1X6K74M4G8XA7037NSNDRDN", + "kind": "memory", + "score": 0.9968542456626892, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X6FCR2A1FS53B5VE4XJM4D", + "id": "01M1X6K74NCQEFE1ZST99QYHK2", + "kind": "memory", + "score": 0.9829630851745604, + "summary": "project:fact - [2026-09-07] [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps \u2014 use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + }, + { + "expansion_handle": "memory:01M1X6FCV2P1VH9VAZGCJQXBTY", + "id": "01M1X6K74NXWWRS79MQCE39JWA", + "kind": "memory", + "score": 0.9262890815734864, + "summary": "project:fact - [2026-09-07] [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace \u2014 including transitive deps \u2014 that depend on `my-crate`. Remove the patch before publishing." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 788.7819000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3010, + "mcp_result_bytes": 3153, + "wire_bytes": 3190, + "reported_used_tokens": 3153, + "working_set_bytes": 290041856, + "peak_working_set_bytes": 290959360 + }, + { + "query": "how do I prevent CI from accepting a modified lockfile silently?", + "ranked": [ + "cargo-lockfile-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCN11BZ03K13K0EZ96KQ", + "id": "01M1X6K7X3VMKA350CNWVV8Z54", + "kind": "memory", + "score": 0.9125379323959352, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this \u2014 it errors on any lockfile diff." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 910.0345, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 765, + "mcp_result_bytes": 846, + "wire_bytes": 883, + "reported_used_tokens": 846, + "working_set_bytes": 290045952, + "peak_working_set_bytes": 290963456 + }, + { + "query": "build.rs reruns on every incremental build even when nothing changed", + "ranked": [ + "cargo-build-script-rerun" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCNZ6AVH5M9HH3ZBVGJG", + "id": "01M1X6K8SKP3BXMJT7G4YMBEV9", + "kind": "memory", + "score": 0.9996689558029176, + "summary": "project:fact - [2026-09-07] [tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 939.6328000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 698, + "mcp_result_bytes": 779, + "wire_bytes": 816, + "reported_used_tokens": 779, + "working_set_bytes": 290045952, + "peak_working_set_bytes": 290963456 + }, + { + "query": "incremental cargo build is slow because build script runs every time", + "ranked": [ + "cargo-build-script-rerun" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCNZ6AVH5M9HH3ZBVGJG", + "id": "01M1X6K9PW6SANTR4C6MFGYQ77", + "kind": "memory", + "score": 0.9978280663490297, + "summary": "project:fact - [tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 918.0167, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 685, + "mcp_result_bytes": 766, + "wire_bytes": 803, + "reported_used_tokens": 766, + "working_set_bytes": 290050048, + "peak_working_set_bytes": 290963456 + }, + { + "query": "a dev-dependency is activating an embeddings feature in my production build", + "ranked": [ + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCQ2KKER2KYG7B2280XC", + "id": "01M1X6KAMMB2J3SXZRH2V2B36N", + "kind": "memory", + "score": 0.9944193959236144, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 974.3892, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 931, + "mcp_result_bytes": 1012, + "wire_bytes": 1049, + "reported_used_tokens": 1012, + "working_set_bytes": 290066432, + "peak_working_set_bytes": 290979840 + }, + { + "query": "how do I prevent a test-only feature from bleeding into the non-test compilation?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 895.5907, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 290070528, + "peak_working_set_bytes": 290983936 + }, + { + "query": "linker errors in target/ caused by antivirus holding the exe file", + "ranked": [ + "windows-file-locking-av", + "cargo-target-dir-sharing" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FFR991WAFW34D9V4G69V", + "id": "01M1X6KCEANFEF3TG102B468HH", + "kind": "memory", + "score": 0.9997633099555968, + "summary": "project:fact - [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + }, + { + "expansion_handle": "memory:01M1X6FCR2A1FS53B5VE4XJM4D", + "id": "01M1X6KCEA3ZF5GRSAGT4T8VPD", + "kind": "memory", + "score": 0.7463976740837097, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps \u2014 use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 901.1538, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1523, + "mcp_result_bytes": 1622, + "wire_bytes": 1659, + "reported_used_tokens": 1622, + "working_set_bytes": 290082816, + "peak_working_set_bytes": 290992128 + }, + { + "query": "Access is denied (os error 5) when linking on Windows \u2014 how do I fix this?", + "ranked": [ + "windows-file-locking-av" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FFR991WAFW34D9V4G69V", + "id": "01M1X6KDADTVWCW3R3600DYK09", + "kind": "memory", + "score": 0.9977193474769592, + "summary": "project:fact - [2026-09-07] [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 902.3153000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 769, + "mcp_result_bytes": 850, + "wire_bytes": 887, + "reported_used_tokens": 850, + "working_set_bytes": 290328576, + "peak_working_set_bytes": 291237888 + }, + { + "query": "incremental build broke with a type mismatch after switching branches", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCS3JS2284QAPZ6KRSQR", + "id": "01M1X6KE6JD4QT1KN133SGBXGR", + "kind": "memory", + "score": 0.7971777319908142, + "summary": "project:fact - [2026-09-07] [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 896.1498, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 890, + "mcp_result_bytes": 971, + "wire_bytes": 1008, + "reported_used_tokens": 971, + "working_set_bytes": 290459648, + "peak_working_set_bytes": 291373056 + }, + { + "query": "cargo reports a type error that references a type not in the codebase", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCS3JS2284QAPZ6KRSQR", + "id": "01M1X6KF2JNYGZAZB58E3CWZA7", + "kind": "memory", + "score": 0.7925198078155518, + "summary": "project:fact - [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 862.7095999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 877, + "mcp_result_bytes": 958, + "wire_bytes": 995, + "reported_used_tokens": 958, + "working_set_bytes": 290467840, + "peak_working_set_bytes": 291389440 + }, + { + "query": "compile fastembed at O2 in debug builds to avoid slow embedding inference", + "ranked": [ + "cargo-profile-override", + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCT0SQT9SH5HPBEB6HH8", + "id": "01M1X6KFXKF92ZK6E1BEPAZA55", + "kind": "memory", + "score": 0.9932281374931335, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1X6G1X1XGE9MM05BANVE5DQ", + "id": "01M1X6KFXKPB5T4X2BV2PP4BBB", + "kind": "memory", + "score": 0.987656831741333, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 918.1323, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1322, + "mcp_result_bytes": 1421, + "wire_bytes": 1458, + "reported_used_tokens": 1421, + "working_set_bytes": 290496512, + "peak_working_set_bytes": 291414016 + }, + { + "query": "override compilation profile for a single crate in a Cargo workspace", + "ranked": [ + "cargo-patch-section", + "cargo-profile-override", + "cargo-target-dir-sharing", + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCV2P1VH9VAZGCJQXBTY", + "id": "01M1X6KGTAF1D3HBVECR5E443E", + "kind": "memory", + "score": 0.9984123706817628, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace \u2014 including transitive deps \u2014 that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1X6FCT0SQT9SH5HPBEB6HH8", + "id": "01M1X6KGTAXXV6JPS1WZ28DYH5", + "kind": "memory", + "score": 0.9979992508888244, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1X6FCR2A1FS53B5VE4XJM4D", + "id": "01M1X6KGTA4T47SC1XCG4CWX4K", + "kind": "memory", + "score": 0.9956549406051636, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps \u2014 use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + }, + { + "expansion_handle": "memory:01M1X6FCQ2KKER2KYG7B2280XC", + "id": "01M1X6KGTAX04A4N7TMHDQ08SD", + "kind": "memory", + "score": 0.9820712208747864, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 0.5, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 928.2771, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2682, + "mcp_result_bytes": 2821, + "wire_bytes": 2858, + "reported_used_tokens": 2821, + "working_set_bytes": 290574336, + "peak_working_set_bytes": 291495936 + }, + { + "query": "[patch.crates-io] workspace dependency override", + "ranked": [ + "cargo-patch-section", + "cargo-lockfile-drift", + "cargo-dev-dep-leak", + "cargo-target-dir-sharing" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCV2P1VH9VAZGCJQXBTY", + "id": "01M1X6KHQFF6QETXP02VH59DNS", + "kind": "memory", + "score": 0.9999405145645142, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace \u2014 including transitive deps \u2014 that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1X6FCN11BZ03K13K0EZ96KQ", + "id": "01M1X6KHQF7X0CM7H48E867YGY", + "kind": "memory", + "score": 0.9975811243057252, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this \u2014 it errors on any lockfile diff." + }, + { + "expansion_handle": "memory:01M1X6FCQ2KKER2KYG7B2280XC", + "id": "01M1X6KHQFSZ47XB4BP4TBGNJN", + "kind": "memory", + "score": 0.994149684906006, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + }, + { + "expansion_handle": "memory:01M1X6FCR2A1FS53B5VE4XJM4D", + "id": "01M1X6KHQFR4PFYV55AWPBEKEE", + "kind": "memory", + "score": 0.7471600770950317, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps \u2014 use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 707.4231, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2755, + "mcp_result_bytes": 2894, + "wire_bytes": 2931, + "reported_used_tokens": 2894, + "working_set_bytes": 290574336, + "peak_working_set_bytes": 291495936 + }, + { + "query": "pin minimum supported Rust version in Cargo.toml", + "ranked": [ + "cargo-msrv" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCW109CC42VFPE46KZBB", + "id": "01M1X6KJDN572GZM349PXMVA2G", + "kind": "memory", + "score": 0.999652862548828, + "summary": "project:fact - [tags: cargo rust msrv edition compatibility] Set `rust-version` in each `Cargo.toml` to declare the minimum supported Rust version (MSRV). Cargo enforces this with `--check`: `cargo check` fails if the toolchain is older than `rust-version`. Keep MSRV as old as your oldest supported deployment target." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 820.0004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 693, + "mcp_result_bytes": 774, + "wire_bytes": 811, + "reported_used_tokens": 774, + "working_set_bytes": 290582528, + "peak_working_set_bytes": 291500032 + }, + { + "query": "Windows path over 260 characters causes OS error 3 during Cargo build", + "ranked": [ + "windows-long-paths", + "windows-file-locking-av" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FFQ8VSBKZ2BKTFTATS24", + "id": "01M1X6KK80N8F0DYJQK5168RDA", + "kind": "memory", + "score": 0.9964189529418944, + "summary": "project:fact - [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe." + }, + { + "expansion_handle": "memory:01M1X6FFR991WAFW34D9V4G69V", + "id": "01M1X6KK80GJ9JJRYBYCM2HM76", + "kind": "memory", + "score": 0.9571694135665894, + "summary": "project:fact - [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 845.1892, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1297, + "mcp_result_bytes": 1406, + "wire_bytes": 1443, + "reported_used_tokens": 1406, + "working_set_bytes": 290709504, + "peak_working_set_bytes": 291618816 + }, + { + "query": "how do I enable long file paths for Cargo on Windows?", + "ranked": [ + "windows-long-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FFQ8VSBKZ2BKTFTATS24", + "id": "01M1X6KM20ZRWK6KR8TY3G93KZ", + "kind": "memory", + "score": 0.9998334646224976, + "summary": "project:fact - [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 915.3448, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 769, + "mcp_result_bytes": 860, + "wire_bytes": 897, + "reported_used_tokens": 860, + "working_set_bytes": 290951168, + "peak_working_set_bytes": 291868672 + }, + { + "query": "intermittent sharing violation errors when Rust linker writes the exe on Windows", + "ranked": [ + "windows-file-locking-av", + "windows-long-paths", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FFR991WAFW34D9V4G69V", + "id": "01M1X6KMY4E6D9K37NPZ8VR6PV", + "kind": "memory", + "score": 0.999750316143036, + "summary": "project:fact - [2026-09-07] [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + }, + { + "expansion_handle": "memory:01M1X6FFQ8VSBKZ2BKTFTATS24", + "id": "01M1X6KMY5JMQRR6N1FAXMBM38", + "kind": "memory", + "score": 0.4757097661495209, + "summary": "project:fact - [2026-09-07] [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe." + }, + { + "expansion_handle": "memory:01M1X6FCB5829A6ZF2C5FB9VZW", + "id": "01M1X6KMY5EB4GGM5WZEE2G4QJ", + "kind": "memory", + "score": 0.38107830286026, + "summary": "project:fact - [2026-09-07] [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 850.1333, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2006, + "mcp_result_bytes": 2133, + "wire_bytes": 2170, + "reported_used_tokens": 2133, + "working_set_bytes": 291012608, + "peak_working_set_bytes": 291926016 + }, + { + "query": "Rust walkdir follows junctions differently from symlinks on Windows", + "ranked": [ + "windows-junctions-vs-symlinks" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FFVEP9KN0JQRHTND0FM1", + "id": "01M1X6KNRWJ5GM441FQX63AZ96", + "kind": "memory", + "score": 0.9996020197868348, + "summary": "project:fact - [tags: windows junctions symlinks rust std::fs] On Windows, directory junctions (NTFS reparse points) behave like symlinks for directory traversal but `std::fs::symlink_metadata` returns `FileType::is_symlink() = false` for junctions (only true for regular symlinks). Use `std::fs::read_link` \u2014 it succeeds for both junction and symlink. `walkdir` crate's `follow_links` follows both, but its `is_symlink()` method correctly reports only actual symlinks." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 868.5502, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 845, + "mcp_result_bytes": 926, + "wire_bytes": 963, + "reported_used_tokens": 926, + "working_set_bytes": 291024896, + "peak_working_set_bytes": 291938304 + }, + { + "query": "UNC path canonicalize returns verbatim prefix \u2014 how do I strip it?", + "ranked": [ + "windows-unc-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FFS7DE9DPM08NGY5SVHJ", + "id": "01M1X6KPKZZ02FNY0QDVVWA5VP", + "kind": "memory", + "score": 0.9988629817962646, + "summary": "project:fact - [tags: windows unc-paths rust std::fs] Windows UNC paths (`\\\\server\\share\\...`) are not supported by most Rust `std::fs` operations unless passed through the extended-length prefix `\\\\?\\UNC\\server\\share\\...`. `std::path::Path::new(\"\\\\\\\\server\\\\share\")` works for basic operations but breaks with `canonicalize()` which returns the verbatim prefix form. When walking directory trees that may start on UNC paths, use the `dunce` crate to strip the verbatim prefix before comparing or displaying paths." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 998.3716000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 908, + "mcp_result_bytes": 1025, + "wire_bytes": 1062, + "reported_used_tokens": 1025, + "working_set_bytes": 291024896, + "peak_working_set_bytes": 291950592 + }, + { + "query": "UTF-8 memory text prints as mojibake in the Windows console", + "ranked": [ + "windows-console-encoding" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FFTBPZ8XFF494GQ45PTS", + "id": "01M1X6KQM7YNXTWW4NTC54680J", + "kind": "memory", + "score": 0.9996604919433594, + "summary": "project:fact - [tags: windows console encoding utf8 rust] Windows console code page defaults to the system ANSI code page (usually CP1252 or CP932), not UTF-8. Rust's `println!` writes UTF-8 bytes which display as mojibake in a non-UTF-8 console. Fix at process startup: call `SetConsoleOutputCP(65001)` via `winapi` or `windows-sys`, or set `PYTHONUTF8=1`/`RUST_LOG` before launch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 931.2324, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 757, + "mcp_result_bytes": 838, + "wire_bytes": 875, + "reported_used_tokens": 838, + "working_set_bytes": 291078144, + "peak_working_set_bytes": 291995648 + }, + { + "query": "process exit code is 4294967295 instead of -1 on Windows", + "ranked": [ + "windows-exit-codes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FFWJGHSXG15B8NJBY9ZJ", + "id": "01M1X6KRGDTKXYSGFN8WVHV8KP", + "kind": "memory", + "score": 0.9966622591018676, + "summary": "project:fact - [tags: windows exit-codes rust process child] On Windows, process exit codes are 32-bit unsigned integers (DWORD). Rust's `ExitStatus::code()` returns `Option` \u2014 it's `None` if the process was killed by a signal (which Windows doesn't use; instead, TerminateProcess with a code). Conventional codes: 0=success, 1=generic error, 0xC0000005=access violation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 915.4132999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 753, + "mcp_result_bytes": 834, + "wire_bytes": 871, + "reported_used_tokens": 834, + "working_set_bytes": 291135488, + "peak_working_set_bytes": 292048896 + }, + { + "query": "tokenizer.json must match the ONNX model \u2014 what breaks if it doesn't?", + "ranked": [ + "onnx-tokenizer-mismatch" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FFYSXQQ6C45AZ59YT4EV", + "id": "01M1X6KSD0M9NHHWZ1HH0G51A2", + "kind": "memory", + "score": 0.9991299510002136, + "summary": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly \u2014 specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings \u2014 cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 929.2096, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 959, + "mcp_result_bytes": 1040, + "wire_bytes": 1077, + "reported_used_tokens": 1040, + "working_set_bytes": 291233792, + "peak_working_set_bytes": 292147200 + }, + { + "query": "embedding quality degraded after I swapped in the INT8 quantized model", + "ranked": [ + "onnx-quantization-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FFZW8Z92NY5W7JM4TSR9", + "id": "01M1X6KTACKG2ZJ19VQQTB02D3", + "kind": "memory", + "score": 0.997980535030365, + "summary": "project:fact - [2026-09-07] [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals \u2014 cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 929.6717, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 990, + "mcp_result_bytes": 1071, + "wire_bytes": 1108, + "reported_used_tokens": 1071, + "working_set_bytes": 291233792, + "peak_working_set_bytes": 292151296 + }, + { + "query": "missing attention mask causes low-norm embeddings in batch inference", + "ranked": [ + "onnx-batch-padding" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FG0RGT5FJC7C49H034FY", + "id": "01M1X6KV7WBHFXETC6W09RDH1A", + "kind": "memory", + "score": 0.9998397827148438, + "summary": "project:fact - [tags: onnx batch padding attention-mask embeddings] When running batch inference with an ONNX model, all inputs in the batch must be padded to the same sequence length. The `attention_mask` tensor marks which tokens are real (1) and which are padding (0). Failing to pass `attention_mask` causes the model to average-pool over padding tokens, producing systematically lower-norm embeddings." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 925.7204, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 781, + "mcp_result_bytes": 862, + "wire_bytes": 899, + "reported_used_tokens": 862, + "working_set_bytes": 291262464, + "peak_working_set_bytes": 292167680 + }, + { + "query": "ONNX model download fails in a Docker container with no home directory", + "ranked": [ + "onnx-model-cache-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FG1TGFPZM415MBZM9G9G", + "id": "01M1X6KW408N341PVZ376S5R73", + "kind": "memory", + "score": 0.9887272119522096, + "summary": "project:fact - [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 946.7518, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 755, + "mcp_result_bytes": 838, + "wire_bytes": 875, + "reported_used_tokens": 838, + "working_set_bytes": 291270656, + "peak_working_set_bytes": 292179968 + }, + { + "query": "fastembed cache path environment variable for CI", + "ranked": [ + "onnx-model-cache-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FG1TGFPZM415MBZM9G9G", + "id": "01M1X6KX1KDNG5ZX8GGMHD6439", + "kind": "memory", + "score": 0.9995118379592896, + "summary": "project:fact - [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 909.1628000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 756, + "mcp_result_bytes": 839, + "wire_bytes": 876, + "reported_used_tokens": 839, + "working_set_bytes": 291270656, + "peak_working_set_bytes": 292179968 + }, + { + "query": "cosine similarity vs dot product for L2-normalized embedding vectors", + "ranked": [ + "onnx-cosine-vs-dot", + "onnx-tokenizer-mismatch", + "onnx-quantization-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FG2RYQFQGDVARXZ18F3S", + "id": "01M1X6KXY1C3W0TKGDX1ZV69GY", + "kind": "memory", + "score": 0.9999407529830932, + "summary": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing \u2014 double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + }, + { + "expansion_handle": "memory:01M1X6FFYSXQQ6C45AZ59YT4EV", + "id": "01M1X6KXY1S5C85AH5678YDSWR", + "kind": "memory", + "score": 0.9514977931976318, + "summary": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly \u2014 specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings \u2014 cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo." + }, + { + "expansion_handle": "memory:01M1X6FFZW8Z92NY5W7JM4TSR9", + "id": "01M1X6KXY1SPE5YGAYTQM3MRRM", + "kind": "memory", + "score": 0.941756010055542, + "summary": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals \u2014 cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 869.5242, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2245, + "mcp_result_bytes": 2362, + "wire_bytes": 2399, + "reported_used_tokens": 2362, + "working_set_bytes": 291270656, + "peak_working_set_bytes": 292184064 + }, + { + "query": "stored vectors have wrong dimension after switching embedding models", + "ranked": [ + "onnx-dim-mismatch", + "onnx-cosine-vs-dot", + "onnx-tokenizer-mismatch" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FG3R3G9YXMC4MA14S64D", + "id": "01M1X6KYS9MCRJ1AFGBT0DP22C", + "kind": "memory", + "score": 0.9997621178627014, + "summary": "project:fact - [2026-09-07] [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results \u2014 the ANN index shape mismatch isn't always caught at runtime." + }, + { + "expansion_handle": "memory:01M1X6FG2RYQFQGDVARXZ18F3S", + "id": "01M1X6KYS9EGA88M5AM8GW2R7E", + "kind": "memory", + "score": 0.997715711593628, + "summary": "project:fact - [2026-09-07] [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing \u2014 double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + }, + { + "expansion_handle": "memory:01M1X6FFYSXQQ6C45AZ59YT4EV", + "id": "01M1X6KYS97XARDXADZQ41HHVF", + "kind": "memory", + "score": 0.9388805031776428, + "summary": "project:fact - [2026-09-07] [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly \u2014 specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings \u2014 cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 768.2161, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2049, + "mcp_result_bytes": 2166, + "wire_bytes": 2203, + "reported_used_tokens": 2166, + "working_set_bytes": 291270656, + "peak_working_set_bytes": 292184064 + }, + { + "query": "E5 and Instructor models need a query prefix \u2014 what happens without it?", + "ranked": [ + "onnx-prefix-instructions", + "onnx-cosine-vs-dot" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FS5P1F6TRPAAV5EB9RV2", + "id": "01M1X6KZHEESF4ACEXFQDBMHK8", + "kind": "memory", + "score": 0.996955633163452, + "summary": "project:fact - [tags: onnx embeddings prefix instruction e5 query passage] E5 and Instructor family models require a text prefix on BOTH query and passage sides to produce meaningful similarities: query prefix `\"query: \"`, passage prefix `\"passage: \"`. Omitting the prefix can drop MRR by 10-15 percentage points on out-of-domain datasets. Check the model's README for the exact prefix string \u2014 it varies by model family." + }, + { + "expansion_handle": "memory:01M1X6FG2RYQFQGDVARXZ18F3S", + "id": "01M1X6KZHE81VHH6YC0CSF273A", + "kind": "memory", + "score": 0.9543967247009276, + "summary": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing \u2014 double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 914.6564000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1339, + "mcp_result_bytes": 1446, + "wire_bytes": 1483, + "reported_used_tokens": 1446, + "working_set_bytes": 291270656, + "peak_working_set_bytes": 292184064 + }, + { + "query": "ORT thread pool contention when running multiple bench processes in parallel", + "ranked": [ + "onnx-ort-threading" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FS98N5KG1JWR5W9JGH3Q", + "id": "01M1X6M0DWQCWCBGD11RD7CYA3", + "kind": "memory", + "score": 0.9998078942298888, + "summary": "project:fact - [2026-09-07] [tags: onnx ort thread-pool parallelism cpu] ORT (ONNX Runtime) creates its own inter-op and intra-op thread pools. In a multi-process bench setup, each child inherits these pools and they compete for CPU cores. Set `SessionOptionsBuilder::with_intra_threads(1).with_inter_threads(1)` if you're running many parallel bench processes \u2014 this sacrifices per-inference throughput for lower contention." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 918.3127, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 802, + "mcp_result_bytes": 883, + "wire_bytes": 920, + "reported_used_tokens": 883, + "working_set_bytes": 291291136, + "peak_working_set_bytes": 292204544 + }, + { + "query": "git worktrees share the .kimetsu brain \u2014 how do I isolate test runs?", + "ranked": [ + "git-worktree-brain-isolation", + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FSA7MZHYF9BQ9B8J0E8G", + "id": "01M1X6M1AY0FNJAF3SFKXMJX8A", + "kind": "memory", + "score": 0.9996256828308104, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root \u2014 if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + }, + { + "expansion_handle": "memory:01M1X6FC1PYTV8EEQVV2FCY4FV", + "id": "01M1X6M1AY753Q10QP24Y4DW7M", + "kind": "memory", + "score": 0.9904396533966064, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 909.6406999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1435, + "mcp_result_bytes": 1534, + "wire_bytes": 1571, + "reported_used_tokens": 1534, + "working_set_bytes": 291328000, + "peak_working_set_bytes": 292245504 + }, + { + "query": "when is it safe to use --no-verify on git commit?", + "ranked": [ + "git-hooks-bypass", + "git-reflog-rescue" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FSB5ESHBE4FQ0K5CA3VS", + "id": "01M1X6M27BDCAB8HY686PWWCH3", + "kind": "memory", + "score": 0.9956986904144288, + "summary": "project:fact - [2026-09-07] [tags: git hooks bypass pre-commit skip] `git commit --no-verify` skips ALL hooks (pre-commit and commit-msg). Never use this in shared team repos where hooks enforce quality gates (lint, tests, memory harvest). Instead, fix the failing hook." + }, + { + "expansion_handle": "memory:01M1X6FSF4P5WNAZZCJ0H37WSZ", + "id": "01M1X6M27B961TANKRCYN3T0WR", + "kind": "memory", + "score": 0.5084817409515381, + "summary": "project:fact - [2026-09-07] [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone \u2014 they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only \u2014 remote reflog is not accessible via normal git commands." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 924.5867, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1193, + "mcp_result_bytes": 1292, + "wire_bytes": 1329, + "reported_used_tokens": 1292, + "working_set_bytes": 291332096, + "peak_working_set_bytes": 292245504 + }, + { + "query": "reduce clone size and bandwidth for server-side repo ingest", + "ranked": [ + "git-sparse-checkout", + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FSC5XPNYYWTGYFQSJA47", + "id": "01M1X6M351SX8SPJM75HQFN0QN", + "kind": "memory", + "score": 0.9969936609268188, + "summary": "project:fact - [tags: git sparse-checkout partial-clone bandwidth] `git sparse-checkout init --cone` combined with `git clone --filter=blob:none` (partial clone) fetches only the commit graph and tree objects, not blobs. Individual blobs are fetched on demand when accessed. This cuts clone time for large repos from minutes to seconds." + }, + { + "expansion_handle": "memory:01M1X6FBMAHT4JHT1S9T1YAQCN", + "id": "01M1X6M3512J8BEE29C79SGPAX", + "kind": "memory", + "score": 0.8199672698974609, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1012.184, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1744, + "mcp_result_bytes": 1843, + "wire_bytes": 1880, + "reported_used_tokens": 1843, + "working_set_bytes": 291344384, + "peak_working_set_bytes": 292253696 + }, + { + "query": "spurious diffs from Windows CRLF line ending conversion in git", + "ranked": [ + "git-line-endings-windows" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FSD6AF9T2S6W9B43HM0C", + "id": "01M1X6M43QJEZFBQ9MCYJTC76A", + "kind": "memory", + "score": 0.9993343949317932, + "summary": "project:fact - [tags: git line-endings windows crlf autocrlf] On Windows, `core.autocrlf=true` (git's default for Windows installs) converts LF to CRLF on checkout and CRLF to LF on commit. This causes spurious diffs when files are edited on Windows then committed \u2014 the content is identical but the line endings differ in the index vs the working tree. Fix: set `core.autocrlf=false` and `.gitattributes` with `* text=auto eol=lf` for the repo." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 921.7174, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 940, + "reported_used_tokens": 903, + "working_set_bytes": 291459072, + "peak_working_set_bytes": 292364288 + }, + { + "query": "git submodule always gets the wrong commit in CI", + "ranked": [ + "git-submodule-pinning", + "git-hooks-bypass" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FSE7P3FNP04PMXVVBWR0", + "id": "01M1X6M50FTNDZCJT0C68F8V5J", + "kind": "memory", + "score": 0.9992856383323668, + "summary": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip \u2014 this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version." + }, + { + "expansion_handle": "memory:01M1X6FSB5ESHBE4FQ0K5CA3VS", + "id": "01M1X6M50FS5TW6W6JF08G3EPS", + "kind": "memory", + "score": 0.6295387744903564, + "summary": "project:fact - [tags: git hooks bypass pre-commit skip] `git commit --no-verify` skips ALL hooks (pre-commit and commit-msg). Never use this in shared team repos where hooks enforce quality gates (lint, tests, memory harvest). Instead, fix the failing hook." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 924.2198999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1157, + "mcp_result_bytes": 1256, + "wire_bytes": 1293, + "reported_used_tokens": 1256, + "working_set_bytes": 291495936, + "peak_working_set_bytes": 292409344 + }, + { + "query": "accidentally ran git reset --hard and lost commits \u2014 can I recover?", + "ranked": [ + "git-reflog-rescue" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FSF4P5WNAZZCJ0H37WSZ", + "id": "01M1X6M5XD2J8EFBSF72FC37E9", + "kind": "memory", + "score": 0.9995450377464294, + "summary": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone \u2014 they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only \u2014 remote reflog is not accessible via normal git commands." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 945.5919, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 762, + "mcp_result_bytes": 843, + "wire_bytes": 880, + "reported_used_tokens": 843, + "working_set_bytes": 291557376, + "peak_working_set_bytes": 292454400 + }, + { + "query": "blocking SQLite call from an async tokio handler causes latency spikes", + "ranked": [ + "tokio-blocking-in-async" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FSG1SR2HKPVJWCKTW0XB", + "id": "01M1X6M6TYVD8PQY00RS71CX4Q", + "kind": "memory", + "score": 0.9996535778045654, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 862.9171, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 766, + "mcp_result_bytes": 847, + "wire_bytes": 884, + "reported_used_tokens": 847, + "working_set_bytes": 291561472, + "peak_working_set_bytes": 292470784 + }, + { + "query": "Cannot start a runtime from within a runtime in a tokio test", + "ranked": [ + "tokio-runtime-in-tests", + "tokio-blocking-in-async" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FSH3ZJYWBSJKZKZYY4Z4", + "id": "01M1X6M7QFPEDE24VS6RKEDPY3", + "kind": "memory", + "score": 0.9997126460075378, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + }, + { + "expansion_handle": "memory:01M1X6FSG1SR2HKPVJWCKTW0XB", + "id": "01M1X6M7QFKT0YQ3QJTZAYCXT7", + "kind": "memory", + "score": 0.5779464840888977, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 919.6691000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1370, + "mcp_result_bytes": 1477, + "wire_bytes": 1514, + "reported_used_tokens": 1477, + "working_set_bytes": 291565568, + "peak_working_set_bytes": 292478976 + }, + { + "query": "tokio select cancels the other branch and loses the value in the channel", + "ranked": [ + "tokio-select-cancellation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FSJ8K553R8HRDXZWVYCK", + "id": "01M1X6M8K4CDNFFQ8JVHPY0972", + "kind": "memory", + "score": 0.9981033802032472, + "summary": "project:fact - [tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 898.5843, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 751, + "mcp_result_bytes": 832, + "wire_bytes": 869, + "reported_used_tokens": 832, + "working_set_bytes": 291524608, + "peak_working_set_bytes": 292478976 + }, + { + "query": "mpsc channel backpressure causing senders to stall", + "ranked": [ + "tokio-channel-backpressure" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FSKAN9D5R0R7PEPAWVXZ", + "id": "01M1X6M9F0XFK1MC0CPDWJJVYD", + "kind": "memory", + "score": 0.9999104738235474, + "summary": "project:fact - [tags: tokio mpsc channel backpressure async rust] `tokio::sync::mpsc::channel(N)` with a bounded buffer provides backpressure: senders block when the buffer is full. This prevents unbounded memory growth but can cause sender tasks to stall. Choosing N: too small causes frequent backpressure (throughput drops); too large defeats the purpose." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 970.8599, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 733, + "mcp_result_bytes": 814, + "wire_bytes": 851, + "reported_used_tokens": 814, + "working_set_bytes": 291590144, + "peak_working_set_bytes": 292495360 + }, + { + "query": "overhead from calling spawn_blocking on every single query request", + "ranked": [ + "tokio-spawn-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FSMB4HAEPE1NRY0HQGYK", + "id": "01M1X6MADWPJ3RHQY40X6YEHNB", + "kind": "memory", + "score": 0.9961729645729064, + "summary": "project:fact - [tags: tokio spawn_blocking thread-pool rust blocking] `tokio::task::spawn_blocking` places work on a dedicated blocking thread pool (default up to 512 threads, configurable via `Builder::max_blocking_threads`). Each call creates or reuses a thread \u2014 there's no true pooling, threads may be created on demand. For many short-duration blocking calls (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 931.0636, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 746, + "mcp_result_bytes": 827, + "wire_bytes": 864, + "reported_used_tokens": 827, + "working_set_bytes": 291631104, + "peak_working_set_bytes": 292540416 + }, + { + "query": "axum server panics during shutdown because the DB pool is already closed", + "ranked": [ + "tokio-shutdown-ordering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FXM81NH999732FH0NQF1", + "id": "01M1X6MBAYGGGM485Z5ND03RKR", + "kind": "memory", + "score": 0.98052579164505, + "summary": "project:fact - [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries \u2014 the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 955.8805, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 931, + "mcp_result_bytes": 1012, + "wire_bytes": 1049, + "reported_used_tokens": 1012, + "working_set_bytes": 291647488, + "peak_working_set_bytes": 292564992 + }, + { + "query": "reqwest Client created per-request defeats connection pooling", + "ranked": [ + "http-connection-pooling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FXN8ZABYN3NE7VTA3TA2", + "id": "01M1X6MC84P6EQWE21RDGRX339", + "kind": "memory", + "score": 0.9998082518577576, + "summary": "project:fact - [tags: http reqwest connection-pool keep-alive rust] reqwest's `Client` holds a connection pool; always create ONE `Client` instance and clone it for each handler \u2014 cloning is cheap (Arc under the hood). Creating a `Client::new()` per request defeats connection pooling and causes TCP connection exhaustion under load. The default pool settings: max_idle_per_host=usize::MAX (unbounded), idle_timeout=90s." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 859.7458, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 797, + "mcp_result_bytes": 878, + "wire_bytes": 915, + "reported_used_tokens": 878, + "working_set_bytes": 291659776, + "peak_working_set_bytes": 292564992 + }, + { + "query": "LLM request times out during streaming \u2014 which timeout setting applies?", + "ranked": [ + "http-timeout-layering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FXPB6K07WRKD3KGXF7EF", + "id": "01M1X6MD3128DSK4QG6RVDQWDD", + "kind": "memory", + "score": 0.9987107515335084, + "summary": "project:fact - [tags: http reqwest timeout connect read total rust] reqwest has three distinct timeout knobs: `connect_timeout`, `read_timeout`, and `timeout` (total). They compose: if all three are set, the request fails at whichever fires first. For LLM API calls with streaming responses, `read_timeout` must be larger than the slowest expected token (often 30-60s) while `connect_timeout` can be tight (3-5s)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 781.1405, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 788, + "mcp_result_bytes": 869, + "wire_bytes": 906, + "reported_used_tokens": 869, + "working_set_bytes": 291663872, + "peak_working_set_bytes": 292577280 + }, + { + "query": "how do I safely retry a POST to the LLM API without creating duplicates?", + "ranked": [ + "http-retry-idempotency" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FXQC2X80NMEXZKQGHQ6F", + "id": "01M1X6MDVFQ39CRNH3HDDEDMYK", + "kind": "memory", + "score": 0.9995805621147156, + "summary": "project:fact - [tags: http retry idempotency post put reqwest] Only retry idempotent requests automatically. GET, HEAD, PUT, DELETE are idempotent. POST is NOT \u2014 retrying a POST may create duplicate resources." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 945.2389, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 585, + "mcp_result_bytes": 666, + "wire_bytes": 703, + "reported_used_tokens": 666, + "working_set_bytes": 291676160, + "peak_working_set_bytes": 292593664 + }, + { + "query": "custom enterprise root CA not trusted by rustls on Windows", + "ranked": [ + "http-tls-roots", + "http-proxy-env" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FXREK18YEP6CRP9SWNC4", + "id": "01M1X6MES1J8QSSTGY8VRRSBNM", + "kind": "memory", + "score": 0.9998220801353456, + "summary": "project:fact - [tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle \u2014 the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle." + }, + { + "expansion_handle": "memory:01M1X6FXTH25ZXV6TZSV92WFM9", + "id": "01M1X6MES1CPSS3Q5VDWVHZ3SD", + "kind": "memory", + "score": 0.38715291023254395, + "summary": "project:fact - [tags: http proxy environment reqwest rust corporate] reqwest respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` environment variables by default (with `default-tls` or `rustls-tls`). In a corporate network, these may redirect traffic through an intercepting proxy that breaks mTLS or adds latency. To disable proxy usage entirely: `reqwest::ClientBuilder::no_proxy()`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 924.959, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1311, + "mcp_result_bytes": 1410, + "wire_bytes": 1447, + "reported_used_tokens": 1410, + "working_set_bytes": 291676160, + "peak_working_set_bytes": 292593664 + }, + { + "query": "parsing server-sent events when a single TCP chunk contains a partial SSE frame", + "ranked": [ + "http-streaming-bodies" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FXSE15217ZRDGWT76DK4", + "id": "01M1X6MFP30W4XP3RSK1921688", + "kind": "memory", + "score": 0.9667426943778992, + "summary": "project:fact - [2026-09-07] [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding \u2014 a chunk may split across frame boundaries." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 849.7919, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 859, + "mcp_result_bytes": 940, + "wire_bytes": 977, + "reported_used_tokens": 940, + "working_set_bytes": 291684352, + "peak_working_set_bytes": 292597760 + }, + { + "query": "reqwest does not use the system proxy settings on Windows", + "ranked": [ + "http-proxy-env", + "http-tls-roots", + "http-connection-pooling", + "http-streaming-bodies" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FXTH25ZXV6TZSV92WFM9", + "id": "01M1X6MGGHJ21NBKD71ACG1AHJ", + "kind": "memory", + "score": 0.9997830986976624, + "summary": "project:fact - [tags: http proxy environment reqwest rust corporate] reqwest respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` environment variables by default (with `default-tls` or `rustls-tls`). In a corporate network, these may redirect traffic through an intercepting proxy that breaks mTLS or adds latency. To disable proxy usage entirely: `reqwest::ClientBuilder::no_proxy()`." + }, + { + "expansion_handle": "memory:01M1X6FXREK18YEP6CRP9SWNC4", + "id": "01M1X6MGGHZ3TWG9FY7M7WXKJ8", + "kind": "memory", + "score": 0.9808586239814758, + "summary": "project:fact - [tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle \u2014 the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle." + }, + { + "expansion_handle": "memory:01M1X6FXN8ZABYN3NE7VTA3TA2", + "id": "01M1X6MGGHVEA7MZVMHEGYYMPH", + "kind": "memory", + "score": 0.719273030757904, + "summary": "project:fact - [tags: http reqwest connection-pool keep-alive rust] reqwest's `Client` holds a connection pool; always create ONE `Client` instance and clone it for each handler \u2014 cloning is cheap (Arc under the hood). Creating a `Client::new()` per request defeats connection pooling and causes TCP connection exhaustion under load. The default pool settings: max_idle_per_host=usize::MAX (unbounded), idle_timeout=90s." + }, + { + "expansion_handle": "memory:01M1X6FXSE15217ZRDGWT76DK4", + "id": "01M1X6MGGHK2N1P2TK66PR1Z6C", + "kind": "memory", + "score": 0.7009692192077637, + "summary": "project:fact - [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding \u2014 a chunk may split across frame boundaries." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 904.0970000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2497, + "mcp_result_bytes": 2632, + "wire_bytes": 2669, + "reported_used_tokens": 2632, + "working_set_bytes": 291696640, + "peak_working_set_bytes": 292605952 + }, + { + "query": "insta snapshot tests fail in CI because output includes a timestamp", + "ranked": [ + "testing-snapshot-churn", + "ci-flaky-quarantine" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FXVJWFJRV5NQKVFGYH08", + "id": "01M1X6MHD3DY28R97EJHE7AJJM", + "kind": "memory", + "score": 0.999855637550354, + "summary": "project:fact - [tags: testing snapshot insta assert churn rust] Snapshot tests (e.g. with the `insta` crate) fail whenever the output changes, even for intended changes. In CI, they fail loudly; locally, `cargo insta review` walks you through accepting or rejecting changes." + }, + { + "expansion_handle": "memory:01M1X6G569HH9Q1FNPCPH6GQV2", + "id": "01M1X6MHD3RJ9CQR3GZSA2VVDH", + "kind": "memory", + "score": 0.5997360348701477, + "summary": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal \u2014 a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 834.9459, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1196, + "mcp_result_bytes": 1295, + "wire_bytes": 1332, + "reported_used_tokens": 1295, + "working_set_bytes": 291733504, + "peak_working_set_bytes": 292646912 + }, + { + "query": "two test workers writing to the same temp directory path race each other", + "ranked": [ + "testing-temp-dirs-ci" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FXWMDSNDYDFJ8T7ES5D5", + "id": "01M1X6MJ70BV9J7SFVFZ8MEMP6", + "kind": "memory", + "score": 0.9889234900474548, + "summary": "project:fact - [tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 887.6359, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 755, + "mcp_result_bytes": 836, + "wire_bytes": 873, + "reported_used_tokens": 836, + "working_set_bytes": 291758080, + "peak_working_set_bytes": 292667392 + }, + { + "query": "test passes locally but fails on a slow CI runner due to a 100ms sleep", + "ranked": [ + "testing-time-dependent-flakes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FXXQ3XGY48XD6EHMVND0", + "id": "01M1X6MK2X9E7NQVJ8AWRM492B", + "kind": "memory", + "score": 0.808289110660553, + "summary": "project:fact - [tags: testing time flaky clock mock rust] Tests that depend on wall-clock time are inherently flaky under load (slow CI runners, GC pauses). Abstract time behind a trait (`Clock: Fn() -> SystemTime`) injected at construction, and supply a fake in tests. For tests checking that something happened \"within N seconds\", use a generous multiple of the expected duration (10x is not unreasonable for CI)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 959.1518, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 790, + "mcp_result_bytes": 875, + "wire_bytes": 912, + "reported_used_tokens": 875, + "working_set_bytes": 291758080, + "peak_working_set_bytes": 292679680 + }, + { + "query": "proptest found a hash collision in text normalization that example tests missed", + "ranked": [ + "testing-property-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FXYP8RYKPBJ33PNZH7NJ", + "id": "01M1X6MM128FX20K47YSSQY8PZ", + "kind": "memory", + "score": 0.9994783997535706, + "summary": "project:fact - [tags: testing property-based proptest quickcheck rust] Property-based tests (proptest, quickcheck) find edge cases that example-based tests miss. For kimetsu's memory text normalization, proptest found that zero-width joiner characters and right-to-left marks caused hash collisions. Run proptest with `PROPTEST_CASES=10000` in CI for thorough coverage." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 947.9764, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 744, + "mcp_result_bytes": 825, + "wire_bytes": 862, + "reported_used_tokens": 825, + "working_set_bytes": 291766272, + "peak_working_set_bytes": 292679680 + }, + { + "query": "set_var in tests races when cargo test runs them in parallel", + "ranked": [ + "testing-serial-vs-parallel" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FXZM3KNGPDANSTPNKTQR", + "id": "01M1X6MMYFMGRB9XPFGTCKPN07", + "kind": "memory", + "score": 0.9997344613075256, + "summary": "project:fact - [2026-09-07] [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 925.0024999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 832, + "mcp_result_bytes": 913, + "wire_bytes": 950, + "reported_used_tokens": 913, + "working_set_bytes": 291799040, + "peak_working_set_bytes": 292720640 + }, + { + "query": "hardcoded JSON fixtures broke after a schema migration", + "ranked": [ + "testing-fixture-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G1V5C0B2FRN6AHK9JQJW", + "id": "01M1X6MNV8YP6F0RYMGDYHWPR6", + "kind": "memory", + "score": 0.9998371601104736, + "summary": "project:fact - [2026-09-07] [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 782.6581, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 783, + "mcp_result_bytes": 864, + "wire_bytes": 901, + "reported_used_tokens": 864, + "working_set_bytes": 291811328, + "peak_working_set_bytes": 292720640 + }, + { + "query": "debug print in the MCP handler corrupts the JSON-Lines protocol stream", + "ranked": [ + "mcp-stdout-protocol" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G1W0ZBD86TBRPQESXKTX", + "id": "01M1X6MPMR5PRF1QA1REGZJKRF", + "kind": "memory", + "score": 0.9997472167015076, + "summary": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 971.3231, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 705, + "mcp_result_bytes": 786, + "wire_bytes": 823, + "reported_used_tokens": 786, + "working_set_bytes": 291811328, + "peak_working_set_bytes": 292724736 + }, + { + "query": "kimetsu MCP tool call times out because embedding model is re-initialized every call", + "ranked": [ + "mcp-tool-timeouts", + "mcp-schema-validation", + "kimetsu-bench-remote-embedder-singleton" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G1X1XGE9MM05BANVE5DQ", + "id": "01M1X6MQJNZA08SYFEBY2FTXQR", + "kind": "memory", + "score": 0.9995898604393004, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + }, + { + "expansion_handle": "memory:01M1X6G1Z4B34X6RM0V12B7VR5", + "id": "01M1X6MQJNVBYQVE3PXJFW0CWC", + "kind": "memory", + "score": 0.6027993559837341, + "summary": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array \u2014 omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error." + }, + { + "expansion_handle": "memory:01M1X6G5NP21NSKR0TBJPHB80K", + "id": "01M1X6MQJN52SARADGD0N8X6WN", + "kind": "memory", + "score": 0.5117799639701843, + "summary": "project:fact - [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 838.5128, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2085, + "mcp_result_bytes": 2202, + "wire_bytes": 2239, + "reported_used_tokens": 2202, + "working_set_bytes": 291811328, + "peak_working_set_bytes": 292724736 + }, + { + "query": "env var set after host launch is not visible to the MCP server process", + "ranked": [ + "mcp-env-propagation", + "kimetsu-daemon-lifecycle" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G1Y3K75Y1EDB0JHXK3KF", + "id": "01M1X6MRCKJJ7W1Q8HWV657ZEF", + "kind": "memory", + "score": 0.9984827637672424, + "summary": "project:fact - [2026-09-07] [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment \u2014 changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate." + }, + { + "expansion_handle": "memory:01M1X6G57BG9P4RZW0F69BHBK0", + "id": "01M1X6MRCK3MNYZBV929T4S2TE", + "kind": "memory", + "score": 0.9977922439575196, + "summary": "project:fact - [2026-09-07] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 966.4412, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1267, + "mcp_result_bytes": 1366, + "wire_bytes": 1403, + "reported_used_tokens": 1366, + "working_set_bytes": 291811328, + "peak_working_set_bytes": 292732928 + }, + { + "query": "MCP tool call fails because a required field is missing from the JSON input", + "ranked": [ + "mcp-schema-validation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G1Z4B34X6RM0V12B7VR5", + "id": "01M1X6MSAMD1HH97HMTTKWFBX1", + "kind": "memory", + "score": 0.998538613319397, + "summary": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array \u2014 omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 916.9861000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 798, + "mcp_result_bytes": 879, + "wire_bytes": 916, + "reported_used_tokens": 879, + "working_set_bytes": 291811328, + "peak_working_set_bytes": 292732928 + }, + { + "query": "Claude Code rejects the tool name with a hyphen in it", + "ranked": [ + "mcp-tool-naming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G20244N0FWHDXK4YPMG6", + "id": "01M1X6MT78G03JH3AXGB7E51C1", + "kind": "memory", + "score": 0.9982439279556274, + "summary": "project:fact - [tags: mcp tool naming convention kimetsu] MCP tool names must be valid identifiers for all host agents. Claude Code restricts tool names to `[a-zA-Z0-9_-]` and max 64 chars. Use `snake_case` (kimetsu_brain_context, kimetsu_brain_record) \u2014 hyphen is technically allowed but some hosts reject it." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 886.049, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 687, + "mcp_result_bytes": 768, + "wire_bytes": 805, + "reported_used_tokens": 768, + "working_set_bytes": 291913728, + "peak_working_set_bytes": 292827136 + }, + { + "query": "MCP response path uses backslashes and the host rejects it", + "ranked": [ + "mcp-transcript-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G212AV49JAYRE5J3NT5J", + "id": "01M1X6MV36REFAJV4K7G1ZS6MW", + "kind": "memory", + "score": 0.9984637498855592, + "summary": "project:fact - [tags: mcp transcript paths kimetsu hooks runs] kimetsu writes run transcripts to `/.kimetsu/runs//`. The post-session hook reads the latest run's transcript to trigger memory harvest. On Windows, the path uses backslashes internally but the MCP JSON must use forward slashes or the host may reject path-type arguments." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 937.4074, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 724, + "mcp_result_bytes": 805, + "wire_bytes": 842, + "reported_used_tokens": 805, + "working_set_bytes": 291913728, + "peak_working_set_bytes": 292827136 + }, + { + "query": "AWS credentials not found \u2014 which env var does kimetsu read for Bedrock?", + "ranked": [ + "aws-credentials-chain", + "aws-region-resolution", + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G225TRC4EVAFGXT9RHRX", + "id": "01M1X6MW0PS1JA22Y4HJ1Z70X5", + "kind": "memory", + "score": 0.9990235567092896, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + }, + { + "expansion_handle": "memory:01M1X6G23CEN8Y7G8DV4SNBYQB", + "id": "01M1X6MW0PWGGKJ4EPH21AQ096", + "kind": "memory", + "score": 0.9968422651290894, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X6FBSFGZG953DWGDC5CQJC", + "id": "01M1X6MW0P9SRETETMKHTR361N", + "kind": "memory", + "score": 0.9849756360054016, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X6FBYZ5JT46Z87176E5AER", + "id": "01M1X6MW0PT2RPDA1PZCPSY4V0", + "kind": "memory", + "score": 0.9203452467918396, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 969.0597, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3455, + "mcp_result_bytes": 3618, + "wire_bytes": 3655, + "reported_used_tokens": 3618, + "working_set_bytes": 291917824, + "peak_working_set_bytes": 292831232 + }, + { + "query": "Bedrock InvokeModel fails because the region is not configured", + "ranked": [ + "aws-region-resolution", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G23CEN8Y7G8DV4SNBYQB", + "id": "01M1X6MWYKJNCPBR1K12RPT1AQ", + "kind": "memory", + "score": 0.99688321352005, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X6FBYZ5JT46Z87176E5AER", + "id": "01M1X6MWYK0NFSDM8CV0SQC7M3", + "kind": "memory", + "score": 0.6450709104537964, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 958.3565, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1810, + "mcp_result_bytes": 1929, + "wire_bytes": 1966, + "reported_used_tokens": 1929, + "working_set_bytes": 291979264, + "peak_working_set_bytes": 292896768 + }, + { + "query": "how do I handle ThrottlingException from Bedrock with exponential backoff?", + "ranked": [ + "aws-retry-throttling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G24BKQF5H1P4JPDZJ0GV", + "id": "01M1X6MXWMH5X809J0F1TFFTSK", + "kind": "memory", + "score": 0.9997082352638244, + "summary": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with \u00b125% jitter." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 982.6761, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 771, + "mcp_result_bytes": 868, + "wire_bytes": 905, + "reported_used_tokens": 868, + "working_set_bytes": 291987456, + "peak_working_set_bytes": 292896768 + }, + { + "query": "generating a presigned S3 URL for brain export without exposing credentials", + "ranked": [ + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G25DB3HRGVQYHSTK90X7", + "id": "01M1X6MYVDVY1J0BKKTRQ3HK7N", + "kind": "memory", + "score": 0.9990487694740297, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 927.4926, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 875, + "mcp_result_bytes": 956, + "wire_bytes": 993, + "reported_used_tokens": 956, + "working_set_bytes": 292012032, + "peak_working_set_bytes": 292925440 + }, + { + "query": "IMDSv2 token required for instance metadata \u2014 PUT before GET", + "ranked": [ + "aws-instance-metadata" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G26FFC1E97N2YGRW0EA1", + "id": "01M1X6MZRGZJ66JT8PYXAQBVVD", + "kind": "memory", + "score": 0.9997182488441468, + "summary": "project:fact - [2026-09-07] [tags: aws imds instance-metadata ec2 token] The AWS Instance Metadata Service v2 (IMDSv2) requires a session token: PUT `http://169.254.169.254/latest/api/token` with `X-aws-ec2-metadata-token-ttl-seconds: 21600` to get a token, then GET metadata with `X-aws-ec2-metadata-token: `. IMDSv1 (no token) is disabled on hardened instances. The metadata endpoint is only reachable from within EC2 \u2014 a connection timeout means you're not on EC2." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 922.7968, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 851, + "mcp_result_bytes": 932, + "wire_bytes": 969, + "reported_used_tokens": 932, + "working_set_bytes": 292012032, + "peak_working_set_bytes": 292925440 + }, + { + "query": "Cargo cache key strategy for GitHub Actions to avoid toolchain version collisions", + "ranked": [ + "ci-cache-keys" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G52K557P44BJMTJKMB7J", + "id": "01M1X6N0N7P1HGY345YSW21QG8", + "kind": "memory", + "score": 0.998869240283966, + "summary": "project:fact - [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key \u2014 macOS and Windows have incompatible artifact formats." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 938.7366999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 788, + "mcp_result_bytes": 869, + "wire_bytes": 906, + "reported_used_tokens": 869, + "working_set_bytes": 292032512, + "peak_working_set_bytes": 292945920 + }, + { + "query": "CI matrix has 18 jobs and costs too much \u2014 how do I reduce it?", + "ranked": [ + "ci-matrix-explosion" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G53FQFM4EWTPS5X1Q0PW", + "id": "01M1X6N1JPVE6CXB148EMGDMT3", + "kind": "memory", + "score": 0.999057948589325, + "summary": "project:fact - [tags: ci github-actions matrix jobs resources] A CI matrix combining OS (3) x Rust toolchain (3) x features (2) = 18 jobs. Each spawns a runner; at $0.008/min for Ubuntu and $0.016/min for Windows, a 10-minute build costs $2.40 per push. Reduce: test the full matrix only on PRs to main; on feature branches, test only Linux+stable." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 965.0097, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 722, + "mcp_result_bytes": 803, + "wire_bytes": 840, + "reported_used_tokens": 803, + "working_set_bytes": 292151296, + "peak_working_set_bytes": 293068800 + }, + { + "query": "GitHub Actions secret accidentally printed in build logs", + "ranked": [ + "ci-secrets-masking", + "ci-cache-keys", + "ci-artifact-retention" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G54DHRYVMZCDFFVTEDFC", + "id": "01M1X6N2GPHAFQ7S700B9VFG1N", + "kind": "memory", + "score": 0.9963951706886292, + "summary": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output \u2014 but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable." + }, + { + "expansion_handle": "memory:01M1X6G52K557P44BJMTJKMB7J", + "id": "01M1X6N2GPXCNTBYKNPTE87CF2", + "kind": "memory", + "score": 0.4342843890190125, + "summary": "project:fact - [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key \u2014 macOS and Windows have incompatible artifact formats." + }, + { + "expansion_handle": "memory:01M1X6G55DSAXTVCH86QSPHSYF", + "id": "01M1X6N2GPQN6ENVNSNH58H05B", + "kind": "memory", + "score": 0.3422144949436188, + "summary": "project:fact - [tags: ci github-actions artifacts retention benchmark] GitHub Actions artifacts are retained for 90 days (default). For benchmark results, use `actions/upload-artifact` with `retention-days: 365` for long-term tracking. The free tier has 500MB storage \u2014 per-combo JSON files from kimetsu bench (each ~60KB) add up fast if you upload them on every push." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 908.0222, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1791, + "mcp_result_bytes": 1908, + "wire_bytes": 1945, + "reported_used_tokens": 1908, + "working_set_bytes": 292179968, + "peak_working_set_bytes": 293093376 + }, + { + "query": "how long do GitHub Actions artifacts persist and what's the storage limit?", + "ranked": [ + "ci-artifact-retention" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G55DSAXTVCH86QSPHSYF", + "id": "01M1X6N3DFME0ZM2ZYKT7CD6XR", + "kind": "memory", + "score": 0.999624252319336, + "summary": "project:fact - [tags: ci github-actions artifacts retention benchmark] GitHub Actions artifacts are retained for 90 days (default). For benchmark results, use `actions/upload-artifact` with `retention-days: 365` for long-term tracking. The free tier has 500MB storage \u2014 per-combo JSON files from kimetsu bench (each ~60KB) add up fast if you upload them on every push." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1000.9542, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 744, + "mcp_result_bytes": 825, + "wire_bytes": 862, + "reported_used_tokens": 825, + "working_set_bytes": 292192256, + "peak_working_set_bytes": 293109760 + }, + { + "query": "timing-based test flake in CI \u2014 quarantine or fix?", + "ranked": [ + "ci-flaky-quarantine", + "testing-time-dependent-flakes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G569HH9Q1FNPCPH6GQV2", + "id": "01M1X6N4D0VJAPV6CWFC7Q6YHH", + "kind": "memory", + "score": 0.9994743466377258, + "summary": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal \u2014 a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output." + }, + { + "expansion_handle": "memory:01M1X6FXXQ3XGY48XD6EHMVND0", + "id": "01M1X6N4D0GZH144QFVFNPANHB", + "kind": "memory", + "score": 0.9849997162818908, + "summary": "project:fact - [tags: testing time flaky clock mock rust] Tests that depend on wall-clock time are inherently flaky under load (slow CI runners, GC pauses). Abstract time behind a trait (`Clock: Fn() -> SystemTime`) injected at construction, and supply a fake in tests. For tests checking that something happened \"within N seconds\", use a generous multiple of the expected duration (10x is not unreasonable for CI)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 970.1291, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1340, + "mcp_result_bytes": 1443, + "wire_bytes": 1480, + "reported_used_tokens": 1443, + "working_set_bytes": 292216832, + "peak_working_set_bytes": 293138432 + }, + { + "query": "kimetsu doctor says the MCP server is running \u2014 how do I stop it before an update?", + "ranked": [ + "kimetsu-daemon-lifecycle", + "mcp-env-propagation", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G57BG9P4RZW0F69BHBK0", + "id": "01M1X6N5BJ5DS5AE36N9CXTP55", + "kind": "memory", + "score": 0.9989782571792604, + "summary": "project:fact - [2026-09-07] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1X6G1Y3K75Y1EDB0JHXK3KF", + "id": "01M1X6N5BJYPVFVQQZ2HAKQY39", + "kind": "memory", + "score": 0.9049031734466552, + "summary": "project:fact - [2026-09-07] [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment \u2014 changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate." + }, + { + "expansion_handle": "memory:01M1X6FBP8RS75FNREKQZ0WD3Q", + "id": "01M1X6N5BJCXQ0XH9MPGJMDXCB", + "kind": "memory", + "score": 0.4812128245830536, + "summary": "project:fact - [2026-09-07] [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1002.3016, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2046, + "mcp_result_bytes": 2211, + "wire_bytes": 2248, + "reported_used_tokens": 2211, + "working_set_bytes": 292216832, + "peak_working_set_bytes": 293138432 + }, + { + "query": "noise capsules consuming token budget without contributing retrieval signal", + "ranked": [ + "kimetsu-capsule-budgets" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G58AVVH0NJSP58CG862R", + "id": "01M1X6N6A12MX8YE25SAN5BTY9", + "kind": "memory", + "score": 0.9997420907020568, + "summary": "project:fact - [tags: kimetsu capsule tokens budget retrieval] kimetsu retrieval enforces a token budget per capsule type: memory capsules are capped at 6000 tokens total (across all retrieved memories), file capsules at 3000 tokens. When a memory is large and would exceed the budget, it is truncated at a sentence boundary. The budget is enforced AFTER reranking \u2014 reranking may reorder results so that a truncated high-ranked memory displaces a full lower-ranked one." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 768.0916, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 847, + "mcp_result_bytes": 928, + "wire_bytes": 965, + "reported_used_tokens": 928, + "working_set_bytes": 292249600, + "peak_working_set_bytes": 293163008 + }, + { + "query": "kimetsu_brain_record writes to the wrong brain location \u2014 user vs project scope", + "ranked": [ + "kimetsu-memory-scopes", + "kimetsu-write-tools-gate", + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G596BN9AAJMY7GJTXTHF", + "id": "01M1X6N728ZCVAWP533GZGEFS7", + "kind": "memory", + "score": 0.999030828475952, + "summary": "project:fact - [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available \u2014 if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope." + }, + { + "expansion_handle": "memory:01M1X6G5C7NRN6660DTHDS58ZS", + "id": "01M1X6N728YC86EDVPSGZ8CZRJ", + "kind": "memory", + "score": 0.9838979840278624, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level \u2014 disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1X6FC1PYTV8EEQVV2FCY4FV", + "id": "01M1X6N728TR5V33S7BGRAFYJ8", + "kind": "memory", + "score": 0.3852712512016296, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 899.9648, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2098, + "mcp_result_bytes": 2215, + "wire_bytes": 2252, + "reported_used_tokens": 2215, + "working_set_bytes": 292286464, + "peak_working_set_bytes": 293195776 + }, + { + "query": "how do I configure kimetsu to use Claude Haiku for harvesting but Opus for the agent?", + "ranked": [ + "kimetsu-distiller-config" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G5A797YY5GFD0S04XJGC", + "id": "01M1X6N7YJP2VS70J65WE3HTJY", + "kind": "memory", + "score": 0.9989088773727416, + "summary": "project:fact - [tags: kimetsu distiller harvest config provider] The kimetsu distiller (auto-harvester) uses a SEPARATE provider configuration from the main agent: `distiller.provider`, `distiller.model`, `distiller.api_key`. This allows running the agent on an expensive model (Claude Opus) while harvesting with a cheap model (Claude Haiku). If `distiller.provider` is not set, it inherits `provider`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 938.3766, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 778, + "mcp_result_bytes": 859, + "wire_bytes": 896, + "reported_used_tokens": 859, + "working_set_bytes": 292290560, + "peak_working_set_bytes": 293203968 + }, + { + "query": "first agent turn is slow because kimetsu proactive hook runs embedding inference", + "ranked": [ + "kimetsu-proactive-hooks", + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G5B7FXBAABGSVRFWFCX6", + "id": "01M1X6N8VN8K1VWT7YV4VCM8Z0", + "kind": "memory", + "score": 0.999568521976471, + "summary": "project:fact - [2026-09-07] [tags: kimetsu proactive hooks context injection] kimetsu's proactive context injection runs before each agent turn (pre-turn hook) and injects relevant memories into the system prompt prefix. The hook invocation adds latency to the first token: embedding inference + vector search + reranking + context formatting. On a cold start, this can be 1-3 seconds." + }, + { + "expansion_handle": "memory:01M1X6G1X1XGE9MM05BANVE5DQ", + "id": "01M1X6N8VNPP40W3XBHBDNREDD", + "kind": "memory", + "score": 0.9405298233032228, + "summary": "project:fact - [2026-09-07] [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 962.1216000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1403, + "mcp_result_bytes": 1502, + "wire_bytes": 1539, + "reported_used_tokens": 1502, + "working_set_bytes": 292290560, + "peak_working_set_bytes": 293203968 + }, + { + "query": "make the kimetsu brain read-only for certain repos on a shared remote server", + "ranked": [ + "kimetsu-write-tools-gate", + "remote-ingest-split-roots", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G5C7NRN6660DTHDS58ZS", + "id": "01M1X6N9SRQN7DHX3F87FKW24R", + "kind": "memory", + "score": 0.997682809829712, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level \u2014 disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1X6FBMAHT4JHT1S9T1YAQCN", + "id": "01M1X6N9SRM0J2YEEP4JP0FTKD", + "kind": "memory", + "score": 0.9957050681114196, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1X6FBP8RS75FNREKQZ0WD3Q", + "id": "01M1X6N9SRMCQQ376T38EVJM92", + "kind": "memory", + "score": 0.9909282326698304, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 897.4788, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2725, + "mcp_result_bytes": 2890, + "wire_bytes": 2927, + "reported_used_tokens": 2890, + "working_set_bytes": 292290560, + "peak_working_set_bytes": 293203968 + }, + { + "query": "kimetsu FTS search misses 'deadlocking' when memory says 'deadlock'", + "ranked": [ + "kimetsu-query-stemming", + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G5KQ8DP47BPMHQSWGBGS", + "id": "01M1X6NAP3K0GTZJV1RZGFD6CQ", + "kind": "memory", + "score": 0.9904030561447144, + "summary": "project:fact - [2026-09-07] [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression." + }, + { + "expansion_handle": "memory:01M1X6FBK795VCKKWK7JEKPT4J", + "id": "01M1X6NAP3YAYBZR26X53RNWG5", + "kind": "memory", + "score": 0.91664320230484, + "summary": "project:fact - [2026-09-07] [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure \u2014 `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 874.9429, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1363, + "mcp_result_bytes": 1478, + "wire_bytes": 1515, + "reported_used_tokens": 1478, + "working_set_bytes": 292290560, + "peak_working_set_bytes": 293203968 + }, + { + "query": "how does pool size affect retrieval recall and latency in the bench?", + "ranked": [ + "kimetsu-rerank-pool" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G5MRFQ9WMBZ3BKM72KNT", + "id": "01M1X6NBHCAGXHKWDX593WQZ80", + "kind": "memory", + "score": 0.9998373985290528, + "summary": "project:fact - [tags: kimetsu reranker pool size ann retrieval] kimetsu's retrieval pipeline: ANN (approximate nearest neighbor) retrieves a pool of candidates, then the reranker reorders them, then the top-K are returned. The pool size (default 6 for production, 12 in bench) controls the recall-latency tradeoff: larger pool = higher recall = more reranker calls = more latency. For the jina-tiny reranker, pool 12 adds ~80ms vs pool 6." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 919.0719, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 813, + "mcp_result_bytes": 894, + "wire_bytes": 931, + "reported_used_tokens": 894, + "working_set_bytes": 292290560, + "peak_working_set_bytes": 293208064 + }, + { + "query": "second embedder in a remote bench run gets worse results than the first", + "ranked": [ + "kimetsu-bench-remote-embedder-singleton" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G5NP21NSKR0TBJPHB80K", + "id": "01M1X6NCEQR22YGMNYFKJ61163", + "kind": "memory", + "score": 0.9939629435539246, + "summary": "project:fact - [2026-09-07] [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 932.421, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 895, + "mcp_result_bytes": 976, + "wire_bytes": 1013, + "reported_used_tokens": 976, + "working_set_bytes": 292290560, + "peak_working_set_bytes": 293208064 + }, + { + "query": "what is the expected JSON schema for kimetsu brain bench dataset files?", + "ranked": [ + "kimetsu-eval-fixture-shape", + "testing-fixture-drift", + "kimetsu-mrr-metric", + "mcp-schema-validation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G5PR2N167V5D1K95D9WP", + "id": "01M1X6NDB3TKFYA33H81T9DM5E", + "kind": "memory", + "score": 0.9996767044067384, + "summary": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` \u2014 a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases)." + }, + { + "expansion_handle": "memory:01M1X6G1V5C0B2FRN6AHK9JQJW", + "id": "01M1X6NDB31SDDGJHEJXHTKVGH", + "kind": "memory", + "score": 0.9682880640029908, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + }, + { + "expansion_handle": "memory:01M1X6G5QTTFD19FAJ8MZX4ZPC", + "id": "01M1X6NDB3RYPAVGQ9GEAC0YEE", + "kind": "memory", + "score": 0.8818408250808716, + "summary": "project:fact - [tags: kimetsu bench mrr recall metrics evaluation] kimetsu bench reports MRR (Mean Reciprocal Rank) and Recall@K. MRR is 1/rank_of_first_relevant_result, averaged across cases; it penalizes models that rank the correct answer 2nd or 3rd. Recall@K is the fraction of cases where at least one relevant answer appears in the top K." + }, + { + "expansion_handle": "memory:01M1X6G1Z4B34X6RM0V12B7VR5", + "id": "01M1X6NDB3XSCJJ80X7MM3BXBE", + "kind": "memory", + "score": 0.6527947187423706, + "summary": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array \u2014 omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 932.5379, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2424, + "mcp_result_bytes": 2603, + "wire_bytes": 2640, + "reported_used_tokens": 2603, + "working_set_bytes": 292290560, + "peak_working_set_bytes": 293208064 + }, + { + "query": "what does MRR mean and how do I interpret a 0.01 difference between combos?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 952.2467, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 292286464, + "peak_working_set_bytes": 293208064 + }, + { + "query": "SQLITE_BUSY keeps appearing even with WAL mode enabled", + "ranked": [ + "sqlite-busy-timeout-wal", + "sqlite-wal-network-drive" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCB5829A6ZF2C5FB9VZW", + "id": "01M1X6NF62K57YX3W4N6YV1MT0", + "kind": "memory", + "score": 0.9982662796974182, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + }, + { + "expansion_handle": "memory:01M1X6FCDYEQS880XHFGF7HHKN", + "id": "01M1X6NF62WK8QN5TYMCAT4DA7", + "kind": "memory", + "score": 0.7844027280807495, + "summary": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 962.1621, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1423, + "mcp_result_bytes": 1522, + "wire_bytes": 1559, + "reported_used_tokens": 1522, + "working_set_bytes": 292286464, + "peak_working_set_bytes": 293208064 + }, + { + "query": "my brain file got huge again right after I compacted it", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 900.6366999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 292286464, + "peak_working_set_bytes": 293208064 + }, + { + "query": "all my FTS queries stopped returning results after I changed the tokenizer config", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 929.5967, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 292286464, + "peak_working_set_bytes": 293208064 + }, + { + "query": "something is preventing the kimetsu binary from being replaced during update", + "ranked": [ + "kimetsu-daemon-lifecycle", + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G57BG9P4RZW0F69BHBK0", + "id": "01M1X6NHXEY6JMQ8HWSQ4Z8PCB", + "kind": "memory", + "score": 0.9678457975387572, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1X6FC93AQB8S92QXHHFQGQ9", + "id": "01M1X6NHXE6Y9A3KVRSSZ0YWTM", + "kind": "memory", + "score": 0.9395453929901124, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 0.6666666666666666, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 854.7651000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1657, + "mcp_result_bytes": 1756, + "wire_bytes": 1793, + "reported_used_tokens": 1756, + "working_set_bytes": 292286464, + "peak_working_set_bytes": 293208064 + }, + { + "query": "tool call results not appearing in the context \u2014 is the semantic floor too high?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 954.7316, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 292286464, + "peak_working_set_bytes": 293208064 + }, + { + "query": "CARGO_INCREMENTAL=0 in CI prevents a class of spurious compilation errors", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCS3JS2284QAPZ6KRSQR", + "id": "01M1X6NKP5HT39BKBFJJV0Q2ZG", + "kind": "memory", + "score": 0.7995238304138184, + "summary": "project:fact - [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 923.0652, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 877, + "mcp_result_bytes": 958, + "wire_bytes": 995, + "reported_used_tokens": 958, + "working_set_bytes": 292290560, + "peak_working_set_bytes": 293208064 + }, + { + "query": "how do I check whether my Cargo workspace respects the MSRV constraint?", + "ranked": [ + "cargo-msrv", + "cargo-dev-dep-leak", + "cargo-patch-section", + "cargo-target-dir-sharing" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCW109CC42VFPE46KZBB", + "id": "01M1X6NMKBQBBYJ1VC00NDWWHB", + "kind": "memory", + "score": 0.9921064376831056, + "summary": "project:fact - [tags: cargo rust msrv edition compatibility] Set `rust-version` in each `Cargo.toml` to declare the minimum supported Rust version (MSRV). Cargo enforces this with `--check`: `cargo check` fails if the toolchain is older than `rust-version`. Keep MSRV as old as your oldest supported deployment target." + }, + { + "expansion_handle": "memory:01M1X6FCQ2KKER2KYG7B2280XC", + "id": "01M1X6NMKBYHVNQYQSRZS1J8GW", + "kind": "memory", + "score": 0.887407660484314, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + }, + { + "expansion_handle": "memory:01M1X6FCV2P1VH9VAZGCJQXBTY", + "id": "01M1X6NMKBKN7F0AJ0G7V1X94K", + "kind": "memory", + "score": 0.7220955491065979, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace \u2014 including transitive deps \u2014 that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1X6FCR2A1FS53B5VE4XJM4D", + "id": "01M1X6NMKCTD7R7VR657F4H2F3", + "kind": "memory", + "score": 0.4095200598239898, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps \u2014 use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 967.4589, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2682, + "mcp_result_bytes": 2821, + "wire_bytes": 2858, + "reported_used_tokens": 2821, + "working_set_bytes": 292290560, + "peak_working_set_bytes": 293212160 + }, + { + "query": "rusqlite connection opened but ON DELETE CASCADE cascade never fires", + "ranked": [ + "sqlite-foreign-keys-default-off" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCH5BG9VNPYFKJ7JGXMQ", + "id": "01M1X6NNH31S35MAT93V29QEP4", + "kind": "memory", + "score": 0.9922945499420166, + "summary": "project:fact - [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting \u2014 every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 900.8687, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 735, + "mcp_result_bytes": 816, + "wire_bytes": 853, + "reported_used_tokens": 816, + "working_set_bytes": 292290560, + "peak_working_set_bytes": 293212160 + }, + { + "query": "I cannot connect to kimetsu-remote \u2014 something about TLS cert validation failed", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 894.8147, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 292290560, + "peak_working_set_bytes": 293212160 + }, + { + "query": "graceful shutdown fails because in-flight SQLite queries are still running when pool closes", + "ranked": [ + "tokio-shutdown-ordering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FXM81NH999732FH0NQF1", + "id": "01M1X6NQ9AMBA92W0QPKB43H50", + "kind": "memory", + "score": 0.9996342658996582, + "summary": "project:fact - [2026-09-07] [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries \u2014 the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 896.467, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 947, + "mcp_result_bytes": 1028, + "wire_bytes": 1065, + "reported_used_tokens": 1028, + "working_set_bytes": 292290560, + "peak_working_set_bytes": 293212160 + }, + { + "query": "kimetsu-remote response takes 8 seconds \u2014 which stage is slow?", + "ranked": [ + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G1X1XGE9MM05BANVE5DQ", + "id": "01M1X6NR58JGYGDMZQCZ1AM841", + "kind": "memory", + "score": 0.9876242876052856, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 948.9598, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 858, + "mcp_result_bytes": 939, + "wire_bytes": 976, + "reported_used_tokens": 939, + "working_set_bytes": 292294656, + "peak_working_set_bytes": 293212160 + }, + { + "query": "git reflog to rescue accidentally deleted branch", + "ranked": [ + "git-reflog-rescue" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FSF4P5WNAZZCJ0H37WSZ", + "id": "01M1X6NS32EJYCNKSEECV7H59J", + "kind": "memory", + "score": 0.998464822769165, + "summary": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone \u2014 they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only \u2014 remote reflog is not accessible via normal git commands." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 981.6856, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 761, + "mcp_result_bytes": 842, + "wire_bytes": 879, + "reported_used_tokens": 842, + "working_set_bytes": 292294656, + "peak_working_set_bytes": 293212160 + }, + { + "query": "git submodule --remote advances the pinned SHA unexpectedly", + "ranked": [ + "git-submodule-pinning", + "git-reflog-rescue", + "ci-secrets-masking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FSE7P3FNP04PMXVVBWR0", + "id": "01M1X6NT1MW9VGPJJAEJTYNMH8", + "kind": "memory", + "score": 0.9998551607131958, + "summary": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip \u2014 this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version." + }, + { + "expansion_handle": "memory:01M1X6FSF4P5WNAZZCJ0H37WSZ", + "id": "01M1X6NT1MY89XJJ73Z4AR33CJ", + "kind": "memory", + "score": 0.8857361078262329, + "summary": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone \u2014 they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only \u2014 remote reflog is not accessible via normal git commands." + }, + { + "expansion_handle": "memory:01M1X6G54DHRYVMZCDFFVTEDFC", + "id": "01M1X6NT1M9X5HK93XWEXCQPE6", + "kind": "memory", + "score": 0.8434544205665588, + "summary": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output \u2014 but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 880.4419999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1771, + "mcp_result_bytes": 1888, + "wire_bytes": 1925, + "reported_used_tokens": 1888, + "working_set_bytes": 292306944, + "peak_working_set_bytes": 293212160 + }, + { + "query": "axum SSE streaming drops the last event when client disconnects", + "ranked": [ + "http-streaming-bodies" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FXSE15217ZRDGWT76DK4", + "id": "01M1X6NTXDS95XNTKQG1G2YMNK", + "kind": "memory", + "score": 0.9926375150680542, + "summary": "project:fact - [2026-09-07] [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding \u2014 a chunk may split across frame boundaries." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 958.0794000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 859, + "mcp_result_bytes": 940, + "wire_bytes": 977, + "reported_used_tokens": 940, + "working_set_bytes": 292306944, + "peak_working_set_bytes": 293220352 + }, + { + "query": "how do I detect that I am running inside a git worktree vs the main checkout?", + "ranked": [ + "git-worktree-brain-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FSA7MZHYF9BQ9B8J0E8G", + "id": "01M1X6NVV5TCGM56MG7S7F94AC", + "kind": "memory", + "score": 0.9857924580574036, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root \u2014 if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 933.8018999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 881, + "mcp_result_bytes": 962, + "wire_bytes": 999, + "reported_used_tokens": 962, + "working_set_bytes": 292306944, + "peak_working_set_bytes": 293224448 + }, + { + "query": "ONNX Runtime intra-op threads causing CPU contention during parallel bench", + "ranked": [ + "onnx-ort-threading", + "tokio-blocking-in-async" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FS98N5KG1JWR5W9JGH3Q", + "id": "01M1X6NWRQ7MFTBS6JK3AAZH5W", + "kind": "memory", + "score": 0.9999210834503174, + "summary": "project:fact - [tags: onnx ort thread-pool parallelism cpu] ORT (ONNX Runtime) creates its own inter-op and intra-op thread pools. In a multi-process bench setup, each child inherits these pools and they compete for CPU cores. Set `SessionOptionsBuilder::with_intra_threads(1).with_inter_threads(1)` if you're running many parallel bench processes \u2014 this sacrifices per-inference throughput for lower contention." + }, + { + "expansion_handle": "memory:01M1X6FSG1SR2HKPVJWCKTW0XB", + "id": "01M1X6NWRQ6B4R9HVTN0WZYNVG", + "kind": "memory", + "score": 0.5390238761901855, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 875.7675, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1328, + "mcp_result_bytes": 1427, + "wire_bytes": 1464, + "reported_used_tokens": 1427, + "working_set_bytes": 292311040, + "peak_working_set_bytes": 293224448 + }, + { + "query": "what is the right way to supply AWS session token alongside access key and secret?", + "ranked": [ + "aws-credentials-chain" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G225TRC4EVAFGXT9RHRX", + "id": "01M1X6NXKQMJY9V3RTQSRNXBCE", + "kind": "memory", + "score": 0.9493365287780762, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 910.3599, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 895, + "mcp_result_bytes": 976, + "wire_bytes": 1013, + "reported_used_tokens": 976, + "working_set_bytes": 292311040, + "peak_working_set_bytes": 293224448 + } + ], + "id": "existing-development-100", + "dimension": "retrieval", + "tier": "hard", + "score": 0.8182539682539681, + "skipped": false, + "detail": "positive-recall@4=0.84 mrr=0.85 stale-hit=n/a resolution=n/a false-injection=0.538 (n=13) positive-n=197 negative-n=13 (210 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 0.8182539682539681, + 1 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 0.8182539682539681, + "n": 1, + "ci95": null + } + }, + "overall_index": 0.8182539682539681, + "scenario_weighted_index": 0.8182539682539681 +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-answerability/results/development/1-candidate.json b/docs/audits/2026-09-07-answerability/results/development/1-candidate.json new file mode 100644 index 0000000..76677a4 --- /dev/null +++ b/docs/audits/2026-09-07-answerability/results/development/1-candidate.json @@ -0,0 +1,6811 @@ +{ + "generated_at": "2026-09-07T05:53:41.7216833Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-retrieval\\development-100.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "test_env_lock inside with_user_brain_disabled deadlock", + "ranked": [ + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZHYGSE0FR8XFDPNJP6E", + "id": "01M1X6P4NJ41E6VYRRK2RW85WR", + "kind": "memory", + "score": 0.9999488592147828, + "summary": "project:fact - [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure \u2014 `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1041.7926, + "first_query": true, + "server_startup_ms": 77.6363, + "model_text_bytes": 796, + "mcp_result_bytes": 877, + "wire_bytes": 912, + "reported_used_tokens": 877, + "working_set_bytes": 227131392, + "peak_working_set_bytes": 248270848 + }, + { + "query": "why does my test hang after calling with_user_brain_disabled when I also lock test_env_lock?", + "ranked": [ + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZHYGSE0FR8XFDPNJP6E", + "id": "01M1X6P5BYS7DENZ8MWME9E285", + "kind": "memory", + "score": 0.9990190267562866, + "summary": "project:fact - [2026-09-07] [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure \u2014 `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 831.1445, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 808, + "mcp_result_bytes": 889, + "wire_bytes": 924, + "reported_used_tokens": 889, + "working_set_bytes": 229261312, + "peak_working_set_bytes": 248270848 + }, + { + "query": "ingest_repo_at_root brain_root files_root kimetsu remote", + "ranked": [ + "remote-ingest-split-roots", + "kimetsu-write-tools-gate", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZK0NCCC1P19NW0KM4XV", + "id": "01M1X6P663YP37CJB97YPSW5XX", + "kind": "memory", + "score": 0.999886393547058, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1X6P3Q18YAX03ZC3P1KS0CF", + "id": "01M1X6P663BKS4BNEZP7ABJ97P", + "kind": "memory", + "score": 0.8439717888832092, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level \u2014 disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1X6NZN0FVTV36T1B6KBFMKZ", + "id": "01M1X6P663HYJJGDC14VRNM2KA", + "kind": "memory", + "score": 0.8363722562789917, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 953.6565, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2726, + "mcp_result_bytes": 2891, + "wire_bytes": 2926, + "reported_used_tokens": 2891, + "working_set_bytes": 251510784, + "peak_working_set_bytes": 252420096 + }, + { + "query": "why does the remote server index the wrong directory when I run kimetsu brain ingest?", + "ranked": [ + "remote-ingest-split-roots", + "onnx-dim-mismatch" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZK0NCCC1P19NW0KM4XV", + "id": "01M1X6P73ZJ1Y1MY20QZRG6A3H", + "kind": "memory", + "score": 0.9836117625236512, + "summary": "project:fact - [2026-09-07] [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1X6P1TBDB4YP32AX87Z885E", + "id": "01M1X6P740BPHXQXY24YF6EJXK", + "kind": "memory", + "score": 0.3657674789428711, + "summary": "project:fact - [2026-09-07] [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results \u2014 the ANN index shape mismatch isn't always caught at runtime." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 932.8673, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1800, + "mcp_result_bytes": 1899, + "wire_bytes": 1934, + "reported_used_tokens": 1899, + "working_set_bytes": 257769472, + "peak_working_set_bytes": 258691072 + }, + { + "query": "kimetsu plugin install --remote mcp.json authorization bearer token", + "ranked": [ + "remote-mcp-host-wiring", + "mcp-stdout-protocol" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZN0FVTV36T1B6KBFMKZ", + "id": "01M1X6P81VFV1MEZKYP7BT4998", + "kind": "memory", + "score": 0.999605119228363, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + }, + { + "expansion_handle": "memory:01M1X6P2XXR71TXJXVQBRVTSF6", + "id": "01M1X6P81VFCGB7WP3B4DHDXTP", + "kind": "memory", + "score": 0.3375842869281769, + "summary": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 873.6983, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1472, + "mcp_result_bytes": 1619, + "wire_bytes": 1654, + "reported_used_tokens": 1619, + "working_set_bytes": 258248704, + "peak_working_set_bytes": 259166208 + }, + { + "query": "how do I wire a remote kimetsu brain into Claude Code without storing the token in the config file?", + "ranked": [ + "remote-mcp-host-wiring", + "mcp-tool-naming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZN0FVTV36T1B6KBFMKZ", + "id": "01M1X6P8WAJD6DB9664FQC1CFD", + "kind": "memory", + "score": 0.9963359832763672, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + }, + { + "expansion_handle": "memory:01M1X6P32468TZD6PXNN0J84CQ", + "id": "01M1X6P8WB246CNP8HFYCZMM9X", + "kind": "memory", + "score": 0.831425666809082, + "summary": "project:fact - [tags: mcp tool naming convention kimetsu] MCP tool names must be valid identifiers for all host agents. Claude Code restricts tool names to `[a-zA-Z0-9_-]` and max 64 chars. Use `snake_case` (kimetsu_brain_context, kimetsu_brain_record) \u2014 hyphen is technically allowed but some hosts reject it." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 850.6759000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1454, + "mcp_result_bytes": 1601, + "wire_bytes": 1636, + "reported_used_tokens": 1601, + "working_set_bytes": 258703360, + "peak_working_set_bytes": 259637248 + }, + { + "query": "cargo feature unification kimetsu-brain embeddings fastembed test failure", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-profile-override", + "clap-version-build-flavor" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZPZTKQEJANHCBJAH229", + "id": "01M1X6P9PYMCS2SDFRXR1MRX35", + "kind": "memory", + "score": 0.9996790885925292, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X6P0RPWS98KK91A3P03BXJ", + "id": "01M1X6P9PYDAQ2DDV6H9DHR1X6", + "kind": "memory", + "score": 0.9923595786094666, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1X6P01HC3V39QH0WT3B898R", + "id": "01M1X6P9PYN6WH69E4MHYSRZX8", + "kind": "memory", + "score": 0.585203230381012, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 832.843, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2387, + "mcp_result_bytes": 2524, + "wire_bytes": 2559, + "reported_used_tokens": 2524, + "working_set_bytes": 260403200, + "peak_working_set_bytes": 261324800 + }, + { + "query": "my integration tests pass in isolation but break when I run cargo test --workspace \u2014 embedder changed?", + "ranked": [ + "cargo-feature-unification-embeddings", + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZPZTKQEJANHCBJAH229", + "id": "01M1X6PAH1NXNPMYRMG5C3SPXY", + "kind": "memory", + "score": 0.9943140745162964, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X6P00PJD8FDG3QR2RS7QAK", + "id": "01M1X6PAH1NSABEJ3HGGVBBMHS", + "kind": "memory", + "score": 0.31398114562034607, + "summary": "project:fact - [2026-09-07] [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 895.2949, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1714, + "mcp_result_bytes": 1817, + "wire_bytes": 1852, + "reported_used_tokens": 1817, + "working_set_bytes": 261046272, + "peak_working_set_bytes": 261967872 + }, + { + "query": "build_anthropic_body bedrock-2023-05-31 InvokeModel blocking reqwest", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZRCKJJR7B15FGFKTS4W", + "id": "01M1X6PBCZHH954PAN0VFHQR4Y", + "kind": "memory", + "score": 0.9973788261413574, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X6NZXZMGW8CD9YMNHQ3ZTX", + "id": "01M1X6PBCZ8402R55XBF3DH6G7", + "kind": "memory", + "score": 0.6916899085044861, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 685.8140999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2193, + "mcp_result_bytes": 2320, + "wire_bytes": 2356, + "reported_used_tokens": 2320, + "working_set_bytes": 261664768, + "peak_working_set_bytes": 262578176 + }, + { + "query": "how do I add AWS Bedrock as a model provider in Kimetsu without pulling in the aws-sdk?", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-region-resolution", + "aws-credentials-chain", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZRCKJJR7B15FGFKTS4W", + "id": "01M1X6PC2KY9J0TA3YEF7K0N6T", + "kind": "memory", + "score": 0.9998898506164552, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X6P35QVP8D7M962ZGK168S", + "id": "01M1X6PC2KBKNCCJGFH06EGMXA", + "kind": "memory", + "score": 0.995676338672638, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X6P34CEH6KSKA057ZQRBCP", + "id": "01M1X6PC2K46WRJA4ZW1GCF4CR", + "kind": "memory", + "score": 0.987064242362976, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + }, + { + "expansion_handle": "memory:01M1X6NZXZMGW8CD9YMNHQ3ZTX", + "id": "01M1X6PC2KRQAXHW7D2RDWQW0C", + "kind": "memory", + "score": 0.9493880867958068, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 851.8489999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3455, + "mcp_result_bytes": 3618, + "wire_bytes": 3654, + "reported_used_tokens": 3618, + "working_set_bytes": 269824000, + "peak_working_set_bytes": 270741504 + }, + { + "query": "BridgeTarget enum seams plugin_install_inner plugin_status_inner resolve_setup_hosts", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZT95Z1V2RHNFPXWT53G", + "id": "01M1X6PCX3E4Y77C5S3M1CAXBG", + "kind": "memory", + "score": 0.9997583031654358, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 715.3876, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1060, + "mcp_result_bytes": 1141, + "wire_bytes": 1177, + "reported_used_tokens": 1141, + "working_set_bytes": 279863296, + "peak_working_set_bytes": 280776704 + }, + { + "query": "I added a new host to the bridge enum but cargo gives me compile errors in five different match arms \u2014 what did I miss?", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZT95Z1V2RHNFPXWT53G", + "id": "01M1X6PDKPTATYHR6BW747F46H", + "kind": "memory", + "score": 0.9977060556411744, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 927.0911, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1059, + "mcp_result_bytes": 1140, + "wire_bytes": 1176, + "reported_used_tokens": 1140, + "working_set_bytes": 280354816, + "peak_working_set_bytes": 281268224 + }, + { + "query": "Pi extension factory defineExtension agent_end session_shutdown kimetsu.ts", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZVC2YA8Q7SYT91Q3N9B", + "id": "01M1X6PEGQ700Z60T77S97PH83", + "kind": "memory", + "score": 0.9990354776382446, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 925.2431, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 804, + "mcp_result_bytes": 893, + "wire_bytes": 929, + "reported_used_tokens": 893, + "working_set_bytes": 280813568, + "peak_working_set_bytes": 281726976 + }, + { + "query": "how does Pi (earendil-works/pi) load plugins and what lifecycle hooks does it expose?", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZVC2YA8Q7SYT91Q3N9B", + "id": "01M1X6PFEEKASQ7ENMJHGC8M0J", + "kind": "memory", + "score": 0.9934834837913512, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 960.7774000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 803, + "mcp_result_bytes": 892, + "wire_bytes": 928, + "reported_used_tokens": 892, + "working_set_bytes": 280924160, + "peak_working_set_bytes": 281833472 + }, + { + "query": "aws-sigv4 SigningParams apply_to_request_http1x reqwest sign-http", + "ranked": [ + "aws-sigv4-bedrock-blocking", + "aws-presigned-urls", + "bedrock-kimetsu-provider", + "aws-credentials-chain" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZXZMGW8CD9YMNHQ3ZTX", + "id": "01M1X6PGBG3TD80KCXZEPFBDQ1", + "kind": "memory", + "score": 0.9995608925819396, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1X6P37Y0AZY1Q54BEN2E91E", + "id": "01M1X6PGBGYJZ134XRA9CNVVMD", + "kind": "memory", + "score": 0.984916627407074, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + }, + { + "expansion_handle": "memory:01M1X6NZRCKJJR7B15FGFKTS4W", + "id": "01M1X6PGBGP78PHX8GJ26EBMRF", + "kind": "memory", + "score": 0.983895778656006, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X6P34CEH6KSKA057ZQRBCP", + "id": "01M1X6PGBGD0QC0NEX87YY0AQW", + "kind": "memory", + "score": 0.8592692017555237, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 702.0019, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3507, + "mcp_result_bytes": 3670, + "wire_bytes": 3706, + "reported_used_tokens": 3670, + "working_set_bytes": 280940544, + "peak_working_set_bytes": 281841664 + }, + { + "query": "how do I sign a Bedrock InvokeModel request with aws-sigv4 in blocking Rust?", + "ranked": [ + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider", + "aws-region-resolution", + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZXZMGW8CD9YMNHQ3ZTX", + "id": "01M1X6PH1DCHNW5SP2ZDKPX2P1", + "kind": "memory", + "score": 0.9998323917388916, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1X6NZRCKJJR7B15FGFKTS4W", + "id": "01M1X6PH1DVAK3FRMVQT1D2XF3", + "kind": "memory", + "score": 0.9970844388008118, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X6P35QVP8D7M962ZGK168S", + "id": "01M1X6PH1D6MPMBJXVB85CDWMZ", + "kind": "memory", + "score": 0.9468621611595154, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X6P37Y0AZY1Q54BEN2E91E", + "id": "01M1X6PH1D2CWRZD80QWDWS9MW", + "kind": "memory", + "score": 0.9210098385810852, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 861.1256999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3434, + "mcp_result_bytes": 3597, + "wire_bytes": 3633, + "reported_used_tokens": 3597, + "working_set_bytes": 280940544, + "peak_working_set_bytes": 281858048 + }, + { + "query": "KIMETSU_RUNS_GC env opt-out TraceWriter create gc_old_runs caller", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZZW45G4D5CAC09350DM", + "id": "01M1X6PHWA60016YRDMPSK99BZ", + "kind": "memory", + "score": 0.999936580657959, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 812.7954, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 761, + "mcp_result_bytes": 842, + "wire_bytes": 878, + "reported_used_tokens": 842, + "working_set_bytes": 280948736, + "peak_working_set_bytes": 281862144 + }, + { + "query": "where should I put the KIMETSU_RUNS_GC=0 guard \u2014 inside the GC function or at the call site?", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZZW45G4D5CAC09350DM", + "id": "01M1X6PJNVNEGWY46DXD8KCNEK", + "kind": "memory", + "score": 0.9971211552619934, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 918.0858, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 762, + "mcp_result_bytes": 843, + "wire_bytes": 879, + "reported_used_tokens": 843, + "working_set_bytes": 281305088, + "peak_working_set_bytes": 282226688 + }, + { + "query": "git_init_boundary ProjectPaths::discover temp dir user brain isolation", + "ranked": [ + "init-project-git-boundary", + "git-worktree-brain-isolation", + "testing-temp-dirs-ci", + "kimetsu-memory-scopes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P00PJD8FDG3QR2RS7QAK", + "id": "01M1X6PKJQPJRK6MV35ARP6BG6", + "kind": "memory", + "score": 0.9997712969779968, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + }, + { + "expansion_handle": "memory:01M1X6P1YWTX8Y6NQRBF82KS3J", + "id": "01M1X6PKJQ5V5B9V7MNXWWAG08", + "kind": "memory", + "score": 0.9962491393089294, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root \u2014 if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + }, + { + "expansion_handle": "memory:01M1X6P2P2TSKGVDHTVF5Y34FZ", + "id": "01M1X6PKJQB4YC3BJCQNRV5MGV", + "kind": "memory", + "score": 0.9682154655456544, + "summary": "project:fact - [tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure." + }, + { + "expansion_handle": "memory:01M1X6P3KY5MF7R4RJB34EHA6R", + "id": "01M1X6PKJQV5ZXA9WHD931Z4HA", + "kind": "memory", + "score": 0.3057229816913605, + "summary": "project:fact - [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available \u2014 if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 792.6714999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2580, + "mcp_result_bytes": 2715, + "wire_bytes": 2751, + "reported_used_tokens": 2715, + "working_set_bytes": 281321472, + "peak_working_set_bytes": 282234880 + }, + { + "query": "my test calls init_project but it writes to the real ~/.kimetsu instead of the temp folder \u2014 why?", + "ranked": [ + "init-project-git-boundary", + "cargo-feature-unification-embeddings", + "testing-fixture-drift", + "tokio-runtime-in-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P00PJD8FDG3QR2RS7QAK", + "id": "01M1X6PMBC0K6CN0R7H1QFZ8D4", + "kind": "memory", + "score": 0.9995088577270508, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + }, + { + "expansion_handle": "memory:01M1X6NZPZTKQEJANHCBJAH229", + "id": "01M1X6PMBC7EFE0FD71088WGXW", + "kind": "memory", + "score": 0.7287850975990295, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X6P2X1Q84DSXWGJTFEX3G2", + "id": "01M1X6PMBCXSXPYGNDYZASRYDB", + "kind": "memory", + "score": 0.6596062183380127, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + }, + { + "expansion_handle": "memory:01M1X6P25SW9R97848FW6SCJ4H", + "id": "01M1X6PMBC2923TGG91H67FSTV", + "kind": "memory", + "score": 0.3297702968120575, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 916.1302000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2833, + "mcp_result_bytes": 2980, + "wire_bytes": 3016, + "reported_used_tokens": 2980, + "working_set_bytes": 281718784, + "peak_working_set_bytes": 282632192 + }, + { + "query": "clap command version KIMETSU_VERSION_DISPLAY cfg feature embeddings", + "ranked": [ + "clap-version-build-flavor", + "cargo-feature-unification-embeddings" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P01HC3V39QH0WT3B898R", + "id": "01M1X6PN8672Y3N6J01XHX8NW8", + "kind": "memory", + "score": 0.9996613264083862, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + }, + { + "expansion_handle": "memory:01M1X6NZPZTKQEJANHCBJAH229", + "id": "01M1X6PN86DBXNEKV9WWV5Y30Z", + "kind": "memory", + "score": 0.3973360061645508, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 710.3815, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1922, + "mcp_result_bytes": 2041, + "wire_bytes": 2077, + "reported_used_tokens": 2041, + "working_set_bytes": 282136576, + "peak_working_set_bytes": 283041792 + }, + { + "query": "how do I show the build flavor (lean vs embeddings) in the kimetsu --version output?", + "ranked": [ + "clap-version-build-flavor", + "cargo-feature-unification-embeddings", + "onnx-quantization-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P01HC3V39QH0WT3B898R", + "id": "01M1X6PNYDCD59RJTG9AW0VH47", + "kind": "memory", + "score": 0.9978312849998474, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + }, + { + "expansion_handle": "memory:01M1X6NZPZTKQEJANHCBJAH229", + "id": "01M1X6PNYD44EW9SX4JWCVG2QK", + "kind": "memory", + "score": 0.8926984667778015, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X6P1PMS5XEE1J3MS9A2WGM", + "id": "01M1X6PNYDC1WCG4Y0YBKP2QSA", + "kind": "memory", + "score": 0.8877003192901611, + "summary": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals \u2014 cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 928.7767, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2672, + "mcp_result_bytes": 2809, + "wire_bytes": 2845, + "reported_used_tokens": 2809, + "working_set_bytes": 282488832, + "peak_working_set_bytes": 283406336 + }, + { + "query": "Harbor pyiceberg os.getcwd stale WSL2 DrvFs worker-result subprocess re-exec", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P02RMXB3NSW1B6TJT5GQ", + "id": "01M1X6PPVYZ163D4H54254V5QH", + "kind": "memory", + "score": 0.9998155236244202, + "summary": "project:fact - [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 969.961, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1026, + "mcp_result_bytes": 1107, + "wire_bytes": 1143, + "reported_used_tokens": 1107, + "working_set_bytes": 283000832, + "peak_working_set_bytes": 283910144 + }, + { + "query": "why does my kbench sweep crash after the first trial with 'result.json missing' on WSL2?", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P02RMXB3NSW1B6TJT5GQ", + "id": "01M1X6PQSXJK2ASMX3MBSKAEAS", + "kind": "memory", + "score": 0.998451828956604, + "summary": "project:fact - [2026-09-07] [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 953.1028, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1038, + "mcp_result_bytes": 1119, + "wire_bytes": 1155, + "reported_used_tokens": 1119, + "working_set_bytes": 283144192, + "peak_working_set_bytes": 284065792 + }, + { + "query": "rusqlite VACUUM transaction WAL checkpoint wal_checkpoint TRUNCATE", + "ranked": [ + "sqlite-vacuum-wal-checkpoint", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P040MT6RZK6WACMMJ8MN", + "id": "01M1X6PRQCQTGANBN9D663EQXB", + "kind": "memory", + "score": 0.9996871948242188, + "summary": "project:fact - [tags: rust sqlite vacuum rusqlite windows] When implementing SQLite VACUUM in rusqlite: VACUUM cannot run inside a transaction. rusqlite's Connection does not hold an implicit transaction, so `conn.execute_batch(\"VACUUM;\")` works directly. After VACUUM, run `PRAGMA wal_checkpoint(TRUNCATE);` before measuring file size \u2014 on Windows the WAL file can hold significant space that isn't reflected in the main db file until the checkpoint runs." + }, + { + "expansion_handle": "memory:01M1X6P0A6JDJBPYZTYEXJX1EZ", + "id": "01M1X6PRQCGJC08GE24HB1P9Z0", + "kind": "memory", + "score": 0.5274003744125366, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 712.1927999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1507, + "mcp_result_bytes": 1610, + "wire_bytes": 1646, + "reported_used_tokens": 1610, + "working_set_bytes": 283160576, + "peak_working_set_bytes": 284065792 + }, + { + "query": "my SQLite VACUUM reports the file shrank but the disk usage stayed the same \u2014 Windows WAL?", + "ranked": [ + "sqlite-vacuum-wal-checkpoint", + "sqlite-wal-network-drive" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P040MT6RZK6WACMMJ8MN", + "id": "01M1X6PSGFDE0CVC4NQ8YP23QG", + "kind": "memory", + "score": 0.9155893921852112, + "summary": "project:fact - [tags: rust sqlite vacuum rusqlite windows] When implementing SQLite VACUUM in rusqlite: VACUUM cannot run inside a transaction. rusqlite's Connection does not hold an implicit transaction, so `conn.execute_batch(\"VACUUM;\")` works directly. After VACUUM, run `PRAGMA wal_checkpoint(TRUNCATE);` before measuring file size \u2014 on Windows the WAL file can hold significant space that isn't reflected in the main db file until the checkpoint runs." + }, + { + "expansion_handle": "memory:01M1X6P0CJBPZG72ZW2NCZ8ST6", + "id": "01M1X6PSGGE3363HPD1G3C8EEZ", + "kind": "memory", + "score": 0.902395486831665, + "summary": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1076.2671, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1357, + "mcp_result_bytes": 1460, + "wire_bytes": 1496, + "reported_used_tokens": 1460, + "working_set_bytes": 283283456, + "peak_working_set_bytes": 284200960 + }, + { + "query": "add_memory import dedup seen_ids snapshot pre-existing active memory IDs", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P04SMZJAVHMFMNBRSWBG", + "id": "01M1X6PTFMGJ7X2B75Z8RDRQMA", + "kind": "memory", + "score": 0.9999133348464966, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount \u2014 both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 815.9419999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 966, + "mcp_result_bytes": 1047, + "wire_bytes": 1083, + "reported_used_tokens": 1047, + "working_set_bytes": 283312128, + "peak_working_set_bytes": 284221440 + }, + { + "query": "brain import re-imports the same JSON file but the deduplication counter is wrong \u2014 why?", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P04SMZJAVHMFMNBRSWBG", + "id": "01M1X6PV98CM60CJFQ561PATXQ", + "kind": "memory", + "score": 0.9254016876220704, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount \u2014 both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 875.0482000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 965, + "mcp_result_bytes": 1046, + "wire_bytes": 1082, + "reported_used_tokens": 1046, + "working_set_bytes": 283320320, + "peak_working_set_bytes": 284237824 + }, + { + "query": "toml::from_str Value parse document unexpected content str.parse", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P05X9753C2E7K2XGW40Y", + "id": "01M1X6PW48CA2HAWB0F0W2M0RH", + "kind": "memory", + "score": 0.9991866946220398, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 751.6471, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 734, + "mcp_result_bytes": 815, + "wire_bytes": 851, + "reported_used_tokens": 815, + "working_set_bytes": 283340800, + "peak_working_set_bytes": 284254208 + }, + { + "query": "how do I parse a TOML configuration file into a toml::Value in toml 0.9?", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P05X9753C2E7K2XGW40Y", + "id": "01M1X6PWW1614ZFEWA9AN3YM3F", + "kind": "memory", + "score": 0.9992641806602478, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 892.8201, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 733, + "mcp_result_bytes": 814, + "wire_bytes": 850, + "reported_used_tokens": 814, + "working_set_bytes": 283348992, + "peak_working_set_bytes": 284262400 + }, + { + "query": "CIM CreationDate DMTF WMI ps etimes started_at assess_mcp_skew", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P06VVC3AVKTJXX8VPZMF", + "id": "01M1X6PXQZ1Q88PMQPF3HMQW3B", + "kind": "memory", + "score": 0.9957948923110962, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 697.8135, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 924, + "mcp_result_bytes": 1013, + "wire_bytes": 1049, + "reported_used_tokens": 1013, + "working_set_bytes": 283353088, + "peak_working_set_bytes": 284262400 + }, + { + "query": "how do I read a process start time on both Windows and Linux in pure Rust?", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P06VVC3AVKTJXX8VPZMF", + "id": "01M1X6PYEEYKVC2Q5QVZ5VH9FS", + "kind": "memory", + "score": 0.99687659740448, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 924.393, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 921, + "mcp_result_bytes": 1010, + "wire_bytes": 1046, + "reported_used_tokens": 1010, + "working_set_bytes": 283389952, + "peak_working_set_bytes": 284315648 + }, + { + "query": "processes_locking_target decide_preflight_action BufRead Write update.rs", + "ranked": [ + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P08364QKMJ37XXNKFWJS", + "id": "01M1X6PZACDB0W4NF6WWR917XD", + "kind": "memory", + "score": 0.9995336532592772, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 767.0137, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1133, + "mcp_result_bytes": 1214, + "wire_bytes": 1250, + "reported_used_tokens": 1214, + "working_set_bytes": 283435008, + "peak_working_set_bytes": 284352512 + }, + { + "query": "how should I reuse the existing process enumerator in the update preflight check to avoid a second PowerShell query?", + "ranked": [ + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P08364QKMJ37XXNKFWJS", + "id": "01M1X6Q0334Z3H809NJBJWMXZ3", + "kind": "memory", + "score": 0.9973384737968444, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 972.6356000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1132, + "mcp_result_bytes": 1213, + "wire_bytes": 1249, + "reported_used_tokens": 1213, + "working_set_bytes": 283439104, + "peak_working_set_bytes": 284356608 + }, + { + "query": "cfg_attr windows allow dead_code parse_unix_ps cross-platform tests", + "ranked": [ + "cfg-cross-platform-dead-code", + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P098A9WT8YP7XVFD6RQZ", + "id": "01M1X6Q10SWSVJH9W65RS6C4BX", + "kind": "memory", + "score": 0.9999476671218872, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + }, + { + "expansion_handle": "memory:01M1X6P06VVC3AVKTJXX8VPZMF", + "id": "01M1X6Q10S5S2JA7Z9SV34NKSR", + "kind": "memory", + "score": 0.9764312505722046, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 720.8574, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1518, + "mcp_result_bytes": 1625, + "wire_bytes": 1661, + "reported_used_tokens": 1625, + "working_set_bytes": 283607040, + "peak_working_set_bytes": 284520448 + }, + { + "query": "how do I keep a function that is only called on Unix from triggering dead_code warnings on Windows?", + "ranked": [ + "cfg-cross-platform-dead-code" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P098A9WT8YP7XVFD6RQZ", + "id": "01M1X6Q1QHQJ5S37FQ1Y9PTM70", + "kind": "memory", + "score": 0.9988092184066772, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 883.4047, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 939, + "reported_used_tokens": 903, + "working_set_bytes": 284106752, + "peak_working_set_bytes": 285020160 + }, + { + "query": "deadlocking a Rust mutex in integration tests", + "ranked": [ + "mutex-deadlock-user-brain-disabled", + "testing-serial-vs-parallel", + "kimetsu-query-stemming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZHYGSE0FR8XFDPNJP6E", + "id": "01M1X6Q2K5AY47N9S8WKXA5P63", + "kind": "memory", + "score": 0.9997490048408508, + "summary": "project:fact - [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure \u2014 `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + }, + { + "expansion_handle": "memory:01M1X6P2SBPNK9PEDW9QRXPYZJ", + "id": "01M1X6Q2K6GKQREJY2DA28C050", + "kind": "memory", + "score": 0.9057517647743224, + "summary": "project:fact - [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`)." + }, + { + "expansion_handle": "memory:01M1X6P3TM4S5HK5WSJCSCQQHF", + "id": "01M1X6Q2K6W5QM0V04TZVP0VS3", + "kind": "memory", + "score": 0.4889622032642365, + "summary": "project:fact - [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 851.7991000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1930, + "mcp_result_bytes": 2063, + "wire_bytes": 2099, + "reported_used_tokens": 2063, + "working_set_bytes": 284176384, + "peak_working_set_bytes": 285089792 + }, + { + "query": "benchmarking retrieval quality across embedders", + "ranked": [ + "kimetsu-bench-remote-embedder-singleton", + "onnx-quantization-drift", + "cargo-feature-unification-embeddings" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3WKNTTE5DK6PSVCEB7R", + "id": "01M1X6Q3DHRES1F0E1N1ET36JG", + "kind": "memory", + "score": 0.988014280796051, + "summary": "project:fact - [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval." + }, + { + "expansion_handle": "memory:01M1X6P1PMS5XEE1J3MS9A2WGM", + "id": "01M1X6Q3DH04DDZYNGN2DWKYSG", + "kind": "memory", + "score": 0.985597550868988, + "summary": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals \u2014 cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + }, + { + "expansion_handle": "memory:01M1X6NZPZTKQEJANHCBJAH229", + "id": "01M1X6Q3DJN048Q5ZAESG44W60", + "kind": "memory", + "score": 0.5341982841491699, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 714.6012000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2537, + "mcp_result_bytes": 2658, + "wire_bytes": 2694, + "reported_used_tokens": 2658, + "working_set_bytes": 284479488, + "peak_working_set_bytes": 285388800 + }, + { + "query": "process memory working set RSS peak measurement Windows", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 897.4518, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 285048832, + "peak_working_set_bytes": 285941760 + }, + { + "query": "cloning a git repository server-side into a managed checkout", + "ranked": [ + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZK0NCCC1P19NW0KM4XV", + "id": "01M1X6Q5084AVSEZT7WWZR1Z65", + "kind": "memory", + "score": 0.9466677904129028, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 753.7226, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1261, + "mcp_result_bytes": 1342, + "wire_bytes": 1378, + "reported_used_tokens": 1342, + "working_set_bytes": 285335552, + "peak_working_set_bytes": 286240768 + }, + { + "query": "SigV4 signing HTTP requests in Rust", + "ranked": [ + "aws-presigned-urls", + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P37Y0AZY1Q54BEN2E91E", + "id": "01M1X6Q5R5HX43J2NRDB9ES28H", + "kind": "memory", + "score": 0.9992632269859314, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + }, + { + "expansion_handle": "memory:01M1X6NZXZMGW8CD9YMNHQ3ZTX", + "id": "01M1X6Q5R5DJ3V3ANBSJA6HR5S", + "kind": "memory", + "score": 0.9991399049758912, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1X6NZRCKJJR7B15FGFKTS4W", + "id": "01M1X6Q5R5H3BPT0PSCE8WDMAH", + "kind": "memory", + "score": 0.9803794622421264, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 0.5, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 888.06, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2840, + "mcp_result_bytes": 2985, + "wire_bytes": 3021, + "reported_used_tokens": 2985, + "working_set_bytes": 285712384, + "peak_working_set_bytes": 286609408 + }, + { + "query": "cargo test --workspace feature flag changes broke my unit tests", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-dev-dep-leak", + "ci-flaky-quarantine" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZPZTKQEJANHCBJAH229", + "id": "01M1X6Q6MG35D9MDVVHPMMQ0WZ", + "kind": "memory", + "score": 0.997899889945984, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X6P0NMHGDXYV47S0SSW2XA", + "id": "01M1X6Q6MGQ0XAMJEBH0E3CJ65", + "kind": "memory", + "score": 0.9901249408721924, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + }, + { + "expansion_handle": "memory:01M1X6P3H0TD9NJ1PFEC45G42A", + "id": "01M1X6Q6MGTGCZDXKYM25RGF4H", + "kind": "memory", + "score": 0.835382342338562, + "summary": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal \u2014 a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 778.2959000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2383, + "mcp_result_bytes": 2504, + "wire_bytes": 2540, + "reported_used_tokens": 2504, + "working_set_bytes": 285741056, + "peak_working_set_bytes": 286654464 + }, + { + "query": "how do I make pasta carbonara?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 770.7376, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 285802496, + "peak_working_set_bytes": 286715904 + }, + { + "query": "what is the offside rule in football?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 955.6245, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 285822976, + "peak_working_set_bytes": 286732288 + }, + { + "query": "best way to train for a half marathon", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 970.3988, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 286015488, + "peak_working_set_bytes": 286937088 + }, + { + "query": "my test passes when I run it alone but fails under cargo test --workspace", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZPZTKQEJANHCBJAH229", + "id": "01M1X6QA0DP5QJJNW1WW166JZQ", + "kind": "memory", + "score": 0.9907942414283752, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X6P0NMHGDXYV47S0SSW2XA", + "id": "01M1X6QA0DCCX41CZ6JHSZ211Z", + "kind": "memory", + "score": 0.986136794090271, + "summary": "project:fact - [2026-09-07] [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 921.6551, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1863, + "mcp_result_bytes": 1966, + "wire_bytes": 2002, + "reported_used_tokens": 1966, + "working_set_bytes": 286593024, + "peak_working_set_bytes": 287510528 + }, + { + "query": "all the project tests started hanging forever after I added my new test", + "ranked": [ + "cargo-feature-unification-embeddings", + "tokio-runtime-in-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZPZTKQEJANHCBJAH229", + "id": "01M1X6QAXCJBYJGEDARG6W3GXE", + "kind": "memory", + "score": 0.774284839630127, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X6P25SW9R97848FW6SCJ4H", + "id": "01M1X6QAXCR30T5MA5A2MQ31YC", + "kind": "memory", + "score": 0.33030807971954346, + "summary": "project:fact - [2026-09-07] [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 964.7365, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1763, + "mcp_result_bytes": 1874, + "wire_bytes": 1910, + "reported_used_tokens": 1874, + "working_set_bytes": 286629888, + "peak_working_set_bytes": 287539200 + }, + { + "query": "my integration test silently wrote memories into my real home brain instead of the temp workspace", + "ranked": [ + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P00PJD8FDG3QR2RS7QAK", + "id": "01M1X6QBVCPPRECMCGWFWGEK73", + "kind": "memory", + "score": 0.9922831654548644, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 876.4055, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 780, + "mcp_result_bytes": 861, + "wire_bytes": 897, + "reported_used_tokens": 861, + "working_set_bytes": 286683136, + "peak_working_set_bytes": 287600640 + }, + { + "query": "where should the env-var opt-out check live for a cleanup feature triggered from a hot code path", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZZW45G4D5CAC09350DM", + "id": "01M1X6QCPH2FGN3B2NB5X6E45M", + "kind": "memory", + "score": 0.9952055215835572, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 931.1973, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 761, + "mcp_result_bytes": 842, + "wire_bytes": 878, + "reported_used_tokens": 842, + "working_set_bytes": 286699520, + "peak_working_set_bytes": 287617024 + }, + { + "query": "the brain database file stays huge on Windows even after deleting most rows", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 937.8179, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 286703616, + "peak_working_set_bytes": 287625216 + }, + { + "query": "re-importing the same exported memories file counts them as new instead of deduplicated", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P04SMZJAVHMFMNBRSWBG", + "id": "01M1X6QEH2A1JHRNSCBZRS5V8H", + "kind": "memory", + "score": 0.9878425598144532, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount \u2014 both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 899.7212, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 965, + "mcp_result_bytes": 1046, + "wire_bytes": 1082, + "reported_used_tokens": 1046, + "working_set_bytes": 286711808, + "peak_working_set_bytes": 287629312 + }, + { + "query": "a helper function only called on Unix at runtime fails the dead-code lint on the Windows build", + "ranked": [ + "cfg-cross-platform-dead-code", + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P098A9WT8YP7XVFD6RQZ", + "id": "01M1X6QFD685VJJJ0DCHTREY0Y", + "kind": "memory", + "score": 0.9971064925193788, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + }, + { + "expansion_handle": "memory:01M1X6P08364QKMJ37XXNKFWJS", + "id": "01M1X6QFD6EJ9E0YQMCAPWSJQY", + "kind": "memory", + "score": 0.427912950515747, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 874.7296, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1728, + "mcp_result_bytes": 1827, + "wire_bytes": 1863, + "reported_used_tokens": 1827, + "working_set_bytes": 286851072, + "peak_working_set_bytes": 287772672 + }, + { + "query": "the second Terminal-Bench trial always crashes even though the first one passes", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P02RMXB3NSW1B6TJT5GQ", + "id": "01M1X6QG8H7Y1WF3M7TTXSKQYK", + "kind": "memory", + "score": 0.9963042736053468, + "summary": "project:fact - [2026-09-07] [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 912.2396, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1038, + "mcp_result_bytes": 1119, + "wire_bytes": 1155, + "reported_used_tokens": 1119, + "working_set_bytes": 286859264, + "peak_working_set_bytes": 287772672 + }, + { + "query": "how does doctor tell a running MCP server process is older than the kimetsu binary on disk", + "ranked": [ + "kimetsu-daemon-lifecycle", + "process-start-time-cross-platform", + "mcp-env-propagation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3J1H7C1NPH0T2G7FWCC", + "id": "01M1X6QH54YGPZ9RKASYBJDWKW", + "kind": "memory", + "score": 0.9985345602035522, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1X6P06VVC3AVKTJXX8VPZMF", + "id": "01M1X6QH54MEX6W80GW2WGCJ4B", + "kind": "memory", + "score": 0.9438157677650452, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + }, + { + "expansion_handle": "memory:01M1X6P302K2RV969H5CEVQWE9", + "id": "01M1X6QH54YTF26WDGC444GAGF", + "kind": "memory", + "score": 0.33611738681793213, + "summary": "project:fact - [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment \u2014 changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 0.5, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 913.6339999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1936, + "mcp_result_bytes": 2061, + "wire_bytes": 2097, + "reported_used_tokens": 2061, + "working_set_bytes": 286867456, + "peak_working_set_bytes": 287784960 + }, + { + "query": "the self-update preflight needs the list of running kimetsu processes without re-running the OS query", + "ranked": [ + "windows-update-process-locking", + "kimetsu-daemon-lifecycle" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P08364QKMJ37XXNKFWJS", + "id": "01M1X6QJ1Z85QNYBJ9FARJ7ZQ1", + "kind": "memory", + "score": 0.9972410202026368, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + }, + { + "expansion_handle": "memory:01M1X6P3J1H7C1NPH0T2G7FWCC", + "id": "01M1X6QJ1ZM3XF98QRDY10B1MK", + "kind": "memory", + "score": 0.8902595043182373, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 886.4286, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1658, + "mcp_result_bytes": 1757, + "wire_bytes": 1793, + "reported_used_tokens": 1757, + "working_set_bytes": 286998528, + "peak_working_set_bytes": 287899648 + }, + { + "query": "parsing the WMI DMTF CreationDate timestamp into epoch seconds without extra crates", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P06VVC3AVKTJXX8VPZMF", + "id": "01M1X6QJXE2ZAJSV08FEVJJZK9", + "kind": "memory", + "score": 0.9258026480674744, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 978.8485999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 924, + "mcp_result_bytes": 1013, + "wire_bytes": 1049, + "reported_used_tokens": 1013, + "working_set_bytes": 286998528, + "peak_working_set_bytes": 287911936 + }, + { + "query": "calling Bedrock InvokeModel from blocking reqwest without the aws sdk", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking", + "aws-region-resolution", + "aws-retry-throttling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZRCKJJR7B15FGFKTS4W", + "id": "01M1X6QKW1QXGDFAQQKM7MSZT0", + "kind": "memory", + "score": 0.9991798996925354, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X6NZXZMGW8CD9YMNHQ3ZTX", + "id": "01M1X6QKW1CNRKN4MJ7ETVTS4K", + "kind": "memory", + "score": 0.999082326889038, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1X6P35QVP8D7M962ZGK168S", + "id": "01M1X6QKW1RNPKHD09ZBJGF8D0", + "kind": "memory", + "score": 0.8391201496124268, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X6P36TQNTE0Z7YTV5BM0YV", + "id": "01M1X6QKW18W0XNXE7V5S1H7PX", + "kind": "memory", + "score": 0.4906356632709503, + "summary": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with \u00b125% jitter." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 939.9555, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3330, + "mcp_result_bytes": 3509, + "wire_bytes": 3545, + "reported_used_tokens": 3509, + "working_set_bytes": 287002624, + "peak_working_set_bytes": 287920128 + }, + { + "query": "how do I rotate the encryption key protecting the kimetsu brain database", + "ranked": [ + "kimetsu-eval-fixture-shape" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3XM0BN6GY22JB0THAHB", + "id": "01M1X6QMSC4C8S8712N6YJZGKB", + "kind": "memory", + "score": 0.8046634197235107, + "summary": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` \u2014 a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases)." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 909.9994, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 817, + "mcp_result_bytes": 942, + "wire_bytes": 978, + "reported_used_tokens": 942, + "working_set_bytes": 287260672, + "peak_working_set_bytes": 288169984 + }, + { + "query": "which tokio runtime worker-thread settings does the kimetsu MCP server use", + "ranked": [ + "tokio-blocking-in-async", + "tokio-runtime-in-tests", + "mcp-stdout-protocol" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P24PG6KXJZQB4K5G8MKX", + "id": "01M1X6QNP6B0HTFG3HM08PF75S", + "kind": "memory", + "score": 0.9973159432411194, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + }, + { + "expansion_handle": "memory:01M1X6P25SW9R97848FW6SCJ4H", + "id": "01M1X6QNP684HZGWS62F1BW59M", + "kind": "memory", + "score": 0.8583173155784607, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + }, + { + "expansion_handle": "memory:01M1X6P2XXR71TXJXVQBRVTSF6", + "id": "01M1X6QNP68PA77TP0AFB9QKRT", + "kind": "memory", + "score": 0.8141786456108093, + "summary": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 951.3683, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1847, + "mcp_result_bytes": 1972, + "wire_bytes": 2008, + "reported_used_tokens": 1972, + "working_set_bytes": 287969280, + "peak_working_set_bytes": 288882688 + }, + { + "query": "how does kimetsu sync memories between two machines over the network", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 874.6456000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288296960, + "peak_working_set_bytes": 289210368 + }, + { + "query": "recovering a corrupted usearch ANN index after a power loss", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 816.6948000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288579584, + "peak_working_set_bytes": 289492992 + }, + { + "query": "what postgres schema should I use to store kimetsu memories", + "ranked": [ + "kimetsu-memory-scopes", + "testing-fixture-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3KY5MF7R4RJB34EHA6R", + "id": "01M1X6QR8KC84KS2DMV0K2JAS3", + "kind": "memory", + "score": 0.9890244603157043, + "summary": "project:fact - [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available \u2014 if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope." + }, + { + "expansion_handle": "memory:01M1X6P2X1Q84DSXWGJTFEX3G2", + "id": "01M1X6QR8K60PGMKRGKAN6N1JC", + "kind": "memory", + "score": 0.8922504782676697, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 897.8376999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1389, + "mcp_result_bytes": 1488, + "wire_bytes": 1524, + "reported_used_tokens": 1488, + "working_set_bytes": 288731136, + "peak_working_set_bytes": 289632256 + }, + { + "query": "the whole CI job just froze forever with no failure output after my latest test PR", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 893.2662, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288804864, + "peak_working_set_bytes": 289722368 + }, + { + "query": "running the test suite left junk state in my home directory", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 936.0711, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 289206272, + "peak_working_set_bytes": 290123776 + }, + { + "query": "I deleted a bunch of old rows but the file on disk is still the same size", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 893.7782, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 289599488, + "peak_working_set_bytes": 290516992 + }, + { + "query": "adding one new crate quietly changed how the whole workspace builds", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-lockfile-drift", + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZPZTKQEJANHCBJAH229", + "id": "01M1X6QVSPVW4P30PY0T569ZH2", + "kind": "memory", + "score": 0.9941080808639526, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X6P0KJTZ6A9CZ4Q0SNH3G8", + "id": "01M1X6QVSPC1B6W5HYKME18V7R", + "kind": "memory", + "score": 0.9717232584953308, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this \u2014 it errors on any lockfile diff." + }, + { + "expansion_handle": "memory:01M1X6P0NMHGDXYV47S0SSW2XA", + "id": "01M1X6QVSPEWH5ZS5PT8ETQQFT", + "kind": "memory", + "score": 0.9183088541030884, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 913.8888, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2374, + "mcp_result_bytes": 2495, + "wire_bytes": 2531, + "reported_used_tokens": 2495, + "working_set_bytes": 289632256, + "peak_working_set_bytes": 290553856 + }, + { + "query": "we cannot pull an async runtime into the agent just to talk to AWS", + "ranked": [ + "tokio-blocking-in-async", + "tokio-runtime-in-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P24PG6KXJZQB4K5G8MKX", + "id": "01M1X6QWPM9P3ZKMBQDPJ0Y411", + "kind": "memory", + "score": 0.7520647644996643, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + }, + { + "expansion_handle": "memory:01M1X6P25SW9R97848FW6SCJ4H", + "id": "01M1X6QWPMJ9TGA01SZW8Q738Q", + "kind": "memory", + "score": 0.7233642935752869, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 968.2756999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1369, + "mcp_result_bytes": 1476, + "wire_bytes": 1512, + "reported_used_tokens": 1476, + "working_set_bytes": 289648640, + "peak_working_set_bytes": 290557952 + }, + { + "query": "users should be able to tell which build variant they installed from the version output", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 955.7077, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 289669120, + "peak_working_set_bytes": 290586624 + }, + { + "query": "what gotchas should I expect writing process-inspection code that works on both Windows and Unix?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 882.8116, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 289714176, + "peak_working_set_bytes": 290635776 + }, + { + "query": "why might tests behave differently on my machine than in the full CI run?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 871.5808000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 289759232, + "peak_working_set_bytes": 290676736 + }, + { + "query": "what do I need to know before wiring kimetsu into a brand new host agent?", + "ranked": [ + "bridge-target-enum-seams", + "kimetsu-daemon-lifecycle", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZT95Z1V2RHNFPXWT53G", + "id": "01M1X6R09EZCHYMRT28N89PGG9", + "kind": "memory", + "score": 0.9741999506950378, + "summary": "project:fact - [2026-09-07] [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + }, + { + "expansion_handle": "memory:01M1X6P3J1H7C1NPH0T2G7FWCC", + "id": "01M1X6R09E4GSGTZQ1BTMPCMK2", + "kind": "memory", + "score": 0.9637662768363952, + "summary": "project:fact - [2026-09-07] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1X6NZN0FVTV36T1B6KBFMKZ", + "id": "01M1X6R09E57Q5RXKBBMHNHMVE", + "kind": "memory", + "score": 0.4149944484233856, + "summary": "project:fact - [2026-09-07] [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 0.6666666666666666, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 936.9214000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2390, + "mcp_result_bytes": 2555, + "wire_bytes": 2591, + "reported_used_tokens": 2555, + "working_set_bytes": 289759232, + "peak_working_set_bytes": 290676736 + }, + { + "query": "tell me everything relevant to running kimetsu against AWS", + "ranked": [ + "kimetsu-mrr-metric", + "aws-credentials-chain", + "cargo-feature-unification-embeddings", + "kimetsu-eval-fixture-shape" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3YTF6X09YDCSHS6P0JQ", + "id": "01M1X6R16KQ5G5DB8R6NSHZ8SB", + "kind": "memory", + "score": 0.984548270702362, + "summary": "project:fact - [tags: kimetsu bench mrr recall metrics evaluation] kimetsu bench reports MRR (Mean Reciprocal Rank) and Recall@K. MRR is 1/rank_of_first_relevant_result, averaged across cases; it penalizes models that rank the correct answer 2nd or 3rd. Recall@K is the fraction of cases where at least one relevant answer appears in the top K." + }, + { + "expansion_handle": "memory:01M1X6P34CEH6KSKA057ZQRBCP", + "id": "01M1X6R16KDZT7CJCW02J07ZV5", + "kind": "memory", + "score": 0.9737622141838074, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + }, + { + "expansion_handle": "memory:01M1X6NZPZTKQEJANHCBJAH229", + "id": "01M1X6R16KWBJYRMKN4XW7DVRM", + "kind": "memory", + "score": 0.9726329445838928, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X6P3XM0BN6GY22JB0THAHB", + "id": "01M1X6R16KKJBJ0EF02YFF3AKW", + "kind": "memory", + "score": 0.9641559720039368, + "summary": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` \u2014 a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases)." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 917.4006, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2883, + "mcp_result_bytes": 3066, + "wire_bytes": 3102, + "reported_used_tokens": 3066, + "working_set_bytes": 289787904, + "peak_working_set_bytes": 290697216 + }, + { + "query": "ingesting a cloned repo when the brain lives under a different root", + "ranked": [ + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZK0NCCC1P19NW0KM4XV", + "id": "01M1X6R23CA82V7TEESB3YNRGD", + "kind": "memory", + "score": 0.9995300769805908, + "summary": "project:fact - [2026-09-07] [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 876.21, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1274, + "mcp_result_bytes": 1355, + "wire_bytes": 1391, + "reported_used_tokens": 1355, + "working_set_bytes": 289792000, + "peak_working_set_bytes": 290701312 + }, + { + "query": "streamable-http transport entry for openclaw.json with a bearer token", + "ranked": [ + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZN0FVTV36T1B6KBFMKZ", + "id": "01M1X6R2YTFRK2H9B5YCFV3ME2", + "kind": "memory", + "score": 0.9921918511390686, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 890.6237, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 996, + "mcp_result_bytes": 1125, + "wire_bytes": 1161, + "reported_used_tokens": 1125, + "working_set_bytes": 290045952, + "peak_working_set_bytes": 290963456 + }, + { + "query": "serializing ingests with a tokio mutex to avoid checkout races", + "ranked": [ + "remote-ingest-split-roots", + "testing-serial-vs-parallel", + "tokio-select-cancellation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZK0NCCC1P19NW0KM4XV", + "id": "01M1X6R3TMVVTCK2G60AKREWGR", + "kind": "memory", + "score": 0.9795480966567992, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1X6P2SBPNK9PEDW9QRXPYZJ", + "id": "01M1X6R3TM5VWNMHX7JM7J9DSR", + "kind": "memory", + "score": 0.9425267577171326, + "summary": "project:fact - [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`)." + }, + { + "expansion_handle": "memory:01M1X6P270RXS50A66Q2YBWW4H", + "id": "01M1X6R3TMCGY67R1DW2RNWWB1", + "kind": "memory", + "score": 0.5619664192199707, + "summary": "project:fact - [tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 924.7050999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2376, + "mcp_result_bytes": 2493, + "wire_bytes": 2529, + "reported_used_tokens": 2493, + "working_set_bytes": 290058240, + "peak_working_set_bytes": 290979840 + }, + { + "query": "percent-encoding the colon in the bedrock model id for the invoke URL", + "ranked": [ + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZRCKJJR7B15FGFKTS4W", + "id": "01M1X6R4QQQAY0B8NRFCY395YE", + "kind": "memory", + "score": 0.8341025710105896, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 875.3322999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1204, + "mcp_result_bytes": 1293, + "wire_bytes": 1329, + "reported_used_tokens": 1293, + "working_set_bytes": 290062336, + "peak_working_set_bytes": 290979840 + }, + { + "query": "deduplicating re-imported memories against pre-existing ids", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P04SMZJAVHMFMNBRSWBG", + "id": "01M1X6R5K5S3JP5NTEV4Y59C7E", + "kind": "memory", + "score": 0.9991393089294434, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount \u2014 both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 947.8259, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 966, + "mcp_result_bytes": 1047, + "wire_bytes": 1083, + "reported_used_tokens": 1047, + "working_set_bytes": 290062336, + "peak_working_set_bytes": 290979840 + }, + { + "query": "parsing DMTF datetimes", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P06VVC3AVKTJXX8VPZMF", + "id": "01M1X6R6H9D420GXTDXZ4ASYJ7", + "kind": "memory", + "score": 0.9934942126274108, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 709.198, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 924, + "mcp_result_bytes": 1013, + "wire_bytes": 1049, + "reported_used_tokens": 1013, + "working_set_bytes": 290082816, + "peak_working_set_bytes": 290979840 + }, + { + "query": "how should install derive a stable identifier from the git remote URL?", + "ranked": [ + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZN0FVTV36T1B6KBFMKZ", + "id": "01M1X6R76P46Q5D5GQRKM86MG3", + "kind": "memory", + "score": 0.98285174369812, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 888.1916, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 995, + "mcp_result_bytes": 1124, + "wire_bytes": 1160, + "reported_used_tokens": 1124, + "working_set_bytes": 290082816, + "peak_working_set_bytes": 290996224 + }, + { + "query": "the secret token must not end up written into the host config file", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 940.5013, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 290086912, + "peak_working_set_bytes": 291004416 + }, + { + "query": "keep the cleanup logic unit-testable without touching environment variables", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 905.3257, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 290086912, + "peak_working_set_bytes": 291004416 + }, + { + "query": "how do we stop the server from cloning arbitrary repos clients request?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 876.8886, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 290086912, + "peak_working_set_bytes": 291004416 + }, + { + "query": "make sure a wrong guess about a host plugin API never breaks that host", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZVC2YA8Q7SYT91Q3N9B", + "id": "01M1X6RAR2RFYWVNWWE4VJQKFZ", + "kind": "memory", + "score": 0.928434193134308, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 774.3385000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 803, + "mcp_result_bytes": 892, + "wire_bytes": 928, + "reported_used_tokens": 892, + "working_set_bytes": 290140160, + "peak_working_set_bytes": 291061760 + }, + { + "query": "which wire-format trick lets us reuse the existing Anthropic request builder for AWS?", + "ranked": [ + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZRCKJJR7B15FGFKTS4W", + "id": "01M1X6RBFWVY4MRM1V10T2A5MV", + "kind": "memory", + "score": 0.9748817682266236, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 946.9912, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1203, + "mcp_result_bytes": 1292, + "wire_bytes": 1328, + "reported_used_tokens": 1292, + "working_set_bytes": 290242560, + "peak_working_set_bytes": 291160064 + }, + { + "query": "the self-update froze because something was still holding the executable", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1191.3511999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 290361344, + "peak_working_set_bytes": 291270656 + }, + { + "query": "our notes about the extension API turned out wrong once we read the actual repo", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 990.2658, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 290414592, + "peak_working_set_bytes": 291323904 + }, + { + "query": "half the benchmark trials die right after the first one finishes", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 898.4513, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 290414592, + "peak_working_set_bytes": 291332096 + }, + { + "query": "I need this parser visible to tests on every OS even though only one OS calls it", + "ranked": [ + "cfg-cross-platform-dead-code" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P098A9WT8YP7XVFD6RQZ", + "id": "01M1X6RFDT459ZETAP0KPQ5PDY", + "kind": "memory", + "score": 0.36490198969841, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 886.3231000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 939, + "reported_used_tokens": 903, + "working_set_bytes": 290463744, + "peak_working_set_bytes": 291368960 + }, + { + "query": "the config file content refuses to parse even though the TOML looks valid", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P05X9753C2E7K2XGW40Y", + "id": "01M1X6RG9G6RWKDW994V43ERXA", + "kind": "memory", + "score": 0.6614054441452026, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 918.6942, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 733, + "mcp_result_bytes": 814, + "wire_bytes": 850, + "reported_used_tokens": 814, + "working_set_bytes": 290496512, + "peak_working_set_bytes": 291414016 + }, + { + "query": "the remote server must refresh its checkout before answering file queries", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 927.0274, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 290504704, + "peak_working_set_bytes": 291422208 + }, + { + "query": "tests must not climb to a parent git repository when resolving project paths", + "ranked": [ + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P00PJD8FDG3QR2RS7QAK", + "id": "01M1X6RJ36TMCZHZXJVNK3NHST", + "kind": "memory", + "score": 0.9839988350868224, + "summary": "project:fact - [2026-09-07] [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 922.201, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 794, + "mcp_result_bytes": 875, + "wire_bytes": 911, + "reported_used_tokens": 875, + "working_set_bytes": 290508800, + "peak_working_set_bytes": 291422208 + }, + { + "query": "how do I test request signing deterministically when timestamps change every run?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 916.098, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 290652160, + "peak_working_set_bytes": 291565568 + }, + { + "query": "adding a new variant to the host target enum - which places will I forget to update?", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZT95Z1V2RHNFPXWT53G", + "id": "01M1X6RKWSSN92D4QCNNPV6A82", + "kind": "memory", + "score": 0.885076105594635, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 947.0763, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1058, + "mcp_result_bytes": 1139, + "wire_bytes": 1175, + "reported_used_tokens": 1139, + "working_set_bytes": 290689024, + "peak_working_set_bytes": 291602432 + }, + { + "query": "how do I enable GPU acceleration for kimetsu embedding inference", + "ranked": [ + "mcp-tool-timeouts", + "kimetsu-proactive-hooks" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P2YZ4YRDGDVJV5XMKCP1", + "id": "01M1X6RMTG3K4PTK1A2E035A77", + "kind": "memory", + "score": 0.9826309084892272, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + }, + { + "expansion_handle": "memory:01M1X6P3P27FMRCY89DFW7MFKS", + "id": "01M1X6RMTGQHA4QRZ5NYCF4JPB", + "kind": "memory", + "score": 0.8807981610298157, + "summary": "project:fact - [tags: kimetsu proactive hooks context injection] kimetsu's proactive context injection runs before each agent turn (pre-turn hook) and injects relevant memories into the system prompt prefix. The hook invocation adds latency to the first token: embedding inference + vector search + reranking + context formatting. On a cold start, this can be 1-3 seconds." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 919.2233, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1377, + "mcp_result_bytes": 1476, + "wire_bytes": 1512, + "reported_used_tokens": 1476, + "working_set_bytes": 290693120, + "peak_working_set_bytes": 291602432 + }, + { + "query": "how do I throttle kimetsu API spend per month", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 876.0017, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 290717696, + "peak_working_set_bytes": 291635200 + }, + { + "query": "can the kimetsu brain database be stored in S3 instead of on disk", + "ranked": [ + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P37Y0AZY1Q54BEN2E91E", + "id": "01M1X6RPK32C71E7KF2QNWHPX9", + "kind": "memory", + "score": 0.38596054911613464, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 859.2295, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 875, + "mcp_result_bytes": 956, + "wire_bytes": 992, + "reported_used_tokens": 956, + "working_set_bytes": 290766848, + "peak_working_set_bytes": 291684352 + }, + { + "query": "how do I plug a custom tokenizer into the FTS index", + "ranked": [ + "sqlite-fts5-tokenizer" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0DKH32KYKD57N5WWCRX", + "id": "01M1X6RQDBQAH13FD1AT5GBSW0", + "kind": "memory", + "score": 0.9691632390022278, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 902.3303000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 671, + "mcp_result_bytes": 756, + "wire_bytes": 792, + "reported_used_tokens": 756, + "working_set_bytes": 290770944, + "peak_working_set_bytes": 291684352 + }, + { + "query": "what should I check when kimetsu behaves differently on Windows than on Linux?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 896.6828999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 290914304, + "peak_working_set_bytes": 291827712 + }, + { + "query": "what are the moving parts of the kimetsu remote deployment story?", + "ranked": [ + "kimetsu-write-tools-gate", + "ci-secrets-masking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3Q18YAX03ZC3P1KS0CF", + "id": "01M1X6RS5JA94A08523CDXEA76", + "kind": "memory", + "score": 0.9729357361793518, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level \u2014 disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1X6P3F12CWS8FE5S50H7TYH", + "id": "01M1X6RS5KMA7A7FAXAAX7TSHC", + "kind": "memory", + "score": 0.8412115573883057, + "summary": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output \u2014 but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 884.3661000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1410, + "mcp_result_bytes": 1509, + "wire_bytes": 1546, + "reported_used_tokens": 1509, + "working_set_bytes": 290996224, + "peak_working_set_bytes": 291913728 + }, + { + "query": "which lessons cover guarding behavior behind environment variables?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1033.2767000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 291028992, + "peak_working_set_bytes": 291934208 + }, + { + "query": "SQLite BUSY error under concurrent writes", + "ranked": [ + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0A6JDJBPYZTYEXJX1EZ", + "id": "01M1X6RV26BQJ1MYTQMV28B0S7", + "kind": "memory", + "score": 0.9978362917900084, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 799.0188, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 898, + "mcp_result_bytes": 979, + "wire_bytes": 1016, + "reported_used_tokens": 979, + "working_set_bytes": 291110912, + "peak_working_set_bytes": 292003840 + }, + { + "query": "SQLite WAL mode breaks when the database is on a network share", + "ranked": [ + "sqlite-wal-network-drive", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0CJBPZG72ZW2NCZ8ST6", + "id": "01M1X6RVTJJ04CSBSWAPMTV4NM", + "kind": "memory", + "score": 0.999302864074707, + "summary": "project:fact - [2026-09-07] [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + }, + { + "expansion_handle": "memory:01M1X6P0A6JDJBPYZTYEXJX1EZ", + "id": "01M1X6RVTJ2S9C5VHRAGNSQ14E", + "kind": "memory", + "score": 0.9966553449630736, + "summary": "project:fact - [2026-09-07] [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 926.5964, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1448, + "mcp_result_bytes": 1547, + "wire_bytes": 1584, + "reported_used_tokens": 1547, + "working_set_bytes": 291115008, + "peak_working_set_bytes": 292028416 + }, + { + "query": "my SQLite WAL database causes SQLITE_IOERR_LOCK on a mapped drive", + "ranked": [ + "sqlite-wal-network-drive" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0CJBPZG72ZW2NCZ8ST6", + "id": "01M1X6RWQSAEM9PHMBKHY906KK", + "kind": "memory", + "score": 0.99892657995224, + "summary": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 961.2746, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 748, + "mcp_result_bytes": 829, + "wire_bytes": 866, + "reported_used_tokens": 829, + "working_set_bytes": 291188736, + "peak_working_set_bytes": 292098048 + }, + { + "query": "FTS5 tokenizer configuration for Rust identifiers with underscores", + "ranked": [ + "sqlite-fts5-tokenizer", + "kimetsu-query-stemming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0DKH32KYKD57N5WWCRX", + "id": "01M1X6RXNM47BJ4ZA8VE513MXQ", + "kind": "memory", + "score": 0.998104453086853, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + }, + { + "expansion_handle": "memory:01M1X6P3TM4S5HK5WSJCSCQQHF", + "id": "01M1X6RXNNMQG9ZSJ22RG6HR2H", + "kind": "memory", + "score": 0.7023860812187195, + "summary": "project:fact - [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 892.2713, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1212, + "mcp_result_bytes": 1331, + "wire_bytes": 1368, + "reported_used_tokens": 1331, + "working_set_bytes": 291196928, + "peak_working_set_bytes": 292106240 + }, + { + "query": "I switched the FTS5 tokenizer but search stopped returning results", + "ranked": [ + "sqlite-fts5-tokenizer" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0DKH32KYKD57N5WWCRX", + "id": "01M1X6RYHXYF8ZA3YBKA5XA4V3", + "kind": "memory", + "score": 0.8194089531898499, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 962.6889, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 670, + "mcp_result_bytes": 755, + "wire_bytes": 792, + "reported_used_tokens": 755, + "working_set_bytes": 291217408, + "peak_working_set_bytes": 292126720 + }, + { + "query": "optimal SQLite page size for storing embedding vectors", + "ranked": [ + "sqlite-page-size", + "onnx-dim-mismatch", + "onnx-cosine-vs-dot" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0EN1F2M5R0H76S72S7T", + "id": "01M1X6RZFJAD704NXT455EBR43", + "kind": "memory", + "score": 0.9990121126174928, + "summary": "project:fact - [tags: sqlite page_size performance rusqlite] SQLite's default page_size is 4096 bytes. For a write-heavy brain database with large BLOB payloads (embedding vectors), raising page_size to 16384 reduces fragmentation and improves sequential scan throughput. `PRAGMA page_size = 16384;` must be set BEFORE the first table is created \u2014 changing it on an existing database requires a VACUUM afterward to rebuild all pages." + }, + { + "expansion_handle": "memory:01M1X6P1TBDB4YP32AX87Z885E", + "id": "01M1X6RZFJRA85DJ0WXG90V6CA", + "kind": "memory", + "score": 0.9881643056869508, + "summary": "project:fact - [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results \u2014 the ANN index shape mismatch isn't always caught at runtime." + }, + { + "expansion_handle": "memory:01M1X6P1SDTFE6G9SQ1JWY55C5", + "id": "01M1X6RZFJGAWSQJ1J0KKEFPNQ", + "kind": "memory", + "score": 0.9425415992736816, + "summary": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing \u2014 double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 847.7025, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1860, + "mcp_result_bytes": 1977, + "wire_bytes": 2014, + "reported_used_tokens": 1977, + "working_set_bytes": 291250176, + "peak_working_set_bytes": 292155392 + }, + { + "query": "ON DELETE CASCADE in SQLite does nothing \u2014 foreign keys not enforced", + "ranked": [ + "sqlite-foreign-keys-default-off" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0FMEPPEMPPJJ176K5CE", + "id": "01M1X6S0A5Z079XXSQWCJDQR8P", + "kind": "memory", + "score": 0.9996858835220336, + "summary": "project:fact - [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting \u2014 every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 934.907, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 736, + "mcp_result_bytes": 817, + "wire_bytes": 854, + "reported_used_tokens": 817, + "working_set_bytes": 291258368, + "peak_working_set_bytes": 292167680 + }, + { + "query": "indexing a JSON metadata column in SQLite without a schema migration", + "ranked": [ + "sqlite-json1-extract", + "testing-fixture-drift", + "onnx-dim-mismatch", + "sqlite-partial-index" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0GGPA6E0F0TZYGKXDT9", + "id": "01M1X6S17B0WRPHZJP2H8JPNV1", + "kind": "memory", + "score": 0.9955366849899292, + "summary": "project:fact - [tags: sqlite json1 json_extract rusqlite] SQLite's json1 extension (built in since 3.38.0) lets you index and query JSONB columns with `json_extract(col, '$.field')`. To create a partial index over a JSON field: `CREATE INDEX idx ON memories (json_extract(metadata, '$.scope')) WHERE json_extract(metadata, '$.scope') IS NOT NULL;`. Use `json_each` for array fields." + }, + { + "expansion_handle": "memory:01M1X6P2X1Q84DSXWGJTFEX3G2", + "id": "01M1X6S17BG6KTDNQ00FN6K67Y", + "kind": "memory", + "score": 0.8227390646934509, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + }, + { + "expansion_handle": "memory:01M1X6P1TBDB4YP32AX87Z885E", + "id": "01M1X6S17BPS5649MT8R5PXT2V", + "kind": "memory", + "score": 0.38374292850494385, + "summary": "project:fact - [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results \u2014 the ANN index shape mismatch isn't always caught at runtime." + }, + { + "expansion_handle": "memory:01M1X6P0JKGMF00QG34TX11P9N", + "id": "01M1X6S17BJSYFX5KEGEG1YRE1", + "kind": "memory", + "score": 0.3276048004627228, + "summary": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query \u2014 the planner uses the partial index only when the WHERE clause matches." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 879.3746, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2381, + "mcp_result_bytes": 2516, + "wire_bytes": 2553, + "reported_used_tokens": 2516, + "working_set_bytes": 291332096, + "peak_working_set_bytes": 292241408 + }, + { + "query": "prepare() vs prepare_cached() in rusqlite hot insert loop", + "ranked": [ + "sqlite-prepared-stmt-cache" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0HK639ECPAWR156DDPZ", + "id": "01M1X6S22XGCKDAYRM3WCPCRY0", + "kind": "memory", + "score": 0.9993672966957092, + "summary": "project:fact - [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 882.1865, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 689, + "mcp_result_bytes": 770, + "wire_bytes": 807, + "reported_used_tokens": 770, + "working_set_bytes": 291356672, + "peak_working_set_bytes": 292261888 + }, + { + "query": "speed up bulk memory ingest by caching SQL statements", + "ranked": [ + "sqlite-prepared-stmt-cache" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0HK639ECPAWR156DDPZ", + "id": "01M1X6S2YNGS3HSJQTS6HBW89Q", + "kind": "memory", + "score": 0.9823396801948548, + "summary": "project:fact - [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 896.6949, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 688, + "mcp_result_bytes": 769, + "wire_bytes": 806, + "reported_used_tokens": 769, + "working_set_bytes": 291409920, + "peak_working_set_bytes": 292315136 + }, + { + "query": "partial index on deleted_at IS NULL for faster active memory queries", + "ranked": [ + "sqlite-partial-index" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0JKGMF00QG34TX11P9N", + "id": "01M1X6S3TGK6GV321CNK17YPWK", + "kind": "memory", + "score": 0.9988954067230223, + "summary": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query \u2014 the planner uses the partial index only when the WHERE clause matches." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 900.2224, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 794, + "mcp_result_bytes": 875, + "wire_bytes": 912, + "reported_used_tokens": 875, + "working_set_bytes": 291786752, + "peak_working_set_bytes": 292687872 + }, + { + "query": "the brain query is slow because it scans all rows including soft-deleted ones", + "ranked": [ + "sqlite-partial-index" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0JKGMF00QG34TX11P9N", + "id": "01M1X6S4PNTHK9Q8C8HWGM8H4A", + "kind": "memory", + "score": 0.5760471224784851, + "summary": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query \u2014 the planner uses the partial index only when the WHERE clause matches." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 943.4644, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 793, + "mcp_result_bytes": 874, + "wire_bytes": 911, + "reported_used_tokens": 874, + "working_set_bytes": 291803136, + "peak_working_set_bytes": 292720640 + }, + { + "query": "Cargo.lock changed unexpectedly after adding a new workspace crate", + "ranked": [ + "cargo-lockfile-drift", + "cargo-feature-unification-embeddings", + "cargo-target-dir-sharing", + "cargo-patch-section" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0KJTZ6A9CZ4Q0SNH3G8", + "id": "01M1X6S5M3QFMWHG68CMCBXPD8", + "kind": "memory", + "score": 0.9991374015808104, + "summary": "project:fact - [2026-09-07] [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this \u2014 it errors on any lockfile diff." + }, + { + "expansion_handle": "memory:01M1X6NZPZTKQEJANHCBJAH229", + "id": "01M1X6S5M38W5G9YPGECNV1Q36", + "kind": "memory", + "score": 0.9968542456626892, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X6P0PQ9PDB9P815QJTY464", + "id": "01M1X6S5M30H983CAXM3RZVVA5", + "kind": "memory", + "score": 0.9829630851745604, + "summary": "project:fact - [2026-09-07] [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps \u2014 use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + }, + { + "expansion_handle": "memory:01M1X6P0SRM17TYNHXSXEWSP31", + "id": "01M1X6S5M3GEQ45GR3M17K65EW", + "kind": "memory", + "score": 0.9262890815734864, + "summary": "project:fact - [2026-09-07] [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace \u2014 including transitive deps \u2014 that depend on `my-crate`. Remove the patch before publishing." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 800.9239, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3010, + "mcp_result_bytes": 3153, + "wire_bytes": 3190, + "reported_used_tokens": 3153, + "working_set_bytes": 291868672, + "peak_working_set_bytes": 292786176 + }, + { + "query": "how do I prevent CI from accepting a modified lockfile silently?", + "ranked": [ + "cargo-lockfile-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0KJTZ6A9CZ4Q0SNH3G8", + "id": "01M1X6S6DE4VMWCZHM5DJRAFT2", + "kind": "memory", + "score": 0.9125379323959352, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this \u2014 it errors on any lockfile diff." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 951.2239999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 765, + "mcp_result_bytes": 846, + "wire_bytes": 883, + "reported_used_tokens": 846, + "working_set_bytes": 292192256, + "peak_working_set_bytes": 293109760 + }, + { + "query": "build.rs reruns on every incremental build even when nothing changed", + "ranked": [ + "cargo-build-script-rerun" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0MFFWR9VFS6MSBGG2HS", + "id": "01M1X6S7BDKKSTWFV4PW7WEQYW", + "kind": "memory", + "score": 0.9996689558029176, + "summary": "project:fact - [2026-09-07] [tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 950.8377, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 698, + "mcp_result_bytes": 779, + "wire_bytes": 816, + "reported_used_tokens": 779, + "working_set_bytes": 292401152, + "peak_working_set_bytes": 293318656 + }, + { + "query": "incremental cargo build is slow because build script runs every time", + "ranked": [ + "cargo-build-script-rerun" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0MFFWR9VFS6MSBGG2HS", + "id": "01M1X6S88QK3GYJN4GN4EBZVXA", + "kind": "memory", + "score": 0.9978280663490297, + "summary": "project:fact - [tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 889.9504000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 685, + "mcp_result_bytes": 766, + "wire_bytes": 803, + "reported_used_tokens": 766, + "working_set_bytes": 292438016, + "peak_working_set_bytes": 293351424 + }, + { + "query": "a dev-dependency is activating an embeddings feature in my production build", + "ranked": [ + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0NMHGDXYV47S0SSW2XA", + "id": "01M1X6S94P0G091PW51J4GXE2C", + "kind": "memory", + "score": 0.9944193959236144, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 908.3688, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 931, + "mcp_result_bytes": 1012, + "wire_bytes": 1049, + "reported_used_tokens": 1012, + "working_set_bytes": 292507648, + "peak_working_set_bytes": 293421056 + }, + { + "query": "how do I prevent a test-only feature from bleeding into the non-test compilation?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 878.5384, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 292536320, + "peak_working_set_bytes": 293449728 + }, + { + "query": "linker errors in target/ caused by antivirus holding the exe file", + "ranked": [ + "windows-file-locking-av", + "cargo-target-dir-sharing" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1FEZNYXN0DQN55611JR", + "id": "01M1X6SAWKR4KYQXKT1AFD7DX6", + "kind": "memory", + "score": 0.9997633099555968, + "summary": "project:fact - [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + }, + { + "expansion_handle": "memory:01M1X6P0PQ9PDB9P815QJTY464", + "id": "01M1X6SAWK78JSP78BZ7TSBA5G", + "kind": "memory", + "score": 0.7463976740837097, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps \u2014 use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 903.6118, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1523, + "mcp_result_bytes": 1622, + "wire_bytes": 1659, + "reported_used_tokens": 1622, + "working_set_bytes": 292642816, + "peak_working_set_bytes": 293552128 + }, + { + "query": "Access is denied (os error 5) when linking on Windows \u2014 how do I fix this?", + "ranked": [ + "windows-file-locking-av" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1FEZNYXN0DQN55611JR", + "id": "01M1X6SBRPZ0H97FMKMXGYWRGG", + "kind": "memory", + "score": 0.9977193474769592, + "summary": "project:fact - [2026-09-07] [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 916.1673000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 769, + "mcp_result_bytes": 850, + "wire_bytes": 887, + "reported_used_tokens": 850, + "working_set_bytes": 292700160, + "peak_working_set_bytes": 293609472 + }, + { + "query": "incremental build broke with a type mismatch after switching branches", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0QQQS1JZYGKXH9ZX9RW", + "id": "01M1X6SCNDPP15MN74NQH7QR15", + "kind": "memory", + "score": 0.7971777319908142, + "summary": "project:fact - [2026-09-07] [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 914.3299000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 890, + "mcp_result_bytes": 971, + "wire_bytes": 1008, + "reported_used_tokens": 971, + "working_set_bytes": 292745216, + "peak_working_set_bytes": 293658624 + }, + { + "query": "cargo reports a type error that references a type not in the codebase", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0QQQS1JZYGKXH9ZX9RW", + "id": "01M1X6SDJ0MV9AN968QKR4RH5S", + "kind": "memory", + "score": 0.7925198078155518, + "summary": "project:fact - [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 855.4866, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 877, + "mcp_result_bytes": 958, + "wire_bytes": 995, + "reported_used_tokens": 958, + "working_set_bytes": 292835328, + "peak_working_set_bytes": 293756928 + }, + { + "query": "compile fastembed at O2 in debug builds to avoid slow embedding inference", + "ranked": [ + "cargo-profile-override", + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0RPWS98KK91A3P03BXJ", + "id": "01M1X6SED3A9KY44N7AZWH1CXC", + "kind": "memory", + "score": 0.9932281374931335, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1X6P2YZ4YRDGDVJV5XMKCP1", + "id": "01M1X6SED3493V0PMG5A9P8TM7", + "kind": "memory", + "score": 0.987656831741333, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 933.6553, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1322, + "mcp_result_bytes": 1421, + "wire_bytes": 1458, + "reported_used_tokens": 1421, + "working_set_bytes": 292950016, + "peak_working_set_bytes": 293867520 + }, + { + "query": "override compilation profile for a single crate in a Cargo workspace", + "ranked": [ + "cargo-patch-section", + "cargo-profile-override", + "cargo-target-dir-sharing", + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0SRM17TYNHXSXEWSP31", + "id": "01M1X6SFAG6DPHKJXAJ03TYVFX", + "kind": "memory", + "score": 0.9984123706817628, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace \u2014 including transitive deps \u2014 that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1X6P0RPWS98KK91A3P03BXJ", + "id": "01M1X6SFAG69KA5H9MT7Z389ST", + "kind": "memory", + "score": 0.9979992508888244, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1X6P0PQ9PDB9P815QJTY464", + "id": "01M1X6SFAGV7ACW2MCDA9FVV3V", + "kind": "memory", + "score": 0.9956549406051636, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps \u2014 use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + }, + { + "expansion_handle": "memory:01M1X6P0NMHGDXYV47S0SSW2XA", + "id": "01M1X6SFAG9VMZGEBR2E90BPHW", + "kind": "memory", + "score": 0.9820712208747864, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 0.5, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 974.1467, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2682, + "mcp_result_bytes": 2821, + "wire_bytes": 2858, + "reported_used_tokens": 2821, + "working_set_bytes": 293011456, + "peak_working_set_bytes": 293933056 + }, + { + "query": "[patch.crates-io] workspace dependency override", + "ranked": [ + "cargo-patch-section", + "cargo-lockfile-drift", + "cargo-dev-dep-leak", + "cargo-target-dir-sharing" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0SRM17TYNHXSXEWSP31", + "id": "01M1X6SGDRT4S592AZAW14SBFW", + "kind": "memory", + "score": 0.9999405145645142, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace \u2014 including transitive deps \u2014 that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1X6P0KJTZ6A9CZ4Q0SNH3G8", + "id": "01M1X6SGDRMH0HEGAPZEQT0RKG", + "kind": "memory", + "score": 0.9975811243057252, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this \u2014 it errors on any lockfile diff." + }, + { + "expansion_handle": "memory:01M1X6P0NMHGDXYV47S0SSW2XA", + "id": "01M1X6SGDRQAC75SSE6KH0TW0G", + "kind": "memory", + "score": 0.994149684906006, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + }, + { + "expansion_handle": "memory:01M1X6P0PQ9PDB9P815QJTY464", + "id": "01M1X6SGDRF36W37VV4FJ4AYC8", + "kind": "memory", + "score": 0.7471600770950317, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps \u2014 use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 888.5977, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2755, + "mcp_result_bytes": 2894, + "wire_bytes": 2931, + "reported_used_tokens": 2894, + "working_set_bytes": 293036032, + "peak_working_set_bytes": 293941248 + }, + { + "query": "pin minimum supported Rust version in Cargo.toml", + "ranked": [ + "cargo-msrv" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0TQV281HN337V782S05", + "id": "01M1X6SH48BC5T890CFYZFRTQT", + "kind": "memory", + "score": 0.999652862548828, + "summary": "project:fact - [tags: cargo rust msrv edition compatibility] Set `rust-version` in each `Cargo.toml` to declare the minimum supported Rust version (MSRV). Cargo enforces this with `--check`: `cargo check` fails if the toolchain is older than `rust-version`. Keep MSRV as old as your oldest supported deployment target." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 815.8598999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 693, + "mcp_result_bytes": 774, + "wire_bytes": 811, + "reported_used_tokens": 774, + "working_set_bytes": 293048320, + "peak_working_set_bytes": 293965824 + }, + { + "query": "Windows path over 260 characters causes OS error 3 during Cargo build", + "ranked": [ + "windows-long-paths", + "windows-file-locking-av" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1EF5D0C56F8HZZCV7WT", + "id": "01M1X6SHXQA3NG56BMV9DJDPB2", + "kind": "memory", + "score": 0.9964189529418944, + "summary": "project:fact - [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe." + }, + { + "expansion_handle": "memory:01M1X6P1FEZNYXN0DQN55611JR", + "id": "01M1X6SHXQBKXZKPD7DXZ5C8YR", + "kind": "memory", + "score": 0.9571694135665894, + "summary": "project:fact - [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 824.7103999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1297, + "mcp_result_bytes": 1406, + "wire_bytes": 1443, + "reported_used_tokens": 1406, + "working_set_bytes": 293048320, + "peak_working_set_bytes": 293965824 + }, + { + "query": "how do I enable long file paths for Cargo on Windows?", + "ranked": [ + "windows-long-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1EF5D0C56F8HZZCV7WT", + "id": "01M1X6SJQH1E9HBJSM4S6Q65RN", + "kind": "memory", + "score": 0.9998334646224976, + "summary": "project:fact - [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 911.0258, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 769, + "mcp_result_bytes": 860, + "wire_bytes": 897, + "reported_used_tokens": 860, + "working_set_bytes": 293150720, + "peak_working_set_bytes": 294068224 + }, + { + "query": "intermittent sharing violation errors when Rust linker writes the exe on Windows", + "ranked": [ + "windows-file-locking-av", + "windows-long-paths", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1FEZNYXN0DQN55611JR", + "id": "01M1X6SKKYT9FK6WQWJAX3ZE8H", + "kind": "memory", + "score": 0.999750316143036, + "summary": "project:fact - [2026-09-07] [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + }, + { + "expansion_handle": "memory:01M1X6P1EF5D0C56F8HZZCV7WT", + "id": "01M1X6SKKYF4QB7WWJHZHVYXWJ", + "kind": "memory", + "score": 0.4757097661495209, + "summary": "project:fact - [2026-09-07] [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe." + }, + { + "expansion_handle": "memory:01M1X6P0A6JDJBPYZTYEXJX1EZ", + "id": "01M1X6SKKYA2BPHJC2RXA5BX3K", + "kind": "memory", + "score": 0.38107830286026, + "summary": "project:fact - [2026-09-07] [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 847.3796, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2006, + "mcp_result_bytes": 2133, + "wire_bytes": 2170, + "reported_used_tokens": 2133, + "working_set_bytes": 293220352, + "peak_working_set_bytes": 294133760 + }, + { + "query": "Rust walkdir follows junctions differently from symlinks on Windows", + "ranked": [ + "windows-junctions-vs-symlinks" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1JGK45HS6ZMEP0VSX0P", + "id": "01M1X6SMEFVC8AYQY8GRACHAJD", + "kind": "memory", + "score": 0.9996020197868348, + "summary": "project:fact - [tags: windows junctions symlinks rust std::fs] On Windows, directory junctions (NTFS reparse points) behave like symlinks for directory traversal but `std::fs::symlink_metadata` returns `FileType::is_symlink() = false` for junctions (only true for regular symlinks). Use `std::fs::read_link` \u2014 it succeeds for both junction and symlink. `walkdir` crate's `follow_links` follows both, but its `is_symlink()` method correctly reports only actual symlinks." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 873.4731, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 845, + "mcp_result_bytes": 926, + "wire_bytes": 963, + "reported_used_tokens": 926, + "working_set_bytes": 293261312, + "peak_working_set_bytes": 294174720 + }, + { + "query": "UNC path canonicalize returns verbatim prefix \u2014 how do I strip it?", + "ranked": [ + "windows-unc-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1GBZ2HQSEPCWPZA4V7M", + "id": "01M1X6SNA0RXY36FA1HWQYX8AE", + "kind": "memory", + "score": 0.9988629817962646, + "summary": "project:fact - [tags: windows unc-paths rust std::fs] Windows UNC paths (`\\\\server\\share\\...`) are not supported by most Rust `std::fs` operations unless passed through the extended-length prefix `\\\\?\\UNC\\server\\share\\...`. `std::path::Path::new(\"\\\\\\\\server\\\\share\")` works for basic operations but breaks with `canonicalize()` which returns the verbatim prefix form. When walking directory trees that may start on UNC paths, use the `dunce` crate to strip the verbatim prefix before comparing or displaying paths." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 916.99, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 908, + "mcp_result_bytes": 1025, + "wire_bytes": 1062, + "reported_used_tokens": 1025, + "working_set_bytes": 293384192, + "peak_working_set_bytes": 294309888 + }, + { + "query": "UTF-8 memory text prints as mojibake in the Windows console", + "ranked": [ + "windows-console-encoding" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1HE2E8TZ61WBXD24YKY", + "id": "01M1X6SP6P8QFC4AM309S29B8D", + "kind": "memory", + "score": 0.9996604919433594, + "summary": "project:fact - [tags: windows console encoding utf8 rust] Windows console code page defaults to the system ANSI code page (usually CP1252 or CP932), not UTF-8. Rust's `println!` writes UTF-8 bytes which display as mojibake in a non-UTF-8 console. Fix at process startup: call `SetConsoleOutputCP(65001)` via `winapi` or `windows-sys`, or set `PYTHONUTF8=1`/`RUST_LOG` before launch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 883.0991, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 757, + "mcp_result_bytes": 838, + "wire_bytes": 875, + "reported_used_tokens": 838, + "working_set_bytes": 293392384, + "peak_working_set_bytes": 294309888 + }, + { + "query": "process exit code is 4294967295 instead of -1 on Windows", + "ranked": [ + "windows-exit-codes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1KG7ET6ANHTAZM767GZ", + "id": "01M1X6SQ2WQ8AZ25WV9AX3T964", + "kind": "memory", + "score": 0.9966622591018676, + "summary": "project:fact - [tags: windows exit-codes rust process child] On Windows, process exit codes are 32-bit unsigned integers (DWORD). Rust's `ExitStatus::code()` returns `Option` \u2014 it's `None` if the process was killed by a signal (which Windows doesn't use; instead, TerminateProcess with a code). Conventional codes: 0=success, 1=generic error, 0xC0000005=access violation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 927.7739, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 753, + "mcp_result_bytes": 834, + "wire_bytes": 871, + "reported_used_tokens": 834, + "working_set_bytes": 293408768, + "peak_working_set_bytes": 294322176 + }, + { + "query": "tokenizer.json must match the ONNX model \u2014 what breaks if it doesn't?", + "ranked": [ + "onnx-tokenizer-mismatch" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1NPTMQW0G2RKJ2C2QZ6", + "id": "01M1X6SQZSA9CVT5VSV8WGR8YB", + "kind": "memory", + "score": 0.9991299510002136, + "summary": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly \u2014 specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings \u2014 cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 970.6116999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 959, + "mcp_result_bytes": 1040, + "wire_bytes": 1077, + "reported_used_tokens": 1040, + "working_set_bytes": 293412864, + "peak_working_set_bytes": 294326272 + }, + { + "query": "embedding quality degraded after I swapped in the INT8 quantized model", + "ranked": [ + "onnx-quantization-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1PMS5XEE1J3MS9A2WGM", + "id": "01M1X6SRXKJX75F6837KC7FHHG", + "kind": "memory", + "score": 0.997980535030365, + "summary": "project:fact - [2026-09-07] [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals \u2014 cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 917.0119, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 990, + "mcp_result_bytes": 1071, + "wire_bytes": 1108, + "reported_used_tokens": 1071, + "working_set_bytes": 293421056, + "peak_working_set_bytes": 294338560 + }, + { + "query": "missing attention mask causes low-norm embeddings in batch inference", + "ranked": [ + "onnx-batch-padding" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1QJRKJ1W5Q7GCPK136E", + "id": "01M1X6SST5ZXBYTRV42212FCSV", + "kind": "memory", + "score": 0.9998397827148438, + "summary": "project:fact - [tags: onnx batch padding attention-mask embeddings] When running batch inference with an ONNX model, all inputs in the batch must be padded to the same sequence length. The `attention_mask` tensor marks which tokens are real (1) and which are padding (0). Failing to pass `attention_mask` causes the model to average-pool over padding tokens, producing systematically lower-norm embeddings." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 904.6568000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 781, + "mcp_result_bytes": 862, + "wire_bytes": 899, + "reported_used_tokens": 862, + "working_set_bytes": 293494784, + "peak_working_set_bytes": 294400000 + }, + { + "query": "ONNX model download fails in a Docker container with no home directory", + "ranked": [ + "onnx-model-cache-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1RMJD8P4HWH6SGXTMZ9", + "id": "01M1X6STPFVB79VRZV1GPWVFC9", + "kind": "memory", + "score": 0.9887272119522096, + "summary": "project:fact - [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 956.6773, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 755, + "mcp_result_bytes": 838, + "wire_bytes": 875, + "reported_used_tokens": 838, + "working_set_bytes": 293548032, + "peak_working_set_bytes": 294457344 + }, + { + "query": "fastembed cache path environment variable for CI", + "ranked": [ + "onnx-model-cache-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1RMJD8P4HWH6SGXTMZ9", + "id": "01M1X6SVMC4RPDZNPEZYT0JF3T", + "kind": "memory", + "score": 0.9995118379592896, + "summary": "project:fact - [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 908.3086000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 756, + "mcp_result_bytes": 839, + "wire_bytes": 876, + "reported_used_tokens": 839, + "working_set_bytes": 293634048, + "peak_working_set_bytes": 294539264 + }, + { + "query": "cosine similarity vs dot product for L2-normalized embedding vectors", + "ranked": [ + "onnx-cosine-vs-dot", + "onnx-tokenizer-mismatch", + "onnx-quantization-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1SDTFE6G9SQ1JWY55C5", + "id": "01M1X6SWGZBACND3NDJQ3AK2VG", + "kind": "memory", + "score": 0.9999407529830932, + "summary": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing \u2014 double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + }, + { + "expansion_handle": "memory:01M1X6P1NPTMQW0G2RKJ2C2QZ6", + "id": "01M1X6SWGZ8JAN62AEXJGBAK66", + "kind": "memory", + "score": 0.9514977931976318, + "summary": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly \u2014 specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings \u2014 cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo." + }, + { + "expansion_handle": "memory:01M1X6P1PMS5XEE1J3MS9A2WGM", + "id": "01M1X6SWGZV9GCACYBXBSWB1VW", + "kind": "memory", + "score": 0.941756010055542, + "summary": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals \u2014 cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 877.8926, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2245, + "mcp_result_bytes": 2362, + "wire_bytes": 2399, + "reported_used_tokens": 2362, + "working_set_bytes": 293957632, + "peak_working_set_bytes": 294871040 + }, + { + "query": "stored vectors have wrong dimension after switching embedding models", + "ranked": [ + "onnx-dim-mismatch", + "onnx-cosine-vs-dot", + "onnx-tokenizer-mismatch" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1TBDB4YP32AX87Z885E", + "id": "01M1X6SXCCAZGK5F98X62WNVR5", + "kind": "memory", + "score": 0.9997621178627014, + "summary": "project:fact - [2026-09-07] [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results \u2014 the ANN index shape mismatch isn't always caught at runtime." + }, + { + "expansion_handle": "memory:01M1X6P1SDTFE6G9SQ1JWY55C5", + "id": "01M1X6SXCCHDNZAGVBDN5XNHNT", + "kind": "memory", + "score": 0.997715711593628, + "summary": "project:fact - [2026-09-07] [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing \u2014 double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + }, + { + "expansion_handle": "memory:01M1X6P1NPTMQW0G2RKJ2C2QZ6", + "id": "01M1X6SXCCS9KSP9WC80HJ29CE", + "kind": "memory", + "score": 0.9388805031776428, + "summary": "project:fact - [2026-09-07] [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly \u2014 specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings \u2014 cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 770.2015, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2049, + "mcp_result_bytes": 2166, + "wire_bytes": 2203, + "reported_used_tokens": 2166, + "working_set_bytes": 294035456, + "peak_working_set_bytes": 294944768 + }, + { + "query": "E5 and Instructor models need a query prefix \u2014 what happens without it?", + "ranked": [ + "onnx-prefix-instructions", + "onnx-cosine-vs-dot" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1X0E7WV7YJ8EMERDVM1", + "id": "01M1X6SY4K842JMHEAMHZAJAE8", + "kind": "memory", + "score": 0.996955633163452, + "summary": "project:fact - [tags: onnx embeddings prefix instruction e5 query passage] E5 and Instructor family models require a text prefix on BOTH query and passage sides to produce meaningful similarities: query prefix `\"query: \"`, passage prefix `\"passage: \"`. Omitting the prefix can drop MRR by 10-15 percentage points on out-of-domain datasets. Check the model's README for the exact prefix string \u2014 it varies by model family." + }, + { + "expansion_handle": "memory:01M1X6P1SDTFE6G9SQ1JWY55C5", + "id": "01M1X6SY4K7KF9MFF7K405RT9V", + "kind": "memory", + "score": 0.9543967247009276, + "summary": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing \u2014 double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 927.7011, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1339, + "mcp_result_bytes": 1446, + "wire_bytes": 1483, + "reported_used_tokens": 1446, + "working_set_bytes": 294100992, + "peak_working_set_bytes": 295014400 + }, + { + "query": "ORT thread pool contention when running multiple bench processes in parallel", + "ranked": [ + "onnx-ort-threading" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1XX3WCG6HCFJES7B7FT", + "id": "01M1X6SZ26GXB5B1S1BVD3F8F0", + "kind": "memory", + "score": 0.9998078942298888, + "summary": "project:fact - [2026-09-07] [tags: onnx ort thread-pool parallelism cpu] ORT (ONNX Runtime) creates its own inter-op and intra-op thread pools. In a multi-process bench setup, each child inherits these pools and they compete for CPU cores. Set `SessionOptionsBuilder::with_intra_threads(1).with_inter_threads(1)` if you're running many parallel bench processes \u2014 this sacrifices per-inference throughput for lower contention." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 946.5814, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 802, + "mcp_result_bytes": 883, + "wire_bytes": 920, + "reported_used_tokens": 883, + "working_set_bytes": 294113280, + "peak_working_set_bytes": 295026688 + }, + { + "query": "git worktrees share the .kimetsu brain \u2014 how do I isolate test runs?", + "ranked": [ + "git-worktree-brain-isolation", + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1YWTX8Y6NQRBF82KS3J", + "id": "01M1X6SZZ3M5MW5K03HJD603A5", + "kind": "memory", + "score": 0.9996256828308104, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root \u2014 if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + }, + { + "expansion_handle": "memory:01M1X6P00PJD8FDG3QR2RS7QAK", + "id": "01M1X6SZZ3E5FZ9CXNCRRMS7CC", + "kind": "memory", + "score": 0.9904396533966064, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 910.5159000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1435, + "mcp_result_bytes": 1534, + "wire_bytes": 1571, + "reported_used_tokens": 1534, + "working_set_bytes": 294117376, + "peak_working_set_bytes": 295034880 + }, + { + "query": "when is it safe to use --no-verify on git commit?", + "ranked": [ + "git-hooks-bypass", + "git-reflog-rescue" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1ZTA6M3QRJEDZXT7GPD", + "id": "01M1X6T0VE2TD5BHNMGMF9TEJ3", + "kind": "memory", + "score": 0.9956986904144288, + "summary": "project:fact - [2026-09-07] [tags: git hooks bypass pre-commit skip] `git commit --no-verify` skips ALL hooks (pre-commit and commit-msg). Never use this in shared team repos where hooks enforce quality gates (lint, tests, memory harvest). Instead, fix the failing hook." + }, + { + "expansion_handle": "memory:01M1X6P23PDPP71F97F0Z839MX", + "id": "01M1X6T0VENFVB9M1DCJRK75AX", + "kind": "memory", + "score": 0.5084817409515381, + "summary": "project:fact - [2026-09-07] [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone \u2014 they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only \u2014 remote reflog is not accessible via normal git commands." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 920.865, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1193, + "mcp_result_bytes": 1292, + "wire_bytes": 1329, + "reported_used_tokens": 1292, + "working_set_bytes": 294117376, + "peak_working_set_bytes": 295034880 + }, + { + "query": "reduce clone size and bandwidth for server-side repo ingest", + "ranked": [ + "git-sparse-checkout", + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P20RSG8Q3WVX1DR7PCMJ", + "id": "01M1X6T1RBPM9HZ3HMMCH5QQ09", + "kind": "memory", + "score": 0.9969936609268188, + "summary": "project:fact - [tags: git sparse-checkout partial-clone bandwidth] `git sparse-checkout init --cone` combined with `git clone --filter=blob:none` (partial clone) fetches only the commit graph and tree objects, not blobs. Individual blobs are fetched on demand when accessed. This cuts clone time for large repos from minutes to seconds." + }, + { + "expansion_handle": "memory:01M1X6NZK0NCCC1P19NW0KM4XV", + "id": "01M1X6T1RBV9K1YTPAASQ2GN3P", + "kind": "memory", + "score": 0.8199672698974609, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 983.3775, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1744, + "mcp_result_bytes": 1843, + "wire_bytes": 1880, + "reported_used_tokens": 1843, + "working_set_bytes": 294129664, + "peak_working_set_bytes": 295038976 + }, + { + "query": "spurious diffs from Windows CRLF line ending conversion in git", + "ranked": [ + "git-line-endings-windows" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P21RP2J0P311XGVMM8MY", + "id": "01M1X6T2Q04X0EEBRPJ79ZB3Q5", + "kind": "memory", + "score": 0.9993343949317932, + "summary": "project:fact - [tags: git line-endings windows crlf autocrlf] On Windows, `core.autocrlf=true` (git's default for Windows installs) converts LF to CRLF on checkout and CRLF to LF on commit. This causes spurious diffs when files are edited on Windows then committed \u2014 the content is identical but the line endings differ in the index vs the working tree. Fix: set `core.autocrlf=false` and `.gitattributes` with `* text=auto eol=lf` for the repo." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 908.5777, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 940, + "reported_used_tokens": 903, + "working_set_bytes": 294154240, + "peak_working_set_bytes": 295059456 + }, + { + "query": "git submodule always gets the wrong commit in CI", + "ranked": [ + "git-submodule-pinning", + "git-hooks-bypass" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P22PHYS4T5P5F3PE7N1M", + "id": "01M1X6T3KKRDFGF8RZQ3A21FK1", + "kind": "memory", + "score": 0.9992856383323668, + "summary": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip \u2014 this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version." + }, + { + "expansion_handle": "memory:01M1X6P1ZTA6M3QRJEDZXT7GPD", + "id": "01M1X6T3KKC72DJF7P63RMM14X", + "kind": "memory", + "score": 0.6295387744903564, + "summary": "project:fact - [tags: git hooks bypass pre-commit skip] `git commit --no-verify` skips ALL hooks (pre-commit and commit-msg). Never use this in shared team repos where hooks enforce quality gates (lint, tests, memory harvest). Instead, fix the failing hook." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 933.53, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1157, + "mcp_result_bytes": 1256, + "wire_bytes": 1293, + "reported_used_tokens": 1256, + "working_set_bytes": 294174720, + "peak_working_set_bytes": 295088128 + }, + { + "query": "accidentally ran git reset --hard and lost commits \u2014 can I recover?", + "ranked": [ + "git-reflog-rescue" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P23PDPP71F97F0Z839MX", + "id": "01M1X6T4GMGS2SDP932S6YRAJX", + "kind": "memory", + "score": 0.9995450377464294, + "summary": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone \u2014 they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only \u2014 remote reflog is not accessible via normal git commands." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 947.8262, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 762, + "mcp_result_bytes": 843, + "wire_bytes": 880, + "reported_used_tokens": 843, + "working_set_bytes": 294187008, + "peak_working_set_bytes": 295088128 + }, + { + "query": "blocking SQLite call from an async tokio handler causes latency spikes", + "ranked": [ + "tokio-blocking-in-async" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P24PG6KXJZQB4K5G8MKX", + "id": "01M1X6T5ENWESC500QTEVKN0GD", + "kind": "memory", + "score": 0.9996535778045654, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 874.721, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 766, + "mcp_result_bytes": 847, + "wire_bytes": 884, + "reported_used_tokens": 847, + "working_set_bytes": 294207488, + "peak_working_set_bytes": 295116800 + }, + { + "query": "Cannot start a runtime from within a runtime in a tokio test", + "ranked": [ + "tokio-runtime-in-tests", + "tokio-blocking-in-async" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P25SW9R97848FW6SCJ4H", + "id": "01M1X6T69T4XKPHT3XF5PK3ZMC", + "kind": "memory", + "score": 0.9997126460075378, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + }, + { + "expansion_handle": "memory:01M1X6P24PG6KXJZQB4K5G8MKX", + "id": "01M1X6T69VV8JYWSKCC9AHYDTF", + "kind": "memory", + "score": 0.5779464840888977, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 862.5042000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1370, + "mcp_result_bytes": 1477, + "wire_bytes": 1514, + "reported_used_tokens": 1477, + "working_set_bytes": 294207488, + "peak_working_set_bytes": 295120896 + }, + { + "query": "tokio select cancels the other branch and loses the value in the channel", + "ranked": [ + "tokio-select-cancellation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P270RXS50A66Q2YBWW4H", + "id": "01M1X6T75MD8MFKE9VRDY77VC3", + "kind": "memory", + "score": 0.9981033802032472, + "summary": "project:fact - [tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 920.0183, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 751, + "mcp_result_bytes": 832, + "wire_bytes": 869, + "reported_used_tokens": 832, + "working_set_bytes": 294178816, + "peak_working_set_bytes": 295120896 + }, + { + "query": "mpsc channel backpressure causing senders to stall", + "ranked": [ + "tokio-channel-backpressure" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P284KFX2CP4805G4SDCD", + "id": "01M1X6T81C59QG0YR8N9HJ5R1V", + "kind": "memory", + "score": 0.9999104738235474, + "summary": "project:fact - [tags: tokio mpsc channel backpressure async rust] `tokio::sync::mpsc::channel(N)` with a bounded buffer provides backpressure: senders block when the buffer is full. This prevents unbounded memory growth but can cause sender tasks to stall. Choosing N: too small causes frequent backpressure (throughput drops); too large defeats the purpose." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 970.7314, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 733, + "mcp_result_bytes": 814, + "wire_bytes": 851, + "reported_used_tokens": 814, + "working_set_bytes": 294182912, + "peak_working_set_bytes": 295120896 + }, + { + "query": "overhead from calling spawn_blocking on every single query request", + "ranked": [ + "tokio-spawn-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P297FDMXCDWTFDYT22QV", + "id": "01M1X6T8ZQYJNHSGC87Z70HQ0E", + "kind": "memory", + "score": 0.9961729645729064, + "summary": "project:fact - [tags: tokio spawn_blocking thread-pool rust blocking] `tokio::task::spawn_blocking` places work on a dedicated blocking thread pool (default up to 512 threads, configurable via `Builder::max_blocking_threads`). Each call creates or reuses a thread \u2014 there's no true pooling, threads may be created on demand. For many short-duration blocking calls (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 912.1324, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 746, + "mcp_result_bytes": 827, + "wire_bytes": 864, + "reported_used_tokens": 827, + "working_set_bytes": 294309888, + "peak_working_set_bytes": 295219200 + }, + { + "query": "axum server panics during shutdown because the DB pool is already closed", + "ranked": [ + "tokio-shutdown-ordering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P2CZ739FX2TTZAGZGTRW", + "id": "01M1X6T9W7WH90QDNAG8RCJZS1", + "kind": "memory", + "score": 0.98052579164505, + "summary": "project:fact - [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries \u2014 the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 920.5963, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 931, + "mcp_result_bytes": 1012, + "wire_bytes": 1049, + "reported_used_tokens": 1012, + "working_set_bytes": 294412288, + "peak_working_set_bytes": 295329792 + }, + { + "query": "reqwest Client created per-request defeats connection pooling", + "ranked": [ + "http-connection-pooling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P2E2AZRTGV4X4V6ND9WP", + "id": "01M1X6TAS5D36434XZB5H58Z3Q", + "kind": "memory", + "score": 0.9998082518577576, + "summary": "project:fact - [tags: http reqwest connection-pool keep-alive rust] reqwest's `Client` holds a connection pool; always create ONE `Client` instance and clone it for each handler \u2014 cloning is cheap (Arc under the hood). Creating a `Client::new()` per request defeats connection pooling and causes TCP connection exhaustion under load. The default pool settings: max_idle_per_host=usize::MAX (unbounded), idle_timeout=90s." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 862.3707, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 797, + "mcp_result_bytes": 878, + "wire_bytes": 915, + "reported_used_tokens": 878, + "working_set_bytes": 294449152, + "peak_working_set_bytes": 295342080 + }, + { + "query": "LLM request times out during streaming \u2014 which timeout setting applies?", + "ranked": [ + "http-timeout-layering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P2F6VFRXARZ6TPWVGB9S", + "id": "01M1X6TBM71HB0Q1WWHWZWGBQW", + "kind": "memory", + "score": 0.9987107515335084, + "summary": "project:fact - [tags: http reqwest timeout connect read total rust] reqwest has three distinct timeout knobs: `connect_timeout`, `read_timeout`, and `timeout` (total). They compose: if all three are set, the request fails at whichever fires first. For LLM API calls with streaming responses, `read_timeout` must be larger than the slowest expected token (often 30-60s) while `connect_timeout` can be tight (3-5s)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 786.327, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 788, + "mcp_result_bytes": 869, + "wire_bytes": 906, + "reported_used_tokens": 869, + "working_set_bytes": 294481920, + "peak_working_set_bytes": 295395328 + }, + { + "query": "how do I safely retry a POST to the LLM API without creating duplicates?", + "ranked": [ + "http-retry-idempotency" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P2GA8QFB8KPZ4G6QRSD5", + "id": "01M1X6TCCM6WM0E7MXHTD0XW2R", + "kind": "memory", + "score": 0.9995805621147156, + "summary": "project:fact - [tags: http retry idempotency post put reqwest] Only retry idempotent requests automatically. GET, HEAD, PUT, DELETE are idempotent. POST is NOT \u2014 retrying a POST may create duplicate resources." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 954.5133, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 585, + "mcp_result_bytes": 666, + "wire_bytes": 703, + "reported_used_tokens": 666, + "working_set_bytes": 294481920, + "peak_working_set_bytes": 295399424 + }, + { + "query": "custom enterprise root CA not trusted by rustls on Windows", + "ranked": [ + "http-tls-roots", + "http-proxy-env" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P2HH2CGAQBG5W2V177J6", + "id": "01M1X6TDAM3J3813F2B31E9PD0", + "kind": "memory", + "score": 0.9998220801353456, + "summary": "project:fact - [tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle \u2014 the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle." + }, + { + "expansion_handle": "memory:01M1X6P2KVN5DQ41TVK0JMYGCV", + "id": "01M1X6TDAMZBV81VF8JTZJ9PTG", + "kind": "memory", + "score": 0.38715291023254395, + "summary": "project:fact - [tags: http proxy environment reqwest rust corporate] reqwest respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` environment variables by default (with `default-tls` or `rustls-tls`). In a corporate network, these may redirect traffic through an intercepting proxy that breaks mTLS or adds latency. To disable proxy usage entirely: `reqwest::ClientBuilder::no_proxy()`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 927.7096, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1311, + "mcp_result_bytes": 1410, + "wire_bytes": 1447, + "reported_used_tokens": 1410, + "working_set_bytes": 294481920, + "peak_working_set_bytes": 295399424 + }, + { + "query": "parsing server-sent events when a single TCP chunk contains a partial SSE frame", + "ranked": [ + "http-streaming-bodies" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P2JNVC441XJQFSMYT0SJ", + "id": "01M1X6TE7RJR1B9A8KZ19177HQ", + "kind": "memory", + "score": 0.9667426943778992, + "summary": "project:fact - [2026-09-07] [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding \u2014 a chunk may split across frame boundaries." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 861.8673, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 859, + "mcp_result_bytes": 940, + "wire_bytes": 977, + "reported_used_tokens": 940, + "working_set_bytes": 294481920, + "peak_working_set_bytes": 295399424 + }, + { + "query": "reqwest does not use the system proxy settings on Windows", + "ranked": [ + "http-proxy-env", + "http-tls-roots", + "http-connection-pooling", + "http-streaming-bodies" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P2KVN5DQ41TVK0JMYGCV", + "id": "01M1X6TF334RZ4YQJDAGMDFFRJ", + "kind": "memory", + "score": 0.9997830986976624, + "summary": "project:fact - [tags: http proxy environment reqwest rust corporate] reqwest respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` environment variables by default (with `default-tls` or `rustls-tls`). In a corporate network, these may redirect traffic through an intercepting proxy that breaks mTLS or adds latency. To disable proxy usage entirely: `reqwest::ClientBuilder::no_proxy()`." + }, + { + "expansion_handle": "memory:01M1X6P2HH2CGAQBG5W2V177J6", + "id": "01M1X6TF33VKQTQAMPKFBCQ9VB", + "kind": "memory", + "score": 0.9808586239814758, + "summary": "project:fact - [tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle \u2014 the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle." + }, + { + "expansion_handle": "memory:01M1X6P2E2AZRTGV4X4V6ND9WP", + "id": "01M1X6TF33A7WZPDJ4JTR7ESXQ", + "kind": "memory", + "score": 0.719273030757904, + "summary": "project:fact - [tags: http reqwest connection-pool keep-alive rust] reqwest's `Client` holds a connection pool; always create ONE `Client` instance and clone it for each handler \u2014 cloning is cheap (Arc under the hood). Creating a `Client::new()` per request defeats connection pooling and causes TCP connection exhaustion under load. The default pool settings: max_idle_per_host=usize::MAX (unbounded), idle_timeout=90s." + }, + { + "expansion_handle": "memory:01M1X6P2JNVC441XJQFSMYT0SJ", + "id": "01M1X6TF33WS7Z975583KRJDDD", + "kind": "memory", + "score": 0.7009692192077637, + "summary": "project:fact - [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding \u2014 a chunk may split across frame boundaries." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 947.9492, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2497, + "mcp_result_bytes": 2632, + "wire_bytes": 2669, + "reported_used_tokens": 2632, + "working_set_bytes": 294481920, + "peak_working_set_bytes": 295399424 + }, + { + "query": "insta snapshot tests fail in CI because output includes a timestamp", + "ranked": [ + "testing-snapshot-churn", + "ci-flaky-quarantine" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P2MX1SRAWDA3HNGSBVT6", + "id": "01M1X6TG04BDRVX490Q6EGVTM0", + "kind": "memory", + "score": 0.999855637550354, + "summary": "project:fact - [tags: testing snapshot insta assert churn rust] Snapshot tests (e.g. with the `insta` crate) fail whenever the output changes, even for intended changes. In CI, they fail loudly; locally, `cargo insta review` walks you through accepting or rejecting changes." + }, + { + "expansion_handle": "memory:01M1X6P3H0TD9NJ1PFEC45G42A", + "id": "01M1X6TG04YKH9RJA1R45FXWNR", + "kind": "memory", + "score": 0.5997360348701477, + "summary": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal \u2014 a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 823.4245999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1196, + "mcp_result_bytes": 1295, + "wire_bytes": 1332, + "reported_used_tokens": 1295, + "working_set_bytes": 294486016, + "peak_working_set_bytes": 295399424 + }, + { + "query": "two test workers writing to the same temp directory path race each other", + "ranked": [ + "testing-temp-dirs-ci" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P2P2TSKGVDHTVF5Y34FZ", + "id": "01M1X6TGSVHW6B7XEKC61RCTZ5", + "kind": "memory", + "score": 0.9889234900474548, + "summary": "project:fact - [tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 878.9519, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 755, + "mcp_result_bytes": 836, + "wire_bytes": 873, + "reported_used_tokens": 836, + "working_set_bytes": 294486016, + "peak_working_set_bytes": 295399424 + }, + { + "query": "test passes locally but fails on a slow CI runner due to a 100ms sleep", + "ranked": [ + "testing-time-dependent-flakes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P2Q8X75PWP6KHNCWMTJZ", + "id": "01M1X6THNAHQXNG3JZW63HGWW1", + "kind": "memory", + "score": 0.808289110660553, + "summary": "project:fact - [tags: testing time flaky clock mock rust] Tests that depend on wall-clock time are inherently flaky under load (slow CI runners, GC pauses). Abstract time behind a trait (`Clock: Fn() -> SystemTime`) injected at construction, and supply a fake in tests. For tests checking that something happened \"within N seconds\", use a generous multiple of the expected duration (10x is not unreasonable for CI)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 938.7784, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 790, + "mcp_result_bytes": 875, + "wire_bytes": 912, + "reported_used_tokens": 875, + "working_set_bytes": 294486016, + "peak_working_set_bytes": 295407616 + }, + { + "query": "proptest found a hash collision in text normalization that example tests missed", + "ranked": [ + "testing-property-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P2RBNN1JHA889PRFA63Y", + "id": "01M1X6TJJT19GXRNWP4WXH4AMM", + "kind": "memory", + "score": 0.9994783997535706, + "summary": "project:fact - [tags: testing property-based proptest quickcheck rust] Property-based tests (proptest, quickcheck) find edge cases that example-based tests miss. For kimetsu's memory text normalization, proptest found that zero-width joiner characters and right-to-left marks caused hash collisions. Run proptest with `PROPTEST_CASES=10000` in CI for thorough coverage." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 943.2481, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 744, + "mcp_result_bytes": 825, + "wire_bytes": 862, + "reported_used_tokens": 825, + "working_set_bytes": 294486016, + "peak_working_set_bytes": 295407616 + }, + { + "query": "set_var in tests races when cargo test runs them in parallel", + "ranked": [ + "testing-serial-vs-parallel" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P2SBPNK9PEDW9QRXPYZJ", + "id": "01M1X6TKGD4JHV527ADD5936JQ", + "kind": "memory", + "score": 0.9997344613075256, + "summary": "project:fact - [2026-09-07] [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 925.4599999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 832, + "mcp_result_bytes": 913, + "wire_bytes": 950, + "reported_used_tokens": 913, + "working_set_bytes": 294494208, + "peak_working_set_bytes": 295415808 + }, + { + "query": "hardcoded JSON fixtures broke after a schema migration", + "ranked": [ + "testing-fixture-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P2X1Q84DSXWGJTFEX3G2", + "id": "01M1X6TMD3GC10F2MJFRHE6NCZ", + "kind": "memory", + "score": 0.9998371601104736, + "summary": "project:fact - [2026-09-07] [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 769.0437, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 783, + "mcp_result_bytes": 864, + "wire_bytes": 901, + "reported_used_tokens": 864, + "working_set_bytes": 294658048, + "peak_working_set_bytes": 295567360 + }, + { + "query": "debug print in the MCP handler corrupts the JSON-Lines protocol stream", + "ranked": [ + "mcp-stdout-protocol" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P2XXR71TXJXVQBRVTSF6", + "id": "01M1X6TN60P1R3Q0ZVVXXS3E0D", + "kind": "memory", + "score": 0.9997472167015076, + "summary": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 975.8611000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 705, + "mcp_result_bytes": 786, + "wire_bytes": 823, + "reported_used_tokens": 786, + "working_set_bytes": 294731776, + "peak_working_set_bytes": 295645184 + }, + { + "query": "kimetsu MCP tool call times out because embedding model is re-initialized every call", + "ranked": [ + "mcp-tool-timeouts", + "mcp-schema-validation", + "kimetsu-bench-remote-embedder-singleton" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P2YZ4YRDGDVJV5XMKCP1", + "id": "01M1X6TP3X3EJ1ZJ6ADMZS7NYE", + "kind": "memory", + "score": 0.9995898604393004, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + }, + { + "expansion_handle": "memory:01M1X6P31407BS4T0BV7PGT98H", + "id": "01M1X6TP3XRGMXGF4YFAEWVXS2", + "kind": "memory", + "score": 0.6027993559837341, + "summary": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array \u2014 omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error." + }, + { + "expansion_handle": "memory:01M1X6P3WKNTTE5DK6PSVCEB7R", + "id": "01M1X6TP3X21F81YVCKKWNCVBG", + "kind": "memory", + "score": 0.5117799639701843, + "summary": "project:fact - [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 831.0237999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2085, + "mcp_result_bytes": 2202, + "wire_bytes": 2239, + "reported_used_tokens": 2202, + "working_set_bytes": 294772736, + "peak_working_set_bytes": 295686144 + }, + { + "query": "env var set after host launch is not visible to the MCP server process", + "ranked": [ + "mcp-env-propagation", + "kimetsu-daemon-lifecycle" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P302K2RV969H5CEVQWE9", + "id": "01M1X6TPY37R0JWYC19FR8M4V9", + "kind": "memory", + "score": 0.9984827637672424, + "summary": "project:fact - [2026-09-07] [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment \u2014 changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate." + }, + { + "expansion_handle": "memory:01M1X6P3J1H7C1NPH0T2G7FWCC", + "id": "01M1X6TPY3075ZPEN7VPSEJR3R", + "kind": "memory", + "score": 0.9977922439575196, + "summary": "project:fact - [2026-09-07] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 998.9179, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1267, + "mcp_result_bytes": 1366, + "wire_bytes": 1403, + "reported_used_tokens": 1366, + "working_set_bytes": 294793216, + "peak_working_set_bytes": 295714816 + }, + { + "query": "MCP tool call fails because a required field is missing from the JSON input", + "ranked": [ + "mcp-schema-validation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P31407BS4T0BV7PGT98H", + "id": "01M1X6TQWZ5B3T64V7ED900M6Y", + "kind": "memory", + "score": 0.998538613319397, + "summary": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array \u2014 omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 915.9673, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 798, + "mcp_result_bytes": 879, + "wire_bytes": 916, + "reported_used_tokens": 879, + "working_set_bytes": 294801408, + "peak_working_set_bytes": 295714816 + }, + { + "query": "Claude Code rejects the tool name with a hyphen in it", + "ranked": [ + "mcp-tool-naming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P32468TZD6PXNN0J84CQ", + "id": "01M1X6TRSPKYV5FAZCZZ854V1H", + "kind": "memory", + "score": 0.9982439279556274, + "summary": "project:fact - [tags: mcp tool naming convention kimetsu] MCP tool names must be valid identifiers for all host agents. Claude Code restricts tool names to `[a-zA-Z0-9_-]` and max 64 chars. Use `snake_case` (kimetsu_brain_context, kimetsu_brain_record) \u2014 hyphen is technically allowed but some hosts reject it." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 890.1919, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 687, + "mcp_result_bytes": 768, + "wire_bytes": 805, + "reported_used_tokens": 768, + "working_set_bytes": 294891520, + "peak_working_set_bytes": 295804928 + }, + { + "query": "MCP response path uses backslashes and the host rejects it", + "ranked": [ + "mcp-transcript-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P337XNQ9HBRDB7E4246Z", + "id": "01M1X6TSNCSZ7GHAETJ4FKQNDK", + "kind": "memory", + "score": 0.9984637498855592, + "summary": "project:fact - [tags: mcp transcript paths kimetsu hooks runs] kimetsu writes run transcripts to `/.kimetsu/runs//`. The post-session hook reads the latest run's transcript to trigger memory harvest. On Windows, the path uses backslashes internally but the MCP JSON must use forward slashes or the host may reject path-type arguments." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 914.7967, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 724, + "mcp_result_bytes": 805, + "wire_bytes": 842, + "reported_used_tokens": 805, + "working_set_bytes": 294903808, + "peak_working_set_bytes": 295813120 + }, + { + "query": "AWS credentials not found \u2014 which env var does kimetsu read for Bedrock?", + "ranked": [ + "aws-credentials-chain", + "aws-region-resolution", + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P34CEH6KSKA057ZQRBCP", + "id": "01M1X6TTJ1HB6BNANXB239NEWQ", + "kind": "memory", + "score": 0.9990235567092896, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + }, + { + "expansion_handle": "memory:01M1X6P35QVP8D7M962ZGK168S", + "id": "01M1X6TTJ1WZTYJ8HFX2781EPJ", + "kind": "memory", + "score": 0.9968422651290894, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X6NZRCKJJR7B15FGFKTS4W", + "id": "01M1X6TTJ19AN40YXA8ERJK6EW", + "kind": "memory", + "score": 0.9849756360054016, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X6NZXZMGW8CD9YMNHQ3ZTX", + "id": "01M1X6TTJ11RNHSXZBRC6D0VD0", + "kind": "memory", + "score": 0.9203452467918396, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 947.4548000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3455, + "mcp_result_bytes": 3618, + "wire_bytes": 3655, + "reported_used_tokens": 3618, + "working_set_bytes": 294903808, + "peak_working_set_bytes": 295817216 + }, + { + "query": "Bedrock InvokeModel fails because the region is not configured", + "ranked": [ + "aws-region-resolution", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P35QVP8D7M962ZGK168S", + "id": "01M1X6TVFWN9APZT2PF094VP2W", + "kind": "memory", + "score": 0.99688321352005, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X6NZXZMGW8CD9YMNHQ3ZTX", + "id": "01M1X6TVFW1DJYV0N1RRE33TJE", + "kind": "memory", + "score": 0.6450709104537964, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1013.5312000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1810, + "mcp_result_bytes": 1929, + "wire_bytes": 1966, + "reported_used_tokens": 1929, + "working_set_bytes": 294903808, + "peak_working_set_bytes": 295821312 + }, + { + "query": "how do I handle ThrottlingException from Bedrock with exponential backoff?", + "ranked": [ + "aws-retry-throttling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P36TQNTE0Z7YTV5BM0YV", + "id": "01M1X6TWFFFSJ34F0JR1TC8CRC", + "kind": "memory", + "score": 0.9997082352638244, + "summary": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with \u00b125% jitter." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 994.9831, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 771, + "mcp_result_bytes": 868, + "wire_bytes": 905, + "reported_used_tokens": 868, + "working_set_bytes": 294907904, + "peak_working_set_bytes": 295821312 + }, + { + "query": "generating a presigned S3 URL for brain export without exposing credentials", + "ranked": [ + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P37Y0AZY1Q54BEN2E91E", + "id": "01M1X6TXEH8C75HCQMQWCNWKKQ", + "kind": "memory", + "score": 0.9990487694740297, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 931.8839, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 875, + "mcp_result_bytes": 956, + "wire_bytes": 993, + "reported_used_tokens": 956, + "working_set_bytes": 294907904, + "peak_working_set_bytes": 295821312 + }, + { + "query": "IMDSv2 token required for instance metadata \u2014 PUT before GET", + "ranked": [ + "aws-instance-metadata" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P392Y3GSQ55B0ZTJV7CZ", + "id": "01M1X6TYBMB7TC1CQTFQ7QF4XY", + "kind": "memory", + "score": 0.9997182488441468, + "summary": "project:fact - [2026-09-07] [tags: aws imds instance-metadata ec2 token] The AWS Instance Metadata Service v2 (IMDSv2) requires a session token: PUT `http://169.254.169.254/latest/api/token` with `X-aws-ec2-metadata-token-ttl-seconds: 21600` to get a token, then GET metadata with `X-aws-ec2-metadata-token: `. IMDSv1 (no token) is disabled on hardened instances. The metadata endpoint is only reachable from within EC2 \u2014 a connection timeout means you're not on EC2." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 945.0785000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 851, + "mcp_result_bytes": 932, + "wire_bytes": 969, + "reported_used_tokens": 932, + "working_set_bytes": 294907904, + "peak_working_set_bytes": 295821312 + }, + { + "query": "Cargo cache key strategy for GitHub Actions to avoid toolchain version collisions", + "ranked": [ + "ci-cache-keys" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3D60EXFZ940W9PTRB37", + "id": "01M1X6TZ9E15D2DFXR7NNFAMTV", + "kind": "memory", + "score": 0.998869240283966, + "summary": "project:fact - [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key \u2014 macOS and Windows have incompatible artifact formats." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 997.9987, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 788, + "mcp_result_bytes": 869, + "wire_bytes": 906, + "reported_used_tokens": 869, + "working_set_bytes": 294907904, + "peak_working_set_bytes": 295821312 + }, + { + "query": "CI matrix has 18 jobs and costs too much \u2014 how do I reduce it?", + "ranked": [ + "ci-matrix-explosion" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3E2Z6GNT2HV8ZC7M4QK", + "id": "01M1X6V08TDGKGMWH8JJB9NWPF", + "kind": "memory", + "score": 0.999057948589325, + "summary": "project:fact - [tags: ci github-actions matrix jobs resources] A CI matrix combining OS (3) x Rust toolchain (3) x features (2) = 18 jobs. Each spawns a runner; at $0.008/min for Ubuntu and $0.016/min for Windows, a 10-minute build costs $2.40 per push. Reduce: test the full matrix only on PRs to main; on feature branches, test only Linux+stable." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 974.2088, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 722, + "mcp_result_bytes": 803, + "wire_bytes": 840, + "reported_used_tokens": 803, + "working_set_bytes": 294907904, + "peak_working_set_bytes": 295825408 + }, + { + "query": "GitHub Actions secret accidentally printed in build logs", + "ranked": [ + "ci-secrets-masking", + "ci-cache-keys", + "ci-artifact-retention" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3F12CWS8FE5S50H7TYH", + "id": "01M1X6V16VMG16XS7GDBZVAA19", + "kind": "memory", + "score": 0.9963951706886292, + "summary": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output \u2014 but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable." + }, + { + "expansion_handle": "memory:01M1X6P3D60EXFZ940W9PTRB37", + "id": "01M1X6V16VM2HBSH5WHWM7PWPE", + "kind": "memory", + "score": 0.4342843890190125, + "summary": "project:fact - [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key \u2014 macOS and Windows have incompatible artifact formats." + }, + { + "expansion_handle": "memory:01M1X6P3G3ZGS62RZRNJXYR8N4", + "id": "01M1X6V16VZV94K0MKM7BZCCK5", + "kind": "memory", + "score": 0.3422144949436188, + "summary": "project:fact - [tags: ci github-actions artifacts retention benchmark] GitHub Actions artifacts are retained for 90 days (default). For benchmark results, use `actions/upload-artifact` with `retention-days: 365` for long-term tracking. The free tier has 500MB storage \u2014 per-combo JSON files from kimetsu bench (each ~60KB) add up fast if you upload them on every push." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 912.6428, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1791, + "mcp_result_bytes": 1908, + "wire_bytes": 1945, + "reported_used_tokens": 1908, + "working_set_bytes": 294916096, + "peak_working_set_bytes": 295829504 + }, + { + "query": "how long do GitHub Actions artifacts persist and what's the storage limit?", + "ranked": [ + "ci-artifact-retention" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3G3ZGS62RZRNJXYR8N4", + "id": "01M1X6V23CGDQ808DE2CG4PGBC", + "kind": "memory", + "score": 0.999624252319336, + "summary": "project:fact - [tags: ci github-actions artifacts retention benchmark] GitHub Actions artifacts are retained for 90 days (default). For benchmark results, use `actions/upload-artifact` with `retention-days: 365` for long-term tracking. The free tier has 500MB storage \u2014 per-combo JSON files from kimetsu bench (each ~60KB) add up fast if you upload them on every push." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 981.2126, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 744, + "mcp_result_bytes": 825, + "wire_bytes": 862, + "reported_used_tokens": 825, + "working_set_bytes": 294924288, + "peak_working_set_bytes": 295841792 + }, + { + "query": "timing-based test flake in CI \u2014 quarantine or fix?", + "ranked": [ + "ci-flaky-quarantine", + "testing-time-dependent-flakes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3H0TD9NJ1PFEC45G42A", + "id": "01M1X6V322JRC3B5NQNV2V73H2", + "kind": "memory", + "score": 0.9994743466377258, + "summary": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal \u2014 a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output." + }, + { + "expansion_handle": "memory:01M1X6P2Q8X75PWP6KHNCWMTJZ", + "id": "01M1X6V322C4809S9DN02KGMX4", + "kind": "memory", + "score": 0.9849997162818908, + "summary": "project:fact - [tags: testing time flaky clock mock rust] Tests that depend on wall-clock time are inherently flaky under load (slow CI runners, GC pauses). Abstract time behind a trait (`Clock: Fn() -> SystemTime`) injected at construction, and supply a fake in tests. For tests checking that something happened \"within N seconds\", use a generous multiple of the expected duration (10x is not unreasonable for CI)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 944.8933000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1340, + "mcp_result_bytes": 1443, + "wire_bytes": 1480, + "reported_used_tokens": 1443, + "working_set_bytes": 294932480, + "peak_working_set_bytes": 295854080 + }, + { + "query": "kimetsu doctor says the MCP server is running \u2014 how do I stop it before an update?", + "ranked": [ + "kimetsu-daemon-lifecycle", + "mcp-env-propagation", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3J1H7C1NPH0T2G7FWCC", + "id": "01M1X6V40E8NG90AGE03QYK1EB", + "kind": "memory", + "score": 0.9989782571792604, + "summary": "project:fact - [2026-09-07] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1X6P302K2RV969H5CEVQWE9", + "id": "01M1X6V40EY1GJQTKKTHD7XD67", + "kind": "memory", + "score": 0.9049031734466552, + "summary": "project:fact - [2026-09-07] [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment \u2014 changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate." + }, + { + "expansion_handle": "memory:01M1X6NZN0FVTV36T1B6KBFMKZ", + "id": "01M1X6V40EACE1V3ASY6EKNB6J", + "kind": "memory", + "score": 0.4812128245830536, + "summary": "project:fact - [2026-09-07] [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1009.7391, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2046, + "mcp_result_bytes": 2211, + "wire_bytes": 2248, + "reported_used_tokens": 2211, + "working_set_bytes": 294940672, + "peak_working_set_bytes": 295854080 + }, + { + "query": "noise capsules consuming token budget without contributing retrieval signal", + "ranked": [ + "kimetsu-capsule-budgets" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3K1QHGWBDZK08Q9G2WJ", + "id": "01M1X6V4Z69AE0W00M1MBKP78M", + "kind": "memory", + "score": 0.9997420907020568, + "summary": "project:fact - [tags: kimetsu capsule tokens budget retrieval] kimetsu retrieval enforces a token budget per capsule type: memory capsules are capped at 6000 tokens total (across all retrieved memories), file capsules at 3000 tokens. When a memory is large and would exceed the budget, it is truncated at a sentence boundary. The budget is enforced AFTER reranking \u2014 reranking may reorder results so that a truncated high-ranked memory displaces a full lower-ranked one." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 777.2112, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 847, + "mcp_result_bytes": 928, + "wire_bytes": 965, + "reported_used_tokens": 928, + "working_set_bytes": 294940672, + "peak_working_set_bytes": 295854080 + }, + { + "query": "kimetsu_brain_record writes to the wrong brain location \u2014 user vs project scope", + "ranked": [ + "kimetsu-memory-scopes", + "kimetsu-write-tools-gate", + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3KY5MF7R4RJB34EHA6R", + "id": "01M1X6V5QSNFC4J5T03FC1ZZ07", + "kind": "memory", + "score": 0.999030828475952, + "summary": "project:fact - [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available \u2014 if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope." + }, + { + "expansion_handle": "memory:01M1X6P3Q18YAX03ZC3P1KS0CF", + "id": "01M1X6V5QSG70X6W226QY6CGZQ", + "kind": "memory", + "score": 0.9838979840278624, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level \u2014 disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1X6P00PJD8FDG3QR2RS7QAK", + "id": "01M1X6V5QS3V6WP72ZKFSX41G5", + "kind": "memory", + "score": 0.3852712512016296, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 911.9002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2098, + "mcp_result_bytes": 2215, + "wire_bytes": 2252, + "reported_used_tokens": 2215, + "working_set_bytes": 294940672, + "peak_working_set_bytes": 295854080 + }, + { + "query": "how do I configure kimetsu to use Claude Haiku for harvesting but Opus for the agent?", + "ranked": [ + "kimetsu-distiller-config" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3MZ6G96CDQ3F49KXK93", + "id": "01M1X6V6M43YZXWSM6D6C0Z8Y6", + "kind": "memory", + "score": 0.9989088773727416, + "summary": "project:fact - [tags: kimetsu distiller harvest config provider] The kimetsu distiller (auto-harvester) uses a SEPARATE provider configuration from the main agent: `distiller.provider`, `distiller.model`, `distiller.api_key`. This allows running the agent on an expensive model (Claude Opus) while harvesting with a cheap model (Claude Haiku). If `distiller.provider` is not set, it inherits `provider`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 941.1107, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 778, + "mcp_result_bytes": 859, + "wire_bytes": 896, + "reported_used_tokens": 859, + "working_set_bytes": 294940672, + "peak_working_set_bytes": 295854080 + }, + { + "query": "first agent turn is slow because kimetsu proactive hook runs embedding inference", + "ranked": [ + "kimetsu-proactive-hooks", + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3P27FMRCY89DFW7MFKS", + "id": "01M1X6V7HNM710D6FZ0KDH5WXC", + "kind": "memory", + "score": 0.999568521976471, + "summary": "project:fact - [2026-09-07] [tags: kimetsu proactive hooks context injection] kimetsu's proactive context injection runs before each agent turn (pre-turn hook) and injects relevant memories into the system prompt prefix. The hook invocation adds latency to the first token: embedding inference + vector search + reranking + context formatting. On a cold start, this can be 1-3 seconds." + }, + { + "expansion_handle": "memory:01M1X6P2YZ4YRDGDVJV5XMKCP1", + "id": "01M1X6V7HNFS87HD26ZB609T2B", + "kind": "memory", + "score": 0.9405298233032228, + "summary": "project:fact - [2026-09-07] [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 979.679, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1403, + "mcp_result_bytes": 1502, + "wire_bytes": 1539, + "reported_used_tokens": 1502, + "working_set_bytes": 294940672, + "peak_working_set_bytes": 295854080 + }, + { + "query": "make the kimetsu brain read-only for certain repos on a shared remote server", + "ranked": [ + "kimetsu-write-tools-gate", + "remote-ingest-split-roots", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3Q18YAX03ZC3P1KS0CF", + "id": "01M1X6V8GNP9TPXE2ZPS3DRJ8P", + "kind": "memory", + "score": 0.997682809829712, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level \u2014 disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1X6NZK0NCCC1P19NW0KM4XV", + "id": "01M1X6V8GNJC3ZSEHMZK6QAWM7", + "kind": "memory", + "score": 0.9957050681114196, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1X6NZN0FVTV36T1B6KBFMKZ", + "id": "01M1X6V8GNY3CWACG49GQY0A04", + "kind": "memory", + "score": 0.9909282326698304, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 918.7103, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2725, + "mcp_result_bytes": 2890, + "wire_bytes": 2927, + "reported_used_tokens": 2890, + "working_set_bytes": 294940672, + "peak_working_set_bytes": 295854080 + }, + { + "query": "kimetsu FTS search misses 'deadlocking' when memory says 'deadlock'", + "ranked": [ + "kimetsu-query-stemming", + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3TM4S5HK5WSJCSCQQHF", + "id": "01M1X6V9CY304YPQSATXDBH499", + "kind": "memory", + "score": 0.9904030561447144, + "summary": "project:fact - [2026-09-07] [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression." + }, + { + "expansion_handle": "memory:01M1X6NZHYGSE0FR8XFDPNJP6E", + "id": "01M1X6V9CYDGV6A8EAKCXCTNXX", + "kind": "memory", + "score": 0.91664320230484, + "summary": "project:fact - [2026-09-07] [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure \u2014 `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1141.5236, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1363, + "mcp_result_bytes": 1478, + "wire_bytes": 1515, + "reported_used_tokens": 1478, + "working_set_bytes": 295002112, + "peak_working_set_bytes": 295911424 + }, + { + "query": "how does pool size affect retrieval recall and latency in the bench?", + "ranked": [ + "kimetsu-rerank-pool" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3VMMSAJGSK3MBD4B2GV", + "id": "01M1X6VAGJDM5F834J7KBRF6TZ", + "kind": "memory", + "score": 0.9998373985290528, + "summary": "project:fact - [tags: kimetsu reranker pool size ann retrieval] kimetsu's retrieval pipeline: ANN (approximate nearest neighbor) retrieves a pool of candidates, then the reranker reorders them, then the top-K are returned. The pool size (default 6 for production, 12 in bench) controls the recall-latency tradeoff: larger pool = higher recall = more reranker calls = more latency. For the jina-tiny reranker, pool 12 adds ~80ms vs pool 6." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 911.9508, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 813, + "mcp_result_bytes": 894, + "wire_bytes": 931, + "reported_used_tokens": 894, + "working_set_bytes": 295002112, + "peak_working_set_bytes": 295919616 + }, + { + "query": "second embedder in a remote bench run gets worse results than the first", + "ranked": [ + "kimetsu-bench-remote-embedder-singleton" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3WKNTTE5DK6PSVCEB7R", + "id": "01M1X6VBD3QGDKMJGS0GJTEGYZ", + "kind": "memory", + "score": 0.9939629435539246, + "summary": "project:fact - [2026-09-07] [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 913.0019, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 895, + "mcp_result_bytes": 976, + "wire_bytes": 1013, + "reported_used_tokens": 976, + "working_set_bytes": 295137280, + "peak_working_set_bytes": 296054784 + }, + { + "query": "what is the expected JSON schema for kimetsu brain bench dataset files?", + "ranked": [ + "kimetsu-eval-fixture-shape", + "testing-fixture-drift", + "kimetsu-mrr-metric", + "mcp-schema-validation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3XM0BN6GY22JB0THAHB", + "id": "01M1X6VC9WHKSDA5QHBCDQ11TC", + "kind": "memory", + "score": 0.9996767044067384, + "summary": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` \u2014 a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases)." + }, + { + "expansion_handle": "memory:01M1X6P2X1Q84DSXWGJTFEX3G2", + "id": "01M1X6VC9WZ413V6QH147HAHRK", + "kind": "memory", + "score": 0.9682880640029908, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + }, + { + "expansion_handle": "memory:01M1X6P3YTF6X09YDCSHS6P0JQ", + "id": "01M1X6VC9W9ZHJSP0MJB1PWJH3", + "kind": "memory", + "score": 0.8818408250808716, + "summary": "project:fact - [tags: kimetsu bench mrr recall metrics evaluation] kimetsu bench reports MRR (Mean Reciprocal Rank) and Recall@K. MRR is 1/rank_of_first_relevant_result, averaged across cases; it penalizes models that rank the correct answer 2nd or 3rd. Recall@K is the fraction of cases where at least one relevant answer appears in the top K." + }, + { + "expansion_handle": "memory:01M1X6P31407BS4T0BV7PGT98H", + "id": "01M1X6VC9WNWMF3KPJYDEJDQE1", + "kind": "memory", + "score": 0.6527947187423706, + "summary": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array \u2014 omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 944.4823, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2424, + "mcp_result_bytes": 2603, + "wire_bytes": 2640, + "reported_used_tokens": 2603, + "working_set_bytes": 295190528, + "peak_working_set_bytes": 296103936 + }, + { + "query": "what does MRR mean and how do I interpret a 0.01 difference between combos?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 947.7141, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 295305216, + "peak_working_set_bytes": 296218624 + }, + { + "query": "SQLITE_BUSY keeps appearing even with WAL mode enabled", + "ranked": [ + "sqlite-busy-timeout-wal", + "sqlite-wal-network-drive" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0A6JDJBPYZTYEXJX1EZ", + "id": "01M1X6VE4TQTVPV4Z3YNYETZ24", + "kind": "memory", + "score": 0.9982662796974182, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + }, + { + "expansion_handle": "memory:01M1X6P0CJBPZG72ZW2NCZ8ST6", + "id": "01M1X6VE4TDCZ7RKEB48C99FVW", + "kind": "memory", + "score": 0.7844027280807495, + "summary": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 961.9783, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1423, + "mcp_result_bytes": 1522, + "wire_bytes": 1559, + "reported_used_tokens": 1522, + "working_set_bytes": 295346176, + "peak_working_set_bytes": 296259584 + }, + { + "query": "my brain file got huge again right after I compacted it", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 897.7642999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 295350272, + "peak_working_set_bytes": 296259584 + }, + { + "query": "all my FTS queries stopped returning results after I changed the tokenizer config", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 952.4657, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 295350272, + "peak_working_set_bytes": 296263680 + }, + { + "query": "something is preventing the kimetsu binary from being replaced during update", + "ranked": [ + "kimetsu-daemon-lifecycle", + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3J1H7C1NPH0T2G7FWCC", + "id": "01M1X6VGXEWR2QDTSSCM44NPV0", + "kind": "memory", + "score": 0.9678457975387572, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1X6P08364QKMJ37XXNKFWJS", + "id": "01M1X6VGXE309S34A232KF1FNV", + "kind": "memory", + "score": 0.9395453929901124, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 0.6666666666666666, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 869.7433, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1657, + "mcp_result_bytes": 1756, + "wire_bytes": 1793, + "reported_used_tokens": 1756, + "working_set_bytes": 295354368, + "peak_working_set_bytes": 296267776 + }, + { + "query": "tool call results not appearing in the context \u2014 is the semantic floor too high?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 981.0944999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 295354368, + "peak_working_set_bytes": 296275968 + }, + { + "query": "CARGO_INCREMENTAL=0 in CI prevents a class of spurious compilation errors", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0QQQS1JZYGKXH9ZX9RW", + "id": "01M1X6VJQ35MG6TQYT9G2W695T", + "kind": "memory", + "score": 0.7995238304138184, + "summary": "project:fact - [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 951.0018, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 877, + "mcp_result_bytes": 958, + "wire_bytes": 995, + "reported_used_tokens": 958, + "working_set_bytes": 295354368, + "peak_working_set_bytes": 296275968 + }, + { + "query": "how do I check whether my Cargo workspace respects the MSRV constraint?", + "ranked": [ + "cargo-msrv", + "cargo-dev-dep-leak", + "cargo-patch-section", + "cargo-target-dir-sharing" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0TQV281HN337V782S05", + "id": "01M1X6VKMHD7RPPNB564J0X4Y2", + "kind": "memory", + "score": 0.9921064376831056, + "summary": "project:fact - [tags: cargo rust msrv edition compatibility] Set `rust-version` in each `Cargo.toml` to declare the minimum supported Rust version (MSRV). Cargo enforces this with `--check`: `cargo check` fails if the toolchain is older than `rust-version`. Keep MSRV as old as your oldest supported deployment target." + }, + { + "expansion_handle": "memory:01M1X6P0NMHGDXYV47S0SSW2XA", + "id": "01M1X6VKMH6KAFNPMDQTEA6C9X", + "kind": "memory", + "score": 0.887407660484314, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + }, + { + "expansion_handle": "memory:01M1X6P0SRM17TYNHXSXEWSP31", + "id": "01M1X6VKMH2PGN2DJK448NTH54", + "kind": "memory", + "score": 0.7220955491065979, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace \u2014 including transitive deps \u2014 that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1X6P0PQ9PDB9P815QJTY464", + "id": "01M1X6VKMHDRQTBQX91SFBY0ZQ", + "kind": "memory", + "score": 0.4095200598239898, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps \u2014 use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 937.0001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2682, + "mcp_result_bytes": 2821, + "wire_bytes": 2858, + "reported_used_tokens": 2821, + "working_set_bytes": 295354368, + "peak_working_set_bytes": 296275968 + }, + { + "query": "rusqlite connection opened but ON DELETE CASCADE cascade never fires", + "ranked": [ + "sqlite-foreign-keys-default-off" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0FMEPPEMPPJJ176K5CE", + "id": "01M1X6VMHXMTKFW9G4RVFVWR6M", + "kind": "memory", + "score": 0.9922945499420166, + "summary": "project:fact - [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting \u2014 every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 911.2325, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 735, + "mcp_result_bytes": 816, + "wire_bytes": 853, + "reported_used_tokens": 816, + "working_set_bytes": 295354368, + "peak_working_set_bytes": 296275968 + }, + { + "query": "I cannot connect to kimetsu-remote \u2014 something about TLS cert validation failed", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 893.6359, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 295354368, + "peak_working_set_bytes": 296275968 + }, + { + "query": "graceful shutdown fails because in-flight SQLite queries are still running when pool closes", + "ranked": [ + "tokio-shutdown-ordering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P2CZ739FX2TTZAGZGTRW", + "id": "01M1X6VPA5YD571Q2V9YWX0H3H", + "kind": "memory", + "score": 0.9996342658996582, + "summary": "project:fact - [2026-09-07] [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries \u2014 the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 889.4746, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 947, + "mcp_result_bytes": 1028, + "wire_bytes": 1065, + "reported_used_tokens": 1028, + "working_set_bytes": 295374848, + "peak_working_set_bytes": 296288256 + }, + { + "query": "kimetsu-remote response takes 8 seconds \u2014 which stage is slow?", + "ranked": [ + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P2YZ4YRDGDVJV5XMKCP1", + "id": "01M1X6VQ67PE24QFP63EED6JFH", + "kind": "memory", + "score": 0.9876242876052856, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 955.9803, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 858, + "mcp_result_bytes": 939, + "wire_bytes": 976, + "reported_used_tokens": 939, + "working_set_bytes": 295374848, + "peak_working_set_bytes": 296288256 + }, + { + "query": "git reflog to rescue accidentally deleted branch", + "ranked": [ + "git-reflog-rescue" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P23PDPP71F97F0Z839MX", + "id": "01M1X6VR41N967WTBNTVE501NM", + "kind": "memory", + "score": 0.998464822769165, + "summary": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone \u2014 they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only \u2014 remote reflog is not accessible via normal git commands." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 988.5865, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 761, + "mcp_result_bytes": 842, + "wire_bytes": 879, + "reported_used_tokens": 842, + "working_set_bytes": 295374848, + "peak_working_set_bytes": 296288256 + }, + { + "query": "git submodule --remote advances the pinned SHA unexpectedly", + "ranked": [ + "git-submodule-pinning", + "git-reflog-rescue", + "ci-secrets-masking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P22PHYS4T5P5F3PE7N1M", + "id": "01M1X6VS3HAD4N84TKQJ8WBWT8", + "kind": "memory", + "score": 0.9998551607131958, + "summary": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip \u2014 this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version." + }, + { + "expansion_handle": "memory:01M1X6P23PDPP71F97F0Z839MX", + "id": "01M1X6VS3HFAM2NS1YDCYY3PDK", + "kind": "memory", + "score": 0.8857361078262329, + "summary": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone \u2014 they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only \u2014 remote reflog is not accessible via normal git commands." + }, + { + "expansion_handle": "memory:01M1X6P3F12CWS8FE5S50H7TYH", + "id": "01M1X6VS3HQWZMYJKAH48Y9VQT", + "kind": "memory", + "score": 0.8434544205665588, + "summary": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output \u2014 but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 902.4643, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1771, + "mcp_result_bytes": 1888, + "wire_bytes": 1925, + "reported_used_tokens": 1888, + "working_set_bytes": 295391232, + "peak_working_set_bytes": 296296448 + }, + { + "query": "axum SSE streaming drops the last event when client disconnects", + "ranked": [ + "http-streaming-bodies" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P2JNVC441XJQFSMYT0SJ", + "id": "01M1X6VSZ0VRG0ZV1KP62KZ0X4", + "kind": "memory", + "score": 0.9926375150680542, + "summary": "project:fact - [2026-09-07] [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding \u2014 a chunk may split across frame boundaries." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 947.4878, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 859, + "mcp_result_bytes": 940, + "wire_bytes": 977, + "reported_used_tokens": 940, + "working_set_bytes": 295407616, + "peak_working_set_bytes": 296321024 + }, + { + "query": "how do I detect that I am running inside a git worktree vs the main checkout?", + "ranked": [ + "git-worktree-brain-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1YWTX8Y6NQRBF82KS3J", + "id": "01M1X6VTWP07Z6888CDJMS6EWB", + "kind": "memory", + "score": 0.9857924580574036, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root \u2014 if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 932.0207, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 881, + "mcp_result_bytes": 962, + "wire_bytes": 999, + "reported_used_tokens": 962, + "working_set_bytes": 295407616, + "peak_working_set_bytes": 296325120 + }, + { + "query": "ONNX Runtime intra-op threads causing CPU contention during parallel bench", + "ranked": [ + "onnx-ort-threading", + "tokio-blocking-in-async" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1XX3WCG6HCFJES7B7FT", + "id": "01M1X6VVSV0N016CEGSCKZJPJH", + "kind": "memory", + "score": 0.9999210834503174, + "summary": "project:fact - [tags: onnx ort thread-pool parallelism cpu] ORT (ONNX Runtime) creates its own inter-op and intra-op thread pools. In a multi-process bench setup, each child inherits these pools and they compete for CPU cores. Set `SessionOptionsBuilder::with_intra_threads(1).with_inter_threads(1)` if you're running many parallel bench processes \u2014 this sacrifices per-inference throughput for lower contention." + }, + { + "expansion_handle": "memory:01M1X6P24PG6KXJZQB4K5G8MKX", + "id": "01M1X6VVSVJH3GDRJ80J7JVT5G", + "kind": "memory", + "score": 0.5390238761901855, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 848.3687, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1328, + "mcp_result_bytes": 1427, + "wire_bytes": 1464, + "reported_used_tokens": 1427, + "working_set_bytes": 295444480, + "peak_working_set_bytes": 296357888 + }, + { + "query": "what is the right way to supply AWS session token alongside access key and secret?", + "ranked": [ + "aws-credentials-chain" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P34CEH6KSKA057ZQRBCP", + "id": "01M1X6VWMAPS9ZX7H8Q9BCFVC1", + "kind": "memory", + "score": 0.9493365287780762, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 904.8004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 895, + "mcp_result_bytes": 976, + "wire_bytes": 1013, + "reported_used_tokens": 976, + "working_set_bytes": 295444480, + "peak_working_set_bytes": 296357888 + } + ], + "id": "existing-development-100", + "dimension": "retrieval", + "tier": "hard", + "score": 0.8182539682539681, + "skipped": false, + "detail": "positive-recall@4=0.84 mrr=0.85 stale-hit=n/a resolution=n/a false-injection=0.538 (n=13) positive-n=197 negative-n=13 (210 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 0.8182539682539681, + 1 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 0.8182539682539681, + "n": 1, + "ci95": null + } + }, + "overall_index": 0.8182539682539681, + "scenario_weighted_index": 0.8182539682539681 +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-answerability/results/development/comparison.json b/docs/audits/2026-09-07-answerability/results/development/comparison.json new file mode 100644 index 0000000..145857a --- /dev/null +++ b/docs/audits/2026-09-07-answerability/results/development/comparison.json @@ -0,0 +1,155 @@ +{ + "schema_version": 1, + "status": "complete", + "harness": { + "path": "E:\\Kimetsu\\bench\\target\\release\\kbench.exe", + "sha256": "33e0a3fe1c19aaed4d2fc4aad66653d5feeef8ec67c554c2ba9a77b8a6f39c38", + "bytes": 9348096 + }, + "runner": { + "path": "E:\\tmp\\kimetsu-brain-hardening\\bench\\scripts\\compare_brainbench.py", + "sha256": "738bad9404a4ec2b911fff661967ca56f48b584dfeb22f83823c972a1498df37", + "bytes": 24527 + }, + "binaries": { + "baseline": { + "path": "E:\\tmp\\kimetsu-brain-hardening\\tmp-tests\\kimetsu-answerability-candidate.exe", + "sha256": "405d3483fe320e76b0ec776bf9ada3b7771852b04a73f70f3b5da377a43d31c3", + "bytes": 47151104 + }, + "candidate": { + "path": "E:\\tmp\\kimetsu-brain-hardening\\tmp-tests\\kimetsu-answerability-candidate.exe", + "sha256": "405d3483fe320e76b0ec776bf9ada3b7771852b04a73f70f3b5da377a43d31c3", + "bytes": 47151104 + } + }, + "datasets": [ + { + "path": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-retrieval\\development-100.json", + "sha256": "ff3c78f8b5dab7e9b2f10894af93f07965705502a4f9d3e8c45c529b9b6ca33f", + "bytes": 122806 + } + ], + "settings": { + "budget_tokens": 6000, + "dimensions": [ + "poisoning", + "render-contract", + "retrieval", + "workflow" + ], + "jobs": 1, + "warm_start": false, + "include_ambient": false, + "overrides": { + "KIMETSU_BRAIN_EMBEDDER": "bge-small-en-v1.5", + "KIMETSU_DETECT_CONFLICTS": "0", + "KIMETSU_RESOLVE_CONFLICTS": "0", + "FASTEMBED_CACHE_DIR": "E:/Kimetsu/.fastembed_cache", + "HF_HOME": "E:\\tmp\\kimetsu-brain-hardening/tmp-tests/hf-home" + }, + "baseline_threads": 0, + "candidate_threads": 0, + "baseline_reranker": "ms-marco-tinybert-l-2-v2", + "candidate_reranker": "ms-marco-tinybert-l-2-v2", + "baseline_rerank_floor": 0.3, + "candidate_rerank_floor": 0.3 + }, + "runs": [ + { + "label": "baseline", + "repeat": 1, + "intra_threads_override": null, + "rerank_floor_override": "0.3", + "explicit_fact_guard_override": "false", + "reranker_override": "ms-marco-tinybert-l-2-v2", + "wall_seconds": 217.23192869999912, + "report_file": "1-baseline.json" + }, + { + "label": "candidate", + "repeat": 1, + "intra_threads_override": null, + "rerank_floor_override": "0.3", + "explicit_fact_guard_override": "true", + "reranker_override": "ms-marco-tinybert-l-2-v2", + "wall_seconds": 195.58686299994588, + "report_file": "1-candidate.json" + } + ], + "comparison": { + "measurement_summary": { + "baseline": { + "unique_queries": 210, + "query_observations": 210, + "positive_queries": 197, + "negative_queries": 13, + "stale_queries": 0, + "positive_recall_at_4": 0.8417935702199661, + "positive_hit_at_4": 0.8578680203045685, + "positive_mrr": 0.850253807106599, + "negative_injection_rate": 0.5384615384615384, + "stale_injection_rate": null, + "first_query_mean_ms": 1064.0558, + "subsequent_query_p50_ms": 914.585, + "subsequent_query_p95_ms": 980.4788, + "subsequent_observations": 209, + "mean_model_text_bytes": 1167.5666666666666, + "mean_mcp_result_bytes": 1262.395238095238, + "memory_observations": 210, + "mean_mcp_working_set_bytes": 287334263.46666664, + "max_mcp_peak_working_set_bytes": 293224448, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + }, + "candidate": { + "unique_queries": 210, + "query_observations": 210, + "positive_queries": 197, + "negative_queries": 13, + "stale_queries": 0, + "positive_recall_at_4": 0.8417935702199661, + "positive_hit_at_4": 0.8578680203045685, + "positive_mrr": 0.850253807106599, + "negative_injection_rate": 0.5384615384615384, + "stale_injection_rate": null, + "first_query_mean_ms": 1041.7926, + "subsequent_query_p50_ms": 912.2396, + "subsequent_query_p95_ms": 988.5865, + "subsequent_observations": 209, + "mean_model_text_bytes": 1167.5666666666666, + "mean_mcp_result_bytes": 1262.395238095238, + "memory_observations": 210, + "mean_mcp_working_set_bytes": 289011829.0285714, + "max_mcp_peak_working_set_bytes": 296357888, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + } + }, + "by_dimension": { + "retrieval": { + "n_scenarios": 1, + "baseline": 0.8182539682539681, + "candidate": 0.8182539682539681, + "mean_delta": 0.0, + "ci95": null, + "wins": 0, + "ties": 1, + "losses": 0 + } + }, + "scenarios": [ + { + "identity": "retrieval/existing-development-100", + "dimension": "retrieval", + "baseline": 0.8182539682539681, + "candidate": 0.8182539682539681, + "delta": 0.0 + } + ], + "unpaired_scenarios": [], + "unpaired_details": [], + "baseline_errors": 0, + "candidate_errors": 0, + "repeats": 1, + "uncertainty_note": "Exploratory paired bootstrap over scenario IDs after averaging repeats; correlated task families require a separate grouped holdout." + } +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-answerability/results/development/comparison.md b/docs/audits/2026-09-07-answerability/results/development/comparison.md new file mode 100644 index 0000000..7d0dd87 --- /dev/null +++ b/docs/audits/2026-09-07-answerability/results/development/comparison.md @@ -0,0 +1,26 @@ +# Paired BrainBench comparison + +Same harness and fixture; run order alternates. Positive delta favors the candidate. + +| Dimension | Scenarios | Baseline | Candidate | Delta | Exploratory 95% interval | +|---|---:|---:|---:|---:|---| +| retrieval | 1 | 0.818 | 0.818 | +0.000 | n/a | + +Errors: baseline 0, candidate 0. +Unpaired/skipped scenarios: 0. + +Exploratory paired bootstrap over scenario IDs after averaging repeats; correlated task families require a separate grouped holdout. + +Wall times include process/model startup, corpus seeding and queries; they are not warm inference latency. + +baseline: mean complete-run time 217.23 s (1 repeats). +candidate: mean complete-run time 195.59 s (1 repeats). + +Query measurements through persistent MCP (subsequent queries reuse the process): + +| Build | Positive hit@4 | Positive recall@4 | False injection | Subsequent p50 / p95 ms | Mean MCP result bytes | Peak MCP working set MiB | +|---|---:|---:|---:|---:|---:|---:| +| baseline | 0.858 | 0.842 | 0.538 | 914.585 / 980.479 | 1262.395 | 279.641 | +| candidate | 0.858 | 0.842 | 0.538 | 912.240 / 988.587 | 1262.395 | 282.629 | + +Measured bytes include JSON escaping; reported token estimates are retained per query but may use different accounting rules across builds. Query timing excludes the separately recorded MCP initialization and corpus seeding. diff --git a/docs/audits/2026-09-07-answerability/results/missing-fact-development/1-baseline.json b/docs/audits/2026-09-07-answerability/results/missing-fact-development/1-baseline.json new file mode 100644 index 0000000..0ab3a84 --- /dev/null +++ b/docs/audits/2026-09-07-answerability/results/missing-fact-development/1-baseline.json @@ -0,0 +1,2130 @@ +{ + "generated_at": "2026-09-07T05:54:19.6857432Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-retrieval\\validation-frozen.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "Which TCP port should I connect to for Copper staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6VYTF4626NZZFD75CCKA3", + "id": "01M1X6W0N97TBE3SQVHD9EM7X9", + "kind": "memory", + "score": 0.9998394250869752, + "summary": "project:fact - Copper staging HTTP listener binds TCP port 6319. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1792.5176000000001, + "first_query": true, + "server_startup_ms": 72.62540000000001, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 636710912, + "peak_working_set_bytes": 684904448 + }, + { + "query": "Where should Copper diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6VYTXGJW5YTTAPBJZXW18", + "id": "01M1X6W115XE2E9K3FTRTCDYJ3", + "kind": "memory", + "score": 0.9978247880935668, + "summary": "project:fact - Copper diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X6VYWD6E0MSWCQX73RVVJT", + "id": "01M1X6W115V7WSZB2QY79FQ9WD", + "kind": "memory", + "score": 0.6390834450721741, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 352.26869999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 754, + "mcp_result_bytes": 853, + "wire_bytes": 888, + "reported_used_tokens": 853, + "working_set_bytes": 637181952, + "peak_working_set_bytes": 684904448 + }, + { + "query": "Which command rolls back Copper to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6VYV9SPY5S1WTD6WD1KY3", + "id": "01M1X6W1C1S7JC1M7N0613RSRA", + "kind": "memory", + "score": 0.9998852014541626, + "summary": "project:fact - To roll back Copper to the previous release, run `copperctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 346.6184, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 637370368, + "peak_working_set_bytes": 684904448 + }, + { + "query": "Which region hosts Copper production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6VYVNHJ2ZEZSRYRGWG6DZ", + "id": "01M1X6W1PWQY1HRK2GX4QXWFS2", + "kind": "memory", + "score": 0.9999468326568604, + "summary": "project:fact - Copper production runs in region eu-north-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 344.7042, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 609, + "reported_used_tokens": 574, + "working_set_bytes": 637485056, + "peak_working_set_bytes": 684904448 + }, + { + "query": "At what UTC time do daily Copper database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6VYW06MCYZPC4JY0ATTDA", + "id": "01M1X6W21KWV30TPJ5YVSNZHKG", + "kind": "memory", + "score": 0.9999791383743286, + "summary": "project:fact - Copper daily database backups start at 02:40 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 350.64869999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 587, + "reported_used_tokens": 552, + "working_set_bytes": 637661184, + "peak_working_set_bytes": 684904448 + }, + { + "query": "Which database and journal mode does Copper use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6VYWD6E0MSWCQX73RVVJT", + "id": "01M1X6W2CV4QR72H9J19YRS7B0", + "kind": "memory", + "score": 0.9999594688415528, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 358.66540000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 637722624, + "peak_working_set_bytes": 684904448 + }, + { + "query": "\u00bfA qu\u00e9 puerto TCP debo conectarme para staging de Copper?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6VYTF4626NZZFD75CCKA3", + "id": "01M1X6W2R01F4C03322DRH6DJ8", + "kind": "memory", + "score": 0.9998682737350464, + "summary": "project:fact - Copper staging HTTP listener binds TCP port 6319. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 358.5179, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 640077824, + "peak_working_set_bytes": 684904448 + }, + { + "query": "\u00bfD\u00f3nde deben escribirse los mensajes de diagn\u00f3stico de Copper?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6VYTXGJW5YTTAPBJZXW18", + "id": "01M1X6W333YJCYQNRHMN856ES0", + "kind": "memory", + "score": 0.9995033740997314, + "summary": "project:fact - Copper diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 363.23870000000005, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 488, + "mcp_result_bytes": 569, + "wire_bytes": 604, + "reported_used_tokens": 569, + "working_set_bytes": 640528384, + "peak_working_set_bytes": 684904448 + }, + { + "query": "\u00bfQu\u00e9 comando revierte Copper a la versi\u00f3n anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6VYV9SPY5S1WTD6WD1KY3", + "id": "01M1X6W3ECDG36CN2D0SRHAPWF", + "kind": "memory", + "score": 0.9887914657592772, + "summary": "project:fact - To roll back Copper to the previous release, run `copperctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 361.0142, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 616, + "reported_used_tokens": 580, + "working_set_bytes": 640536576, + "peak_working_set_bytes": 684904448 + }, + { + "query": "\u00bfEn qu\u00e9 regi\u00f3n est\u00e1 desplegado Copper en producci\u00f3n?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6VYVNHJ2ZEZSRYRGWG6DZ", + "id": "01M1X6W3SYNWD3QJCXJZBQ3WT6", + "kind": "memory", + "score": 0.9999468326568604, + "summary": "project:fact - Copper production runs in region eu-north-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 372.3626, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 640552960, + "peak_working_set_bytes": 684904448 + }, + { + "query": "\u00bfA qu\u00e9 hora UTC empiezan las copias diarias de la base de datos de Copper?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6VYW06MCYZPC4JY0ATTDA", + "id": "01M1X6W45VG93628VB8HX2XA9C", + "kind": "memory", + "score": 0.9999747276306152, + "summary": "project:fact - Copper daily database backups start at 02:40 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 373.41380000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 640823296, + "peak_working_set_bytes": 684904448 + }, + { + "query": "\u00bfQu\u00e9 base de datos y modo de registro usa Copper para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6VYWD6E0MSWCQX73RVVJT", + "id": "01M1X6W4GZV2KBQPXGR4V2767R", + "kind": "memory", + "score": 0.9983224272727966, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 355.87390000000005, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 640851968, + "peak_working_set_bytes": 684904448 + }, + { + "query": "What authentication password is configured for the Copper staging listener?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6VYTF4626NZZFD75CCKA3", + "id": "01M1X6W4W49ZXJP95V4ARSADYQ", + "kind": "memory", + "score": 0.9784963130950928, + "summary": "project:fact - Copper staging HTTP listener binds TCP port 6319. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 351.9925, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 615, + "reported_used_tokens": 579, + "working_set_bytes": 640929792, + "peak_working_set_bytes": 684904448 + }, + { + "query": "What encryption key protects the Copper database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 352.9818, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 646758400, + "peak_working_set_bytes": 684904448 + }, + { + "query": "How many production replicas run in the Copper deployment region?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6VYVNHJ2ZEZSRYRGWG6DZ", + "id": "01M1X6W5JG006RJDNN1S6RBR3C", + "kind": "memory", + "score": 0.8394170999526978, + "summary": "project:fact - Copper production runs in region eu-north-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 366.6964, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 646803456, + "peak_working_set_bytes": 684904448 + }, + { + "query": "\u00bfQu\u00e9 contrase\u00f1a exige el servidor de staging de Copper?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6VYTF4626NZZFD75CCKA3", + "id": "01M1X6W5XNG54HFV390E36YMW5", + "kind": "memory", + "score": 0.898059606552124, + "summary": "project:fact - Copper staging HTTP listener binds TCP port 6319. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 355.79110000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 497, + "mcp_result_bytes": 578, + "wire_bytes": 614, + "reported_used_tokens": 578, + "working_set_bytes": 646946816, + "peak_working_set_bytes": 684904448 + }, + { + "query": "\u00bfCu\u00e1ntos d\u00edas se conservan las copias de seguridad de Copper?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6VYW06MCYZPC4JY0ATTDA", + "id": "01M1X6W68R8T1VB0MJ2JSFEEFN", + "kind": "memory", + "score": 0.9550348520278932, + "summary": "project:fact - Copper daily database backups start at 02:40 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 362.2124, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 647012352, + "peak_working_set_bytes": 684904448 + }, + { + "query": "\u00bfQu\u00e9 versi\u00f3n de SQLite requiere Copper?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6VYWD6E0MSWCQX73RVVJT", + "id": "01M1X6W6M0TW6SBRY68JPS68A5", + "kind": "memory", + "score": 0.6008884310722351, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 350.2858, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 647061504, + "peak_working_set_bytes": 684904448 + } + ], + "id": "copper-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 0.7222222222222222, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.833 (n=6) positive-n=12 negative-n=6 (18 queries)" + }, + { + "observations": [ + { + "query": "Which TCP port should I connect to for Willow staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6W80485CMCCPMETKBZ0SX", + "id": "01M1X6W9SD69T8C5W8A5FA3Y35", + "kind": "memory", + "score": 0.9999420642852784, + "summary": "project:fact - Willow staging HTTP listener binds TCP port 7421. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1769.1063, + "first_query": true, + "server_startup_ms": 72.8164, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 633749504, + "peak_working_set_bytes": 685101056 + }, + { + "query": "Where should Willow diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6W80M86SH2X85WHMQTBT2", + "id": "01M1X6WA4HR1APZXPPFK6SYZFH", + "kind": "memory", + "score": 0.9971635937690736, + "summary": "project:fact - Willow diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X6W821WEPBVZKB1N95W3VJ", + "id": "01M1X6WA4H6138J0Z6KNGAWAVG", + "kind": "memory", + "score": 0.8971153497695923, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 347.5552, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 754, + "mcp_result_bytes": 853, + "wire_bytes": 888, + "reported_used_tokens": 853, + "working_set_bytes": 634281984, + "peak_working_set_bytes": 685101056 + }, + { + "query": "Which command rolls back Willow to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6W80ZW8B16PYR088WFEPF", + "id": "01M1X6WAFCYJA3725GZ9MKWC9G", + "kind": "memory", + "score": 0.9998542070388794, + "summary": "project:fact - To roll back Willow to the previous release, run `willowctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 352.1509, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 634376192, + "peak_working_set_bytes": 685101056 + }, + { + "query": "Which region hosts Willow production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6W81AHS659WR9H3NH38Y3", + "id": "01M1X6WAT99Q24ZFZNDZQ6GRH9", + "kind": "memory", + "score": 0.9999713897705078, + "summary": "project:fact - Willow production runs in region us-west-2. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 341.2932, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 634396672, + "peak_working_set_bytes": 685101056 + }, + { + "query": "At what UTC time do daily Willow database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6W81NG5523YCKRTEHZK4F", + "id": "01M1X6WB53JHH3FGT87H3BVXPJ", + "kind": "memory", + "score": 0.9999792575836182, + "summary": "project:fact - Willow daily database backups start at 04:15 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 359.1488, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 587, + "reported_used_tokens": 552, + "working_set_bytes": 634556416, + "peak_working_set_bytes": 685101056 + }, + { + "query": "Which database and journal mode does Willow use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6W821WEPBVZKB1N95W3VJ", + "id": "01M1X6WBGAS48VNTZY28767PH0", + "kind": "memory", + "score": 0.9999637603759766, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 355.4484, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 634601472, + "peak_working_set_bytes": 685101056 + }, + { + "query": "\u00bfA qu\u00e9 puerto TCP debo conectarme para staging de Willow?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6W80485CMCCPMETKBZ0SX", + "id": "01M1X6WBVMD71XYQK6VBADCTPS", + "kind": "memory", + "score": 0.9999486207962036, + "summary": "project:fact - Willow staging HTTP listener binds TCP port 7421. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 382.2115, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 636870656, + "peak_working_set_bytes": 685101056 + }, + { + "query": "\u00bfD\u00f3nde deben escribirse los mensajes de diagn\u00f3stico de Willow?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6W80M86SH2X85WHMQTBT2", + "id": "01M1X6WC7CKZAQF7M88RNJ4ZTV", + "kind": "memory", + "score": 0.9996838569641112, + "summary": "project:fact - Willow diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 353.988, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 488, + "mcp_result_bytes": 569, + "wire_bytes": 604, + "reported_used_tokens": 569, + "working_set_bytes": 637177856, + "peak_working_set_bytes": 685101056 + }, + { + "query": "\u00bfQu\u00e9 comando revierte Willow a la versi\u00f3n anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6W80ZW8B16PYR088WFEPF", + "id": "01M1X6WCJEESGZ6N6AJDP0AMAV", + "kind": "memory", + "score": 0.98951655626297, + "summary": "project:fact - To roll back Willow to the previous release, run `willowctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 352.9806, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 497, + "mcp_result_bytes": 578, + "wire_bytes": 614, + "reported_used_tokens": 578, + "working_set_bytes": 637231104, + "peak_working_set_bytes": 685101056 + }, + { + "query": "\u00bfEn qu\u00e9 regi\u00f3n est\u00e1 desplegado Willow en producci\u00f3n?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6W81AHS659WR9H3NH38Y3", + "id": "01M1X6WCXFE03SKBW6E97AJV8J", + "kind": "memory", + "score": 0.9999579191207886, + "summary": "project:fact - Willow production runs in region us-west-2. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 351.45029999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 637267968, + "peak_working_set_bytes": 685101056 + }, + { + "query": "\u00bfA qu\u00e9 hora UTC empiezan las copias diarias de la base de datos de Willow?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6W81NG5523YCKRTEHZK4F", + "id": "01M1X6WD8H2WN4ZTD26MJT04PJ", + "kind": "memory", + "score": 0.99997878074646, + "summary": "project:fact - Willow daily database backups start at 04:15 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 364.5579, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 469, + "mcp_result_bytes": 550, + "wire_bytes": 586, + "reported_used_tokens": 550, + "working_set_bytes": 637501440, + "peak_working_set_bytes": 685101056 + }, + { + "query": "\u00bfQu\u00e9 base de datos y modo de registro usa Willow para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6W821WEPBVZKB1N95W3VJ", + "id": "01M1X6WDM39QAP5J93GXRYSGZ9", + "kind": "memory", + "score": 0.999204695224762, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 362.1442, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 491, + "mcp_result_bytes": 572, + "wire_bytes": 608, + "reported_used_tokens": 572, + "working_set_bytes": 637534208, + "peak_working_set_bytes": 685101056 + }, + { + "query": "What authentication password is configured for the Willow staging listener?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6W80485CMCCPMETKBZ0SX", + "id": "01M1X6WDZ6XEC892Y4EM4RKWFR", + "kind": "memory", + "score": 0.9932246804237366, + "summary": "project:fact - Willow staging HTTP listener binds TCP port 7421. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 353.0434, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 615, + "reported_used_tokens": 579, + "working_set_bytes": 637566976, + "peak_working_set_bytes": 685101056 + }, + { + "query": "What encryption key protects the Willow database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 358.7416, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643379200, + "peak_working_set_bytes": 685101056 + }, + { + "query": "How many production replicas run in the Willow deployment region?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6W81AHS659WR9H3NH38Y3", + "id": "01M1X6WENDTRV75G218J1CY7E9", + "kind": "memory", + "score": 0.9349143505096436, + "summary": "project:fact - Willow production runs in region us-west-2. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 360.8331, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 643395584, + "peak_working_set_bytes": 685101056 + }, + { + "query": "\u00bfQu\u00e9 contrase\u00f1a exige el servidor de staging de Willow?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6W80485CMCCPMETKBZ0SX", + "id": "01M1X6WF0V07AW9HEBDND2PK3W", + "kind": "memory", + "score": 0.9681325554847716, + "summary": "project:fact - Willow staging HTTP listener binds TCP port 7421. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 369.6667, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 615, + "reported_used_tokens": 579, + "working_set_bytes": 643452928, + "peak_working_set_bytes": 685101056 + }, + { + "query": "\u00bfCu\u00e1ntos d\u00edas se conservan las copias de seguridad de Willow?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6W81NG5523YCKRTEHZK4F", + "id": "01M1X6WFE0D2V1FH1MM43PSE3E", + "kind": "memory", + "score": 0.9746375679969788, + "summary": "project:fact - Willow daily database backups start at 04:15 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 420.6737, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 643534848, + "peak_working_set_bytes": 685101056 + }, + { + "query": "\u00bfQu\u00e9 versi\u00f3n de SQLite requiere Willow?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6W821WEPBVZKB1N95W3VJ", + "id": "01M1X6WFT2EK90AMG94WSFHAWT", + "kind": "memory", + "score": 0.7611488103866577, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 381.91740000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 643727360, + "peak_working_set_bytes": 685101056 + } + ], + "id": "willow-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 0.7222222222222222, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.833 (n=6) positive-n=12 negative-n=6 (18 queries)" + }, + { + "observations": [ + { + "query": "Which TCP port should I connect to for Marble staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WH5KFBVXFH7JCSQKXN7N", + "id": "01M1X6WJZK5SG45Q2XYKCQC8R8", + "kind": "memory", + "score": 0.9996737241744996, + "summary": "project:fact - Marble staging HTTP listener binds TCP port 8533. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1782.3248, + "first_query": true, + "server_startup_ms": 81.5187, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 633012224, + "peak_working_set_bytes": 685076480 + }, + { + "query": "Where should Marble diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WH63Z8E8859BYQX8T299", + "id": "01M1X6WKAPMRF6TQSBDSNSCC7W", + "kind": "memory", + "score": 0.9971815347671508, + "summary": "project:fact - Marble diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X6WH7H9Z6WC7SKQJKA9KQM", + "id": "01M1X6WKAPRXYGBNS7NXSEYGX9", + "kind": "memory", + "score": 0.6012999415397644, + "summary": "project:fact - Marble stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 348.7635, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 754, + "mcp_result_bytes": 853, + "wire_bytes": 888, + "reported_used_tokens": 853, + "working_set_bytes": 633520128, + "peak_working_set_bytes": 685076480 + }, + { + "query": "Which command rolls back Marble to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WH6EBZRFDNYZVT3CKHF3", + "id": "01M1X6WKNKDBR1CNYN9VP9FQA3", + "kind": "memory", + "score": 0.9997585415840148, + "summary": "project:fact - To roll back Marble to the previous release, run `marblectl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 355.8333, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 633569280, + "peak_working_set_bytes": 685076480 + }, + { + "query": "Which region hosts Marble production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WH6T405TD74Y4JWMEE4W", + "id": "01M1X6WM14ZNQ8168FWS9EJ9MZ", + "kind": "memory", + "score": 0.999954104423523, + "summary": "project:fact - Marble production runs in region ap-south-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 377.5579, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 633597952, + "peak_working_set_bytes": 685076480 + }, + { + "query": "At what UTC time do daily Marble database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WH76P6HKX6RM89XS5BC8", + "id": "01M1X6WMCNNVG26BHZQTKNPGW1", + "kind": "memory", + "score": 0.999979853630066, + "summary": "project:fact - Marble daily database backups start at 01:25 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 351.1092, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 587, + "reported_used_tokens": 552, + "working_set_bytes": 633720832, + "peak_working_set_bytes": 685076480 + }, + { + "query": "Which database and journal mode does Marble use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WH7H9Z6WC7SKQJKA9KQM", + "id": "01M1X6WMQJRYKSPPCN2H1320T4", + "kind": "memory", + "score": 0.9999568462371826, + "summary": "project:fact - Marble stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 349.9595, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 633802752, + "peak_working_set_bytes": 685076480 + }, + { + "query": "\u00bfA qu\u00e9 puerto TCP debo conectarme para staging de Marble?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WH5KFBVXFH7JCSQKXN7N", + "id": "01M1X6WN2GE3213F4D2J1ATF9P", + "kind": "memory", + "score": 0.9998871088027954, + "summary": "project:fact - Marble staging HTTP listener binds TCP port 8533. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 354.5989, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 636735488, + "peak_working_set_bytes": 685076480 + }, + { + "query": "\u00bfD\u00f3nde deben escribirse los mensajes de diagn\u00f3stico de Marble?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WH63Z8E8859BYQX8T299", + "id": "01M1X6WNDNHA3BBGXRVSTWXQC6", + "kind": "memory", + "score": 0.9992142915725708, + "summary": "project:fact - Marble diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 354.1943, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 488, + "mcp_result_bytes": 569, + "wire_bytes": 604, + "reported_used_tokens": 569, + "working_set_bytes": 637349888, + "peak_working_set_bytes": 685076480 + }, + { + "query": "\u00bfQu\u00e9 comando revierte Marble a la versi\u00f3n anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WH6EBZRFDNYZVT3CKHF3", + "id": "01M1X6WNRTVDM4G9XDT7HETQVV", + "kind": "memory", + "score": 0.976457178592682, + "summary": "project:fact - To roll back Marble to the previous release, run `marblectl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 354.989, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 616, + "reported_used_tokens": 580, + "working_set_bytes": 637501440, + "peak_working_set_bytes": 685076480 + }, + { + "query": "\u00bfEn qu\u00e9 regi\u00f3n est\u00e1 desplegado Marble en producci\u00f3n?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WH6T405TD74Y4JWMEE4W", + "id": "01M1X6WP3YCYXD80FZ5P30JQCA", + "kind": "memory", + "score": 0.9999542236328124, + "summary": "project:fact - Marble production runs in region ap-south-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 357.1646, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 637575168, + "peak_working_set_bytes": 685076480 + }, + { + "query": "\u00bfA qu\u00e9 hora UTC empiezan las copias diarias de la base de datos de Marble?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WH76P6HKX6RM89XS5BC8", + "id": "01M1X6WPF09JZV24DWGVRWR62E", + "kind": "memory", + "score": 0.9999665021896362, + "summary": "project:fact - Marble daily database backups start at 01:25 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 366.478, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 638054400, + "peak_working_set_bytes": 685076480 + }, + { + "query": "\u00bfQu\u00e9 base de datos y modo de registro usa Marble para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WH7H9Z6WC7SKQJKA9KQM", + "id": "01M1X6WPTGDTBATW1MX4ZYFBCG", + "kind": "memory", + "score": 0.9953057169914246, + "summary": "project:fact - Marble stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 363.191, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 638078976, + "peak_working_set_bytes": 685076480 + }, + { + "query": "What authentication password is configured for the Marble staging listener?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WH5KFBVXFH7JCSQKXN7N", + "id": "01M1X6WQ5ZV4ECKPP9CNH6TSBP", + "kind": "memory", + "score": 0.9787366390228271, + "summary": "project:fact - Marble staging HTTP listener binds TCP port 8533. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 373.0707, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 615, + "reported_used_tokens": 579, + "working_set_bytes": 638222336, + "peak_working_set_bytes": 685076480 + }, + { + "query": "What encryption key protects the Marble database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 366.5888, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644038656, + "peak_working_set_bytes": 685076480 + }, + { + "query": "How many production replicas run in the Marble deployment region?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WH6T405TD74Y4JWMEE4W", + "id": "01M1X6WQWXKKX03ZWBAKY5WJZ9", + "kind": "memory", + "score": 0.8181904554367065, + "summary": "project:fact - Marble production runs in region ap-south-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 355.18899999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 644067328, + "peak_working_set_bytes": 685076480 + }, + { + "query": "\u00bfQu\u00e9 contrase\u00f1a exige el servidor de staging de Marble?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WH5KFBVXFH7JCSQKXN7N", + "id": "01M1X6WR86ZNC1NGZ0QY02BCZ9", + "kind": "memory", + "score": 0.8728806972503662, + "summary": "project:fact - Marble staging HTTP listener binds TCP port 8533. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 364.2946, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 615, + "reported_used_tokens": 579, + "working_set_bytes": 644182016, + "peak_working_set_bytes": 685076480 + }, + { + "query": "\u00bfCu\u00e1ntos d\u00edas se conservan las copias de seguridad de Marble?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WH76P6HKX6RM89XS5BC8", + "id": "01M1X6WRKQSEVG0FG6TCDTE280", + "kind": "memory", + "score": 0.9691649079322816, + "summary": "project:fact - Marble daily database backups start at 01:25 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 372.3377, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 644218880, + "peak_working_set_bytes": 685076480 + }, + { + "query": "\u00bfQu\u00e9 versi\u00f3n de SQLite requiere Marble?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 374.79659999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644444160, + "peak_working_set_bytes": 685076480 + } + ], + "id": "marble-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 0.7777777777777778, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.667 (n=6) positive-n=12 negative-n=6 (18 queries)" + }, + { + "observations": [ + { + "query": "Which TCP port should I connect to for Kestrel staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WTC3B4CC1MV6P9AMCKWM", + "id": "01M1X6WW6XC5HWH7GG96YRRE0M", + "kind": "memory", + "score": 0.9999393224716188, + "summary": "project:fact - Kestrel staging HTTP listener binds TCP port 9647. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1806.4251, + "first_query": true, + "server_startup_ms": 82.701, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 636485632, + "peak_working_set_bytes": 685027328 + }, + { + "query": "Where should Kestrel diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WTCJEBFY783Z0H883YSJ", + "id": "01M1X6WWJ4EFVRVY9SNX4Y0B39", + "kind": "memory", + "score": 0.9987480640411376, + "summary": "project:fact - Kestrel diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X6WTE0HZF58SY8864D6TKG", + "id": "01M1X6WWJ4EB6GMBHVGCF14JXW", + "kind": "memory", + "score": 0.9422296285629272, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 347.71020000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 756, + "mcp_result_bytes": 855, + "wire_bytes": 890, + "reported_used_tokens": 855, + "working_set_bytes": 638533632, + "peak_working_set_bytes": 685027328 + }, + { + "query": "Which command rolls back Kestrel to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WTCXN4X9VPN914H5G8BK", + "id": "01M1X6WWX1Y5V6QY6W8N5W92RB", + "kind": "memory", + "score": 0.9997856020927428, + "summary": "project:fact - To roll back Kestrel to the previous release, run `kestrelctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 353.4188, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 501, + "mcp_result_bytes": 582, + "wire_bytes": 617, + "reported_used_tokens": 582, + "working_set_bytes": 638812160, + "peak_working_set_bytes": 685027328 + }, + { + "query": "Which region hosts Kestrel production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WTDAAPJ9TWGJRVR1AN05", + "id": "01M1X6WX80S0M6SXJ5BDEDVBGZ", + "kind": "memory", + "score": 0.9999779462814332, + "summary": "project:fact - Kestrel production runs in region eu-west-3. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 345.63100000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 609, + "reported_used_tokens": 574, + "working_set_bytes": 638844928, + "peak_working_set_bytes": 685027328 + }, + { + "query": "At what UTC time do daily Kestrel database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WTDNZFPX3HR9G8QGR65V", + "id": "01M1X6WXJX916V9BE7C2JXWR1R", + "kind": "memory", + "score": 0.999980330467224, + "summary": "project:fact - Kestrel daily database backups start at 03:50 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 354.9054, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 472, + "mcp_result_bytes": 553, + "wire_bytes": 588, + "reported_used_tokens": 553, + "working_set_bytes": 639127552, + "peak_working_set_bytes": 685027328 + }, + { + "query": "Which database and journal mode does Kestrel use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WTE0HZF58SY8864D6TKG", + "id": "01M1X6WXY7C3JFA2TV8WBABR26", + "kind": "memory", + "score": 0.999975323677063, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 361.6535, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 639156224, + "peak_working_set_bytes": 685027328 + }, + { + "query": "\u00bfA qu\u00e9 puerto TCP debo conectarme para staging de Kestrel?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WTC3B4CC1MV6P9AMCKWM", + "id": "01M1X6WY9F827A6XSV517Y77BG", + "kind": "memory", + "score": 0.9999423027038574, + "summary": "project:fact - Kestrel staging HTTP listener binds TCP port 9647. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 362.04609999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 641765376, + "peak_working_set_bytes": 685027328 + }, + { + "query": "\u00bfD\u00f3nde deben escribirse los mensajes de diagn\u00f3stico de Kestrel?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WTCJEBFY783Z0H883YSJ", + "id": "01M1X6WYMX97AMDX43VRYFVD7A", + "kind": "memory", + "score": 0.9997678399086, + "summary": "project:fact - Kestrel diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 362.5642, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 489, + "mcp_result_bytes": 570, + "wire_bytes": 605, + "reported_used_tokens": 570, + "working_set_bytes": 641912832, + "peak_working_set_bytes": 685027328 + }, + { + "query": "\u00bfQu\u00e9 comando revierte Kestrel a la versi\u00f3n anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WTCXN4X9VPN914H5G8BK", + "id": "01M1X6WZ09NES6A195AWWMM48M", + "kind": "memory", + "score": 0.9766082763671876, + "summary": "project:fact - To roll back Kestrel to the previous release, run `kestrelctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 365.9868, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 501, + "mcp_result_bytes": 582, + "wire_bytes": 618, + "reported_used_tokens": 582, + "working_set_bytes": 642027520, + "peak_working_set_bytes": 685027328 + }, + { + "query": "\u00bfEn qu\u00e9 regi\u00f3n est\u00e1 desplegado Kestrel en producci\u00f3n?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WTDAAPJ9TWGJRVR1AN05", + "id": "01M1X6WZCF49YM4ADRJXZMN0XW", + "kind": "memory", + "score": 0.999974250793457, + "summary": "project:fact - Kestrel production runs in region eu-west-3. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 388.0634, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 642060288, + "peak_working_set_bytes": 685027328 + }, + { + "query": "\u00bfA qu\u00e9 hora UTC empiezan las copias diarias de la base de datos de Kestrel?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WTDNZFPX3HR9G8QGR65V", + "id": "01M1X6WZQM63MSRWR02R1YHQ8K", + "kind": "memory", + "score": 0.9999799728393556, + "summary": "project:fact - Kestrel daily database backups start at 03:50 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 357.6211, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 472, + "mcp_result_bytes": 553, + "wire_bytes": 589, + "reported_used_tokens": 553, + "working_set_bytes": 642473984, + "peak_working_set_bytes": 685027328 + }, + { + "query": "\u00bfQu\u00e9 base de datos y modo de registro usa Kestrel para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WTE0HZF58SY8864D6TKG", + "id": "01M1X6X02T09C7GYHPTEWEQ2MR", + "kind": "memory", + "score": 0.9993937015533448, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 362.96450000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 642560000, + "peak_working_set_bytes": 685027328 + }, + { + "query": "What authentication password is configured for the Kestrel staging listener?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WTC3B4CC1MV6P9AMCKWM", + "id": "01M1X6X0E4AEW6528008DMP3Z0", + "kind": "memory", + "score": 0.9726881980895996, + "summary": "project:fact - Kestrel staging HTTP listener binds TCP port 9647. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 360.3472, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 616, + "reported_used_tokens": 580, + "working_set_bytes": 642605056, + "peak_working_set_bytes": 685027328 + }, + { + "query": "What encryption key protects the Kestrel database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 357.52909999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 646750208, + "peak_working_set_bytes": 685027328 + }, + { + "query": "How many production replicas run in the Kestrel deployment region?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WTDAAPJ9TWGJRVR1AN05", + "id": "01M1X6X14S8XXX7GRGQW7CDS1C", + "kind": "memory", + "score": 0.958982229232788, + "summary": "project:fact - Kestrel production runs in region eu-west-3. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 377.41859999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 646770688, + "peak_working_set_bytes": 685027328 + }, + { + "query": "\u00bfQu\u00e9 contrase\u00f1a exige el servidor de staging de Kestrel?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WTC3B4CC1MV6P9AMCKWM", + "id": "01M1X6X1GC3PFVBE560XRJQHNQ", + "kind": "memory", + "score": 0.9609549045562744, + "summary": "project:fact - Kestrel staging HTTP listener binds TCP port 9647. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 356.8262, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 616, + "reported_used_tokens": 580, + "working_set_bytes": 646873088, + "peak_working_set_bytes": 685027328 + }, + { + "query": "\u00bfCu\u00e1ntos d\u00edas se conservan las copias de seguridad de Kestrel?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WTDNZFPX3HR9G8QGR65V", + "id": "01M1X6X1VN0Y4MQE20A6T65SR6", + "kind": "memory", + "score": 0.9748653173446656, + "summary": "project:fact - Kestrel daily database backups start at 03:50 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 366.2564, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 472, + "mcp_result_bytes": 553, + "wire_bytes": 589, + "reported_used_tokens": 553, + "working_set_bytes": 646914048, + "peak_working_set_bytes": 685027328 + }, + { + "query": "\u00bfQu\u00e9 versi\u00f3n de SQLite requiere Kestrel?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WTE0HZF58SY8864D6TKG", + "id": "01M1X6X2743EGXA7PNE2C8P9KZ", + "kind": "memory", + "score": 0.6894522309303284, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 361.3078, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 647028736, + "peak_working_set_bytes": 685027328 + } + ], + "id": "kestrel-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 0.7222222222222222, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.833 (n=6) positive-n=12 negative-n=6 (18 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 2.9444444444444446, + 4 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 0.7361111111111112, + "n": 4, + "ci95": 0.027222222222222234 + } + }, + "overall_index": 0.7361111111111112, + "scenario_weighted_index": 0.7361111111111112 +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-answerability/results/missing-fact-development/1-candidate.json b/docs/audits/2026-09-07-answerability/results/missing-fact-development/1-candidate.json new file mode 100644 index 0000000..36c482b --- /dev/null +++ b/docs/audits/2026-09-07-answerability/results/missing-fact-development/1-candidate.json @@ -0,0 +1,1921 @@ +{ + "generated_at": "2026-09-07T05:55:02.439744Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-retrieval\\validation-frozen.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "Which TCP port should I connect to for Copper staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6X3NNJ5VZ5X50FH82MK7M", + "id": "01M1X6X5M0SWP7Y5Q7E4ZP51G2", + "kind": "memory", + "score": 0.9998394250869752, + "summary": "project:fact - Copper staging HTTP listener binds TCP port 6319. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1883.5029, + "first_query": true, + "server_startup_ms": 73.5735, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 631738368, + "peak_working_set_bytes": 684945408 + }, + { + "query": "Where should Copper diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6X3P5Y954RYW773ATN8RB", + "id": "01M1X6X60827RCN3Q5T19MM99S", + "kind": "memory", + "score": 0.9978247880935668, + "summary": "project:fact - Copper diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X6X3QK8NSW5AR6PRD0Z5D6", + "id": "01M1X6X608N2YKP8FJNTGJKXN2", + "kind": "memory", + "score": 0.6390834450721741, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 378.62050000000005, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 754, + "mcp_result_bytes": 853, + "wire_bytes": 888, + "reported_used_tokens": 853, + "working_set_bytes": 632242176, + "peak_working_set_bytes": 684945408 + }, + { + "query": "Which command rolls back Copper to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6X3PG4F88HQ1E7VPTKX60", + "id": "01M1X6X6C1XPGAE1083W5BR50F", + "kind": "memory", + "score": 0.9998852014541626, + "summary": "project:fact - To roll back Copper to the previous release, run `copperctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 374.528, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 634347520, + "peak_working_set_bytes": 684945408 + }, + { + "query": "Which region hosts Copper production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6X3PWK0TRN2V1X9J2JF9G", + "id": "01M1X6X6Q64SKXVXT9FYYX0Z71", + "kind": "memory", + "score": 0.9999468326568604, + "summary": "project:fact - Copper production runs in region eu-north-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 350.43809999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 609, + "reported_used_tokens": 574, + "working_set_bytes": 634515456, + "peak_working_set_bytes": 684945408 + }, + { + "query": "At what UTC time do daily Copper database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6X3Q8R53H7YCH0RQDV84F", + "id": "01M1X6X72CSY9YWWYNJVR4ZBNP", + "kind": "memory", + "score": 0.9999791383743286, + "summary": "project:fact - Copper daily database backups start at 02:40 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 360.62129999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 587, + "reported_used_tokens": 552, + "working_set_bytes": 634900480, + "peak_working_set_bytes": 684945408 + }, + { + "query": "Which database and journal mode does Copper use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6X3QK8NSW5AR6PRD0Z5D6", + "id": "01M1X6X7DS0GEJ916AV0QPJPSC", + "kind": "memory", + "score": 0.9999594688415528, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 370.178, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 635052032, + "peak_working_set_bytes": 684945408 + }, + { + "query": "\u00bfA qu\u00e9 puerto TCP debo conectarme para staging de Copper?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6X3NNJ5VZ5X50FH82MK7M", + "id": "01M1X6X7SEDR7RMZKZSHYGXZ42", + "kind": "memory", + "score": 0.9998682737350464, + "summary": "project:fact - Copper staging HTTP listener binds TCP port 6319. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 370.764, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 637345792, + "peak_working_set_bytes": 684945408 + }, + { + "query": "\u00bfD\u00f3nde deben escribirse los mensajes de diagn\u00f3stico de Copper?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6X3P5Y954RYW773ATN8RB", + "id": "01M1X6X84PKT9ZJR55REWY2REQ", + "kind": "memory", + "score": 0.9995033740997314, + "summary": "project:fact - Copper diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 360.773, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 488, + "mcp_result_bytes": 569, + "wire_bytes": 604, + "reported_used_tokens": 569, + "working_set_bytes": 637710336, + "peak_working_set_bytes": 684945408 + }, + { + "query": "\u00bfQu\u00e9 comando revierte Copper a la versi\u00f3n anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6X3PG4F88HQ1E7VPTKX60", + "id": "01M1X6X8G033SG4R9CE13A9KQF", + "kind": "memory", + "score": 0.9887914657592772, + "summary": "project:fact - To roll back Copper to the previous release, run `copperctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 357.7917, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 616, + "reported_used_tokens": 580, + "working_set_bytes": 637755392, + "peak_working_set_bytes": 684945408 + }, + { + "query": "\u00bfEn qu\u00e9 regi\u00f3n est\u00e1 desplegado Copper en producci\u00f3n?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6X3PWK0TRN2V1X9J2JF9G", + "id": "01M1X6X8V1E11G1DZRHVNYR43F", + "kind": "memory", + "score": 0.9999468326568604, + "summary": "project:fact - Copper production runs in region eu-north-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 353.02549999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 637890560, + "peak_working_set_bytes": 684945408 + }, + { + "query": "\u00bfA qu\u00e9 hora UTC empiezan las copias diarias de la base de datos de Copper?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6X3Q8R53H7YCH0RQDV84F", + "id": "01M1X6X96GN5V1TCN5WBJP40TB", + "kind": "memory", + "score": 0.9999747276306152, + "summary": "project:fact - Copper daily database backups start at 02:40 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 372.13960000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 638185472, + "peak_working_set_bytes": 684945408 + }, + { + "query": "\u00bfQu\u00e9 base de datos y modo de registro usa Copper para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6X3QK8NSW5AR6PRD0Z5D6", + "id": "01M1X6X9HQQ85WM4MPA3M888JT", + "kind": "memory", + "score": 0.9983224272727966, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 357.43789999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 638291968, + "peak_working_set_bytes": 684945408 + }, + { + "query": "What authentication password is configured for the Copper staging listener?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 359.9212, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 638402560, + "peak_working_set_bytes": 684945408 + }, + { + "query": "What encryption key protects the Copper database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 352.8064, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644169728, + "peak_working_set_bytes": 684945408 + }, + { + "query": "How many production replicas run in the Copper deployment region?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 373.1592, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644177920, + "peak_working_set_bytes": 684945408 + }, + { + "query": "\u00bfQu\u00e9 contrase\u00f1a exige el servidor de staging de Copper?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 379.70939999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644222976, + "peak_working_set_bytes": 684945408 + }, + { + "query": "\u00bfCu\u00e1ntos d\u00edas se conservan las copias de seguridad de Copper?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 360.5999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644575232, + "peak_working_set_bytes": 684945408 + }, + { + "query": "\u00bfQu\u00e9 versi\u00f3n de SQLite requiere Copper?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 352.8018, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644595712, + "peak_working_set_bytes": 684945408 + } + ], + "id": "copper-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.000 (n=6) positive-n=12 negative-n=6 (18 queries)" + }, + { + "observations": [ + { + "query": "Which TCP port should I connect to for Willow staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XD2VGTMJTPW3X02ZCKY7", + "id": "01M1X6XEX9ZETSNJASMCSD3JQT", + "kind": "memory", + "score": 0.9999420642852784, + "summary": "project:fact - Willow staging HTTP listener binds TCP port 7421. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1797.8110000000001, + "first_query": true, + "server_startup_ms": 78.7194, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 635105280, + "peak_working_set_bytes": 684879872 + }, + { + "query": "Where should Willow diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XD3CG3X6TZSH5Q8S53K1", + "id": "01M1X6XF8MND0EZG32TP1YPY1K", + "kind": "memory", + "score": 0.9971635937690736, + "summary": "project:fact - Willow diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X6XD4X69X1VHSS2AKWX55H", + "id": "01M1X6XF8MZ78NWGNPE17S30J8", + "kind": "memory", + "score": 0.8971153497695923, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 352.9713, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 754, + "mcp_result_bytes": 853, + "wire_bytes": 888, + "reported_used_tokens": 853, + "working_set_bytes": 635588608, + "peak_working_set_bytes": 684879872 + }, + { + "query": "Which command rolls back Willow to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XD3SG64YZRJ5269S9EAD", + "id": "01M1X6XFKTVVJ82B5AD7BMG03V", + "kind": "memory", + "score": 0.9998542070388794, + "summary": "project:fact - To roll back Willow to the previous release, run `willowctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 387.6426, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 637579264, + "peak_working_set_bytes": 684879872 + }, + { + "query": "Which region hosts Willow production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XD4530FQAWBFEEKWHR9D", + "id": "01M1X6XGSM6QN70SYZAYPZHPCA", + "kind": "memory", + "score": 0.9999713897705078, + "summary": "project:fact - Willow production runs in region us-west-2. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1174.5757999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 637612032, + "peak_working_set_bytes": 684879872 + }, + { + "query": "At what UTC time do daily Willow database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XD4HWE7BESKQ4CF5BP1T", + "id": "01M1X6XH4J9PFDKCF6ARQCANBN", + "kind": "memory", + "score": 0.9999792575836182, + "summary": "project:fact - Willow daily database backups start at 04:15 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 582.992, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 587, + "reported_used_tokens": 552, + "working_set_bytes": 637833216, + "peak_working_set_bytes": 684879872 + }, + { + "query": "Which database and journal mode does Willow use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XD4X69X1VHSS2AKWX55H", + "id": "01M1X6XHPR2DAAE5ZYZSSAHHZM", + "kind": "memory", + "score": 0.9999637603759766, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 426.6592, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 637853696, + "peak_working_set_bytes": 684879872 + }, + { + "query": "\u00bfA qu\u00e9 puerto TCP debo conectarme para staging de Willow?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XD2VGTMJTPW3X02ZCKY7", + "id": "01M1X6XJ45DQGTJ0FM07K03C6C", + "kind": "memory", + "score": 0.9999486207962036, + "summary": "project:fact - Willow staging HTTP listener binds TCP port 7421. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 509.51239999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 640262144, + "peak_working_set_bytes": 684879872 + }, + { + "query": "\u00bfD\u00f3nde deben escribirse los mensajes de diagn\u00f3stico de Willow?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XD3CG3X6TZSH5Q8S53K1", + "id": "01M1X6XJZH0GKS9S7CZ8RV0T9K", + "kind": "memory", + "score": 0.9996838569641112, + "summary": "project:fact - Willow diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 799.9312, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 488, + "mcp_result_bytes": 569, + "wire_bytes": 604, + "reported_used_tokens": 569, + "working_set_bytes": 640577536, + "peak_working_set_bytes": 684879872 + }, + { + "query": "\u00bfQu\u00e9 comando revierte Willow a la versi\u00f3n anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XD3SG64YZRJ5269S9EAD", + "id": "01M1X6XKD24PZR4EJT7FN9RPW2", + "kind": "memory", + "score": 0.98951655626297, + "summary": "project:fact - To roll back Willow to the previous release, run `willowctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.601, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 497, + "mcp_result_bytes": 578, + "wire_bytes": 614, + "reported_used_tokens": 578, + "working_set_bytes": 640663552, + "peak_working_set_bytes": 684879872 + }, + { + "query": "\u00bfEn qu\u00e9 regi\u00f3n est\u00e1 desplegado Willow en producci\u00f3n?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XD4530FQAWBFEEKWHR9D", + "id": "01M1X6XKR65B2Q0ZKS2BRA01JS", + "kind": "memory", + "score": 0.9999579191207886, + "summary": "project:fact - Willow production runs in region us-west-2. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 511.5941, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 640761856, + "peak_working_set_bytes": 684879872 + }, + { + "query": "\u00bfA qu\u00e9 hora UTC empiezan las copias diarias de la base de datos de Willow?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XD4HWE7BESKQ4CF5BP1T", + "id": "01M1X6XM88A82148ZMAX1DT86P", + "kind": "memory", + "score": 0.99997878074646, + "summary": "project:fact - Willow daily database backups start at 04:15 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 530.131, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 469, + "mcp_result_bytes": 550, + "wire_bytes": 586, + "reported_used_tokens": 550, + "working_set_bytes": 641073152, + "peak_working_set_bytes": 684879872 + }, + { + "query": "\u00bfQu\u00e9 base de datos y modo de registro usa Willow para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XD4X69X1VHSS2AKWX55H", + "id": "01M1X6XMRZMAFTN3BJZCYRY1YC", + "kind": "memory", + "score": 0.999204695224762, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 466.5879, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 491, + "mcp_result_bytes": 572, + "wire_bytes": 608, + "reported_used_tokens": 572, + "working_set_bytes": 641175552, + "peak_working_set_bytes": 684879872 + }, + { + "query": "What authentication password is configured for the Willow staging listener?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 773.0234, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 641261568, + "peak_working_set_bytes": 684879872 + }, + { + "query": "What encryption key protects the Willow database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 435.5034, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 647110656, + "peak_working_set_bytes": 684879872 + }, + { + "query": "How many production replicas run in the Willow deployment region?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 662.1342, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 647208960, + "peak_working_set_bytes": 684879872 + }, + { + "query": "\u00bfQu\u00e9 contrase\u00f1a exige el servidor de staging de Willow?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 356.422, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 647319552, + "peak_working_set_bytes": 684879872 + }, + { + "query": "\u00bfCu\u00e1ntos d\u00edas se conservan las copias de seguridad de Willow?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 365.8991, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 647499776, + "peak_working_set_bytes": 684879872 + }, + { + "query": "\u00bfQu\u00e9 versi\u00f3n de SQLite requiere Willow?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 597.4815, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 647507968, + "peak_working_set_bytes": 684879872 + } + ], + "id": "willow-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.000 (n=6) positive-n=12 negative-n=6 (18 queries)" + }, + { + "observations": [ + { + "query": "Which TCP port should I connect to for Marble staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XTQNED9YAVHNM15TKWEW", + "id": "01M1X6XWJHKXKEWEZVQVEJ5PHV", + "kind": "memory", + "score": 0.9996737241744996, + "summary": "project:fact - Marble staging HTTP listener binds TCP port 8533. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1791.9889, + "first_query": true, + "server_startup_ms": 73.4707, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 635297792, + "peak_working_set_bytes": 684945408 + }, + { + "query": "Where should Marble diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XTR72YBJ4PB7PQZ3J8HR", + "id": "01M1X6XWXTRNT2ASC0BRV82PYK", + "kind": "memory", + "score": 0.9971815347671508, + "summary": "project:fact - Marble diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X6XTSPW7WY7P8JJF27R42C", + "id": "01M1X6XWXT0JS2V5HCYTA41S47", + "kind": "memory", + "score": 0.6012999415397644, + "summary": "project:fact - Marble stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 349.1746, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 754, + "mcp_result_bytes": 853, + "wire_bytes": 888, + "reported_used_tokens": 853, + "working_set_bytes": 635752448, + "peak_working_set_bytes": 684945408 + }, + { + "query": "Which command rolls back Marble to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XTRK6X63J00C620VH1FQ", + "id": "01M1X6XX8RM3GKS2H1C1DM7E36", + "kind": "memory", + "score": 0.9997585415840148, + "summary": "project:fact - To roll back Marble to the previous release, run `marblectl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 357.39500000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 637992960, + "peak_working_set_bytes": 684945408 + }, + { + "query": "Which region hosts Marble production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XTRYY3705RAMVH6MM50M", + "id": "01M1X6XXKWZ3S1RZTK4XM0YY3A", + "kind": "memory", + "score": 0.999954104423523, + "summary": "project:fact - Marble production runs in region ap-south-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 349.84409999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 638017536, + "peak_working_set_bytes": 684945408 + }, + { + "query": "At what UTC time do daily Marble database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XTSA31QBTC5584FRZ438", + "id": "01M1X6XXZ1GZYTSR28BS75CQ45", + "kind": "memory", + "score": 0.999979853630066, + "summary": "project:fact - Marble daily database backups start at 01:25 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.9635, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 587, + "reported_used_tokens": 552, + "working_set_bytes": 638238720, + "peak_working_set_bytes": 684945408 + }, + { + "query": "Which database and journal mode does Marble use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XTSPW7WY7P8JJF27R42C", + "id": "01M1X6XYA18PD4Q2CZ4Y01NCVP", + "kind": "memory", + "score": 0.9999568462371826, + "summary": "project:fact - Marble stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 352.7874, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 638349312, + "peak_working_set_bytes": 684945408 + }, + { + "query": "\u00bfA qu\u00e9 puerto TCP debo conectarme para staging de Marble?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XTQNED9YAVHNM15TKWEW", + "id": "01M1X6XYNA5010MGK5164CHY1M", + "kind": "memory", + "score": 0.9998871088027954, + "summary": "project:fact - Marble staging HTTP listener binds TCP port 8533. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 364.5638, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 640847872, + "peak_working_set_bytes": 684945408 + }, + { + "query": "\u00bfD\u00f3nde deben escribirse los mensajes de diagn\u00f3stico de Marble?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XTR72YBJ4PB7PQZ3J8HR", + "id": "01M1X6XZ0QC8B80CXHKD841EGT", + "kind": "memory", + "score": 0.9992142915725708, + "summary": "project:fact - Marble diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 367.2181, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 488, + "mcp_result_bytes": 569, + "wire_bytes": 604, + "reported_used_tokens": 569, + "working_set_bytes": 641118208, + "peak_working_set_bytes": 684945408 + }, + { + "query": "\u00bfQu\u00e9 comando revierte Marble a la versi\u00f3n anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XTRK6X63J00C620VH1FQ", + "id": "01M1X6XZCS1MD6ZGJM2JEPVH3J", + "kind": "memory", + "score": 0.976457178592682, + "summary": "project:fact - To roll back Marble to the previous release, run `marblectl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 380.04949999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 616, + "reported_used_tokens": 580, + "working_set_bytes": 641257472, + "peak_working_set_bytes": 684945408 + }, + { + "query": "\u00bfEn qu\u00e9 regi\u00f3n est\u00e1 desplegado Marble en producci\u00f3n?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XTRYY3705RAMVH6MM50M", + "id": "01M1X6XZQT3HZN037XSMJRZNJW", + "kind": "memory", + "score": 0.9999542236328124, + "summary": "project:fact - Marble production runs in region ap-south-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 354.76829999999995, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 641486848, + "peak_working_set_bytes": 684945408 + }, + { + "query": "\u00bfA qu\u00e9 hora UTC empiezan las copias diarias de la base de datos de Marble?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XTSA31QBTC5584FRZ438", + "id": "01M1X6Y02YSN6WQZ9S5NC94W5N", + "kind": "memory", + "score": 0.9999665021896362, + "summary": "project:fact - Marble daily database backups start at 01:25 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 359.5495, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 641867776, + "peak_working_set_bytes": 684945408 + }, + { + "query": "\u00bfQu\u00e9 base de datos y modo de registro usa Marble para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XTSPW7WY7P8JJF27R42C", + "id": "01M1X6Y0E9VBNAXNC0RWH5KTH8", + "kind": "memory", + "score": 0.9953057169914246, + "summary": "project:fact - Marble stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 363.5991, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 641941504, + "peak_working_set_bytes": 684945408 + }, + { + "query": "What authentication password is configured for the Marble staging listener?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 362.6478, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 642183168, + "peak_working_set_bytes": 684945408 + }, + { + "query": "What encryption key protects the Marble database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 368.088, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 648101888, + "peak_working_set_bytes": 684945408 + }, + { + "query": "How many production replicas run in the Marble deployment region?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 368.7263, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 648167424, + "peak_working_set_bytes": 684945408 + }, + { + "query": "\u00bfQu\u00e9 contrase\u00f1a exige el servidor de staging de Marble?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 364.96079999999995, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 648192000, + "peak_working_set_bytes": 684945408 + }, + { + "query": "\u00bfCu\u00e1ntos d\u00edas se conservan las copias de seguridad de Marble?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 364.0675, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 648536064, + "peak_working_set_bytes": 684945408 + }, + { + "query": "\u00bfQu\u00e9 versi\u00f3n de SQLite requiere Marble?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 388.1593, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 648646656, + "peak_working_set_bytes": 684945408 + } + ], + "id": "marble-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.000 (n=6) positive-n=12 negative-n=6 (18 queries)" + }, + { + "observations": [ + { + "query": "Which TCP port should I connect to for Kestrel staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Y3ZKK4NVNBVQ4RVC0Z18", + "id": "01M1X6Y5TT3ZESQ3BF6YDCSFJ3", + "kind": "memory", + "score": 0.9999393224716188, + "summary": "project:fact - Kestrel staging HTTP listener binds TCP port 9647. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1786.8591000000001, + "first_query": true, + "server_startup_ms": 74.20309999999999, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 638173184, + "peak_working_set_bytes": 684789760 + }, + { + "query": "Where should Kestrel diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Y406VP0GS5SQS3J2CYB0", + "id": "01M1X6Y667NV5BY1XV28DF2YPM", + "kind": "memory", + "score": 0.9987480640411376, + "summary": "project:fact - Kestrel diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X6Y41MZ7BSF841D5WF7EK0", + "id": "01M1X6Y6675C92GGJ4JY76340W", + "kind": "memory", + "score": 0.9422296285629272, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 353.5111, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 756, + "mcp_result_bytes": 855, + "wire_bytes": 890, + "reported_used_tokens": 855, + "working_set_bytes": 640311296, + "peak_working_set_bytes": 684789760 + }, + { + "query": "Which command rolls back Kestrel to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Y40HQPXPBQAKK7X65NYG", + "id": "01M1X6Y6H5Z534E0DZ0325B3GG", + "kind": "memory", + "score": 0.9997856020927428, + "summary": "project:fact - To roll back Kestrel to the previous release, run `kestrelctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 359.4194, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 501, + "mcp_result_bytes": 582, + "wire_bytes": 617, + "reported_used_tokens": 582, + "working_set_bytes": 642461696, + "peak_working_set_bytes": 684789760 + }, + { + "query": "Which region hosts Kestrel production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Y40XGDYXN1JE1VCWJC40", + "id": "01M1X6Y6WGFGB4YS34D00N4DTC", + "kind": "memory", + "score": 0.9999779462814332, + "summary": "project:fact - Kestrel production runs in region eu-west-3. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 357.8371, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 609, + "reported_used_tokens": 574, + "working_set_bytes": 642621440, + "peak_working_set_bytes": 684789760 + }, + { + "query": "At what UTC time do daily Kestrel database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Y419AG550M415PJQKWGG", + "id": "01M1X6Y77Q7CDZV4XWATS1SV8K", + "kind": "memory", + "score": 0.999980330467224, + "summary": "project:fact - Kestrel daily database backups start at 03:50 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 360.0407, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 472, + "mcp_result_bytes": 553, + "wire_bytes": 588, + "reported_used_tokens": 553, + "working_set_bytes": 642875392, + "peak_working_set_bytes": 684789760 + }, + { + "query": "Which database and journal mode does Kestrel use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Y41MZ7BSF841D5WF7EK0", + "id": "01M1X6Y7KMTK5T8RH3G7BQ2F19", + "kind": "memory", + "score": 0.999975323677063, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 380.5959, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 643137536, + "peak_working_set_bytes": 684789760 + }, + { + "query": "\u00bfA qu\u00e9 puerto TCP debo conectarme para staging de Kestrel?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Y3ZKK4NVNBVQ4RVC0Z18", + "id": "01M1X6Y7YQP9NE0RZD0B8F19S0", + "kind": "memory", + "score": 0.9999423027038574, + "summary": "project:fact - Kestrel staging HTTP listener binds TCP port 9647. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 360.7013, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 645648384, + "peak_working_set_bytes": 684789760 + }, + { + "query": "\u00bfD\u00f3nde deben escribirse los mensajes de diagn\u00f3stico de Kestrel?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Y406VP0GS5SQS3J2CYB0", + "id": "01M1X6Y8A5V1N70N6BP4X5DD7A", + "kind": "memory", + "score": 0.9997678399086, + "summary": "project:fact - Kestrel diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 374.92350000000005, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 489, + "mcp_result_bytes": 570, + "wire_bytes": 605, + "reported_used_tokens": 570, + "working_set_bytes": 645791744, + "peak_working_set_bytes": 684789760 + }, + { + "query": "\u00bfQu\u00e9 comando revierte Kestrel a la versi\u00f3n anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Y40HQPXPBQAKK7X65NYG", + "id": "01M1X6Y8NQA1P7CS8J7C2QXXM3", + "kind": "memory", + "score": 0.9766082763671876, + "summary": "project:fact - To roll back Kestrel to the previous release, run `kestrelctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.6341, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 501, + "mcp_result_bytes": 582, + "wire_bytes": 618, + "reported_used_tokens": 582, + "working_set_bytes": 646041600, + "peak_working_set_bytes": 684789760 + }, + { + "query": "\u00bfEn qu\u00e9 regi\u00f3n est\u00e1 desplegado Kestrel en producci\u00f3n?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Y40XGDYXN1JE1VCWJC40", + "id": "01M1X6Y90W8F50YD8ST6KZXTKW", + "kind": "memory", + "score": 0.999974250793457, + "summary": "project:fact - Kestrel production runs in region eu-west-3. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 355.6515, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 646066176, + "peak_working_set_bytes": 684789760 + }, + { + "query": "\u00bfA qu\u00e9 hora UTC empiezan las copias diarias de la base de datos de Kestrel?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Y419AG550M415PJQKWGG", + "id": "01M1X6Y9C89TY5HEXM9ZBZNRE9", + "kind": "memory", + "score": 0.9999799728393556, + "summary": "project:fact - Kestrel daily database backups start at 03:50 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 369.5423, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 472, + "mcp_result_bytes": 553, + "wire_bytes": 589, + "reported_used_tokens": 553, + "working_set_bytes": 646369280, + "peak_working_set_bytes": 684789760 + }, + { + "query": "\u00bfQu\u00e9 base de datos y modo de registro usa Kestrel para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Y41MZ7BSF841D5WF7EK0", + "id": "01M1X6Y9QME8S7J76WY0B085PA", + "kind": "memory", + "score": 0.9993937015533448, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 394.824, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 646397952, + "peak_working_set_bytes": 684789760 + }, + { + "query": "What authentication password is configured for the Kestrel staging listener?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 399.1674, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 646524928, + "peak_working_set_bytes": 684789760 + }, + { + "query": "What encryption key protects the Kestrel database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 394.31899999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 650592256, + "peak_working_set_bytes": 684789760 + }, + { + "query": "How many production replicas run in the Kestrel deployment region?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 393.2575, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 650661888, + "peak_working_set_bytes": 684789760 + }, + { + "query": "\u00bfQu\u00e9 contrase\u00f1a exige el servidor de staging de Kestrel?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 356.7486, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 650739712, + "peak_working_set_bytes": 684789760 + }, + { + "query": "\u00bfCu\u00e1ntos d\u00edas se conservan las copias de seguridad de Kestrel?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 358.8879, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 650870784, + "peak_working_set_bytes": 684789760 + }, + { + "query": "\u00bfQu\u00e9 versi\u00f3n de SQLite requiere Kestrel?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 355.9336, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 650932224, + "peak_working_set_bytes": 684789760 + } + ], + "id": "kestrel-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.000 (n=6) positive-n=12 negative-n=6 (18 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 4.0, + 4 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 1.0, + "n": 4, + "ci95": 0.0 + } + }, + "overall_index": 1.0, + "scenario_weighted_index": 1.0 +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-answerability/results/missing-fact-development/comparison.json b/docs/audits/2026-09-07-answerability/results/missing-fact-development/comparison.json new file mode 100644 index 0000000..cf5d558 --- /dev/null +++ b/docs/audits/2026-09-07-answerability/results/missing-fact-development/comparison.json @@ -0,0 +1,179 @@ +{ + "schema_version": 1, + "status": "complete", + "harness": { + "path": "E:\\Kimetsu\\bench\\target\\release\\kbench.exe", + "sha256": "33e0a3fe1c19aaed4d2fc4aad66653d5feeef8ec67c554c2ba9a77b8a6f39c38", + "bytes": 9348096 + }, + "runner": { + "path": "E:\\tmp\\kimetsu-brain-hardening\\bench\\scripts\\compare_brainbench.py", + "sha256": "738bad9404a4ec2b911fff661967ca56f48b584dfeb22f83823c972a1498df37", + "bytes": 24527 + }, + "binaries": { + "baseline": { + "path": "E:\\tmp\\kimetsu-brain-hardening\\tmp-tests\\kimetsu-answerability-candidate.exe", + "sha256": "405d3483fe320e76b0ec776bf9ada3b7771852b04a73f70f3b5da377a43d31c3", + "bytes": 47151104 + }, + "candidate": { + "path": "E:\\tmp\\kimetsu-brain-hardening\\tmp-tests\\kimetsu-answerability-candidate.exe", + "sha256": "405d3483fe320e76b0ec776bf9ada3b7771852b04a73f70f3b5da377a43d31c3", + "bytes": 47151104 + } + }, + "datasets": [ + { + "path": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-retrieval\\validation-frozen.json", + "sha256": "1a09270e09a9521c38f9dca14dfebe4349fed0e10b1537a92857eb18d3f6bb6f", + "bytes": 31222 + } + ], + "settings": { + "budget_tokens": 6000, + "dimensions": [ + "poisoning", + "render-contract", + "retrieval", + "workflow" + ], + "jobs": 1, + "warm_start": false, + "include_ambient": false, + "overrides": { + "KIMETSU_BRAIN_EMBEDDER": "bge-small-en-v1.5", + "KIMETSU_DETECT_CONFLICTS": "0", + "KIMETSU_RESOLVE_CONFLICTS": "0", + "FASTEMBED_CACHE_DIR": "E:/Kimetsu/.fastembed_cache", + "HF_HOME": "E:\\tmp\\kimetsu-brain-hardening/tmp-tests/hf-home" + }, + "baseline_threads": 0, + "candidate_threads": 0, + "baseline_reranker": "mmarco-minilm-l12-v2-int8", + "candidate_reranker": "mmarco-minilm-l12-v2-int8", + "baseline_rerank_floor": 0.55, + "candidate_rerank_floor": 0.55 + }, + "runs": [ + { + "label": "baseline", + "repeat": 1, + "intra_threads_override": null, + "rerank_floor_override": "0.55", + "explicit_fact_guard_override": "false", + "reranker_override": "mmarco-minilm-l12-v2-int8", + "wall_seconds": 37.698994200036395, + "report_file": "1-baseline.json" + }, + { + "label": "candidate", + "repeat": 1, + "intra_threads_override": null, + "rerank_floor_override": "0.55", + "explicit_fact_guard_override": "true", + "reranker_override": "mmarco-minilm-l12-v2-int8", + "wall_seconds": 42.767015999997966, + "report_file": "1-candidate.json" + } + ], + "comparison": { + "measurement_summary": { + "baseline": { + "unique_queries": 72, + "query_observations": 72, + "positive_queries": 48, + "negative_queries": 24, + "stale_queries": 8, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": 1, + "positive_mrr": 1.0, + "negative_injection_rate": 0.7916666666666666, + "stale_injection_rate": 0, + "first_query_mean_ms": 1787.59345, + "subsequent_query_p50_ms": 358.5179, + "subsequent_query_p95_ms": 381.91740000000004, + "subsequent_observations": 68, + "mean_model_text_bytes": 486.7083333333333, + "mean_mcp_result_bytes": 567.4583333333334, + "memory_observations": 72, + "mean_mcp_working_set_bytes": 639916828.4444444, + "max_mcp_peak_working_set_bytes": 685101056, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + }, + "candidate": { + "unique_queries": 72, + "query_observations": 72, + "positive_queries": 48, + "negative_queries": 24, + "stale_queries": 8, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": 1, + "positive_mrr": 1.0, + "negative_injection_rate": 0, + "stale_injection_rate": 0, + "first_query_mean_ms": 1815.040475, + "subsequent_query_p50_ms": 364.5638, + "subsequent_query_p95_ms": 662.1342, + "subsequent_observations": 68, + "mean_model_text_bytes": 416.93055555555554, + "mean_mcp_result_bytes": 492.93055555555554, + "memory_observations": 72, + "mean_mcp_working_set_bytes": 641893432.8888888, + "max_mcp_peak_working_set_bytes": 684945408, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + } + }, + "by_dimension": { + "retrieval": { + "n_scenarios": 4, + "baseline": 0.7361111111111112, + "candidate": 1.0, + "mean_delta": 0.2638888888888889, + "ci95": [ + 0.2361111111111111, + 0.2777777777777778 + ], + "wins": 4, + "ties": 0, + "losses": 0 + } + }, + "scenarios": [ + { + "identity": "retrieval/copper-unseen-project", + "dimension": "retrieval", + "baseline": 0.7222222222222222, + "candidate": 1.0, + "delta": 0.2777777777777778 + }, + { + "identity": "retrieval/kestrel-unseen-project", + "dimension": "retrieval", + "baseline": 0.7222222222222222, + "candidate": 1.0, + "delta": 0.2777777777777778 + }, + { + "identity": "retrieval/marble-unseen-project", + "dimension": "retrieval", + "baseline": 0.7777777777777778, + "candidate": 1.0, + "delta": 0.2222222222222222 + }, + { + "identity": "retrieval/willow-unseen-project", + "dimension": "retrieval", + "baseline": 0.7222222222222222, + "candidate": 1.0, + "delta": 0.2777777777777778 + } + ], + "unpaired_scenarios": [], + "unpaired_details": [], + "baseline_errors": 0, + "candidate_errors": 0, + "repeats": 1, + "uncertainty_note": "Exploratory paired bootstrap over scenario IDs after averaging repeats; correlated task families require a separate grouped holdout." + } +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-answerability/results/missing-fact-development/comparison.md b/docs/audits/2026-09-07-answerability/results/missing-fact-development/comparison.md new file mode 100644 index 0000000..9b22cfa --- /dev/null +++ b/docs/audits/2026-09-07-answerability/results/missing-fact-development/comparison.md @@ -0,0 +1,26 @@ +# Paired BrainBench comparison + +Same harness and fixture; run order alternates. Positive delta favors the candidate. + +| Dimension | Scenarios | Baseline | Candidate | Delta | Exploratory 95% interval | +|---|---:|---:|---:|---:|---| +| retrieval | 4 | 0.736 | 1.000 | +0.264 | [+0.236, +0.278] | + +Errors: baseline 0, candidate 0. +Unpaired/skipped scenarios: 0. + +Exploratory paired bootstrap over scenario IDs after averaging repeats; correlated task families require a separate grouped holdout. + +Wall times include process/model startup, corpus seeding and queries; they are not warm inference latency. + +baseline: mean complete-run time 37.70 s (1 repeats). +candidate: mean complete-run time 42.77 s (1 repeats). + +Query measurements through persistent MCP (subsequent queries reuse the process): + +| Build | Positive hit@4 | Positive recall@4 | False injection | Subsequent p50 / p95 ms | Mean MCP result bytes | Peak MCP working set MiB | +|---|---:|---:|---:|---:|---:|---:| +| baseline | 1.000 | 1.000 | 0.792 | 358.518 / 381.917 | 567.458 | 653.363 | +| candidate | 1.000 | 1.000 | 0.000 | 364.564 / 662.134 | 492.931 | 653.215 | + +Measured bytes include JSON escaping; reported token estimates are retained per query but may use different accounting rules across builds. Query timing excludes the separately recorded MCP initialization and corpus seeding. diff --git a/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/1-baseline.json b/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/1-baseline.json new file mode 100644 index 0000000..60f81e2 --- /dev/null +++ b/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/1-baseline.json @@ -0,0 +1,2130 @@ +{ + "generated_at": "2026-09-07T05:57:27.2168915Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-retrieval\\validation-frozen.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "Which TCP port should I connect to for Copper staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71MNDYNM9PJ3X8SQS8CHB", + "id": "01M1X71PG5N0TEAT9HJRFXYR7N", + "kind": "memory", + "score": 0.9998394250869752, + "summary": "project:fact - Copper staging HTTP listener binds TCP port 6319. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1785.0068999999999, + "first_query": true, + "server_startup_ms": 73.7585, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 631394304, + "peak_working_set_bytes": 684806144 + }, + { + "query": "Where should Copper diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71MNY1V4CPMPAVNNB158V", + "id": "01M1X71PVBP7W3BT8661W6GPTX", + "kind": "memory", + "score": 0.9978247880935668, + "summary": "project:fact - Copper diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X71MQCE02FBBWMMZ0WYF1H", + "id": "01M1X71PVBP7SMFNYBGYGHNB23", + "kind": "memory", + "score": 0.6390834450721741, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 353.1834, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 754, + "mcp_result_bytes": 853, + "wire_bytes": 888, + "reported_used_tokens": 853, + "working_set_bytes": 631873536, + "peak_working_set_bytes": 684806144 + }, + { + "query": "Which command rolls back Copper to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71MP9FT994KQSQYJSTPXR", + "id": "01M1X71Q6H1BDWGJYXGEA71GTM", + "kind": "memory", + "score": 0.9998852014541626, + "summary": "project:fact - To roll back Copper to the previous release, run `copperctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 364.6724, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 632033280, + "peak_working_set_bytes": 684806144 + }, + { + "query": "Which region hosts Copper production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71MPNZ50242NN0MEQFN0R", + "id": "01M1X71QHQNFAJD51D8GNYFKQH", + "kind": "memory", + "score": 0.9999468326568604, + "summary": "project:fact - Copper production runs in region eu-north-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 353.53240000000005, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 609, + "reported_used_tokens": 574, + "working_set_bytes": 632131584, + "peak_working_set_bytes": 684806144 + }, + { + "query": "At what UTC time do daily Copper database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71MQ1XWWDD6X724YGF2DY", + "id": "01M1X71QWT2ARWZJSQ6YMNK13K", + "kind": "memory", + "score": 0.9999791383743286, + "summary": "project:fact - Copper daily database backups start at 02:40 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 351.0684, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 587, + "reported_used_tokens": 552, + "working_set_bytes": 632295424, + "peak_working_set_bytes": 684806144 + }, + { + "query": "Which database and journal mode does Copper use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71MQCE02FBBWMMZ0WYF1H", + "id": "01M1X71R7V2BXHZYSHPQ5DWWB2", + "kind": "memory", + "score": 0.9999594688415528, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 357.2317, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 632442880, + "peak_working_set_bytes": 684806144 + }, + { + "query": "\u00bfA qu\u00e9 puerto TCP debo conectarme para staging de Copper?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71MNDYNM9PJ3X8SQS8CHB", + "id": "01M1X71RK6SYKK12Q7JD9FHJ3W", + "kind": "memory", + "score": 0.9998682737350464, + "summary": "project:fact - Copper staging HTTP listener binds TCP port 6319. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 366.8775, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 634761216, + "peak_working_set_bytes": 684806144 + }, + { + "query": "\u00bfD\u00f3nde deben escribirse los mensajes de diagn\u00f3stico de Copper?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71MNY1V4CPMPAVNNB158V", + "id": "01M1X71RYQQNP5261B5JHET3H6", + "kind": "memory", + "score": 0.9995033740997314, + "summary": "project:fact - Copper diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 373.0024, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 488, + "mcp_result_bytes": 569, + "wire_bytes": 604, + "reported_used_tokens": 569, + "working_set_bytes": 635011072, + "peak_working_set_bytes": 684806144 + }, + { + "query": "\u00bfQu\u00e9 comando revierte Copper a la versi\u00f3n anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71MP9FT994KQSQYJSTPXR", + "id": "01M1X71SAZ3XZJCSQEHE5KFZ3S", + "kind": "memory", + "score": 0.9887914657592772, + "summary": "project:fact - To roll back Copper to the previous release, run `copperctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 381.6808, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 616, + "reported_used_tokens": 580, + "working_set_bytes": 635027456, + "peak_working_set_bytes": 684806144 + }, + { + "query": "\u00bfEn qu\u00e9 regi\u00f3n est\u00e1 desplegado Copper en producci\u00f3n?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71MPNZ50242NN0MEQFN0R", + "id": "01M1X71SP7CR8J0SWYN1FDBYQR", + "kind": "memory", + "score": 0.9999468326568604, + "summary": "project:fact - Copper production runs in region eu-north-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 360.3695, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 635088896, + "peak_working_set_bytes": 684806144 + }, + { + "query": "\u00bfA qu\u00e9 hora UTC empiezan las copias diarias de la base de datos de Copper?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71MQ1XWWDD6X724YGF2DY", + "id": "01M1X71T1EC2BAXM19SPBPQ8C2", + "kind": "memory", + "score": 0.9999747276306152, + "summary": "project:fact - Copper daily database backups start at 02:40 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 369.9482, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 635338752, + "peak_working_set_bytes": 684806144 + }, + { + "query": "\u00bfQu\u00e9 base de datos y modo de registro usa Copper para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71MQCE02FBBWMMZ0WYF1H", + "id": "01M1X71TD65MN5ZQGVG0HQB24T", + "kind": "memory", + "score": 0.9983224272727966, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 365.0951, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 635424768, + "peak_working_set_bytes": 684806144 + }, + { + "query": "What authentication password is configured for the Copper staging listener?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71MNDYNM9PJ3X8SQS8CHB", + "id": "01M1X71TR9ADJDCKH4GN7CF7BJ", + "kind": "memory", + "score": 0.9784963130950928, + "summary": "project:fact - Copper staging HTTP listener binds TCP port 6319. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 357.0822, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 615, + "reported_used_tokens": 579, + "working_set_bytes": 635473920, + "peak_working_set_bytes": 684806144 + }, + { + "query": "What encryption key protects the Copper database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 366.1139, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 641339392, + "peak_working_set_bytes": 684806144 + }, + { + "query": "How many production replicas run in the Copper deployment region?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71MPNZ50242NN0MEQFN0R", + "id": "01M1X71VETF4K10A8Z7035XTP9", + "kind": "memory", + "score": 0.8394170999526978, + "summary": "project:fact - Copper production runs in region eu-north-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 360.72900000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 641462272, + "peak_working_set_bytes": 684806144 + }, + { + "query": "\u00bfQu\u00e9 contrase\u00f1a exige el servidor de staging de Copper?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71MNDYNM9PJ3X8SQS8CHB", + "id": "01M1X71VTF4WKW4YFDX7DNCQC2", + "kind": "memory", + "score": 0.898059606552124, + "summary": "project:fact - Copper staging HTTP listener binds TCP port 6319. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 366.1863, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 497, + "mcp_result_bytes": 578, + "wire_bytes": 614, + "reported_used_tokens": 578, + "working_set_bytes": 641474560, + "peak_working_set_bytes": 684806144 + }, + { + "query": "\u00bfCu\u00e1ntos d\u00edas se conservan las copias de seguridad de Copper?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71MQ1XWWDD6X724YGF2DY", + "id": "01M1X71W5ZR1V1923YRCZPKMKN", + "kind": "memory", + "score": 0.9550348520278932, + "summary": "project:fact - Copper daily database backups start at 02:40 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 369.2679, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 641544192, + "peak_working_set_bytes": 684806144 + }, + { + "query": "\u00bfQu\u00e9 versi\u00f3n de SQLite requiere Copper?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71MQCE02FBBWMMZ0WYF1H", + "id": "01M1X71WJ46GBR0FRYE2N8BAMV", + "kind": "memory", + "score": 0.6008884310722351, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 387.2348, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 641548288, + "peak_working_set_bytes": 684806144 + } + ], + "id": "copper-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 0.7222222222222222, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.833 (n=6) positive-n=12 negative-n=6 (18 queries)" + }, + { + "observations": [ + { + "query": "Which TCP port should I connect to for Willow staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71XYTC0PCNBW56YEBN3C7", + "id": "01M1X71ZVN3Y3WNJC6YXQ2VP85", + "kind": "memory", + "score": 0.9999420642852784, + "summary": "project:fact - Willow staging HTTP listener binds TCP port 7421. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1866.3472, + "first_query": true, + "server_startup_ms": 73.1306, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 634626048, + "peak_working_set_bytes": 684875776 + }, + { + "query": "Where should Willow diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71XZCP833VMQ93Q81HFVQ", + "id": "01M1X7207HPNNHECR625HJ0VXY", + "kind": "memory", + "score": 0.9971635937690736, + "summary": "project:fact - Willow diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X71Y0V0NW250F15FYE8BHG", + "id": "01M1X7207HFNS3P6N3F4FX56E6", + "kind": "memory", + "score": 0.8971153497695923, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 361.4796, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 754, + "mcp_result_bytes": 853, + "wire_bytes": 888, + "reported_used_tokens": 853, + "working_set_bytes": 635150336, + "peak_working_set_bytes": 684875776 + }, + { + "query": "Which command rolls back Willow to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71XZRTP3HGW6ZSS65GPNV", + "id": "01M1X720JDP4YCM0N2S5CCZJKV", + "kind": "memory", + "score": 0.9998542070388794, + "summary": "project:fact - To roll back Willow to the previous release, run `willowctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 350.03319999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 635248640, + "peak_working_set_bytes": 684875776 + }, + { + "query": "Which region hosts Willow production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71Y056WNBS97ATYCHCPAT", + "id": "01M1X720XJAV2W9SV8XXZGSYBZ", + "kind": "memory", + "score": 0.9999713897705078, + "summary": "project:fact - Willow production runs in region us-west-2. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 354.5244, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 635297792, + "peak_working_set_bytes": 684875776 + }, + { + "query": "At what UTC time do daily Willow database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71Y0G0RNA2GTQAB4W727W", + "id": "01M1X7218VKJPJE5QAZVTPV9JK", + "kind": "memory", + "score": 0.9999792575836182, + "summary": "project:fact - Willow daily database backups start at 04:15 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 373.4737, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 587, + "reported_used_tokens": 552, + "working_set_bytes": 635449344, + "peak_working_set_bytes": 684875776 + }, + { + "query": "Which database and journal mode does Willow use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71Y0V0NW250F15FYE8BHG", + "id": "01M1X721MWSFS3GF5WVJEGATK6", + "kind": "memory", + "score": 0.9999637603759766, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 374.6258, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 635490304, + "peak_working_set_bytes": 684875776 + }, + { + "query": "\u00bfA qu\u00e9 puerto TCP debo conectarme para staging de Willow?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71XYTC0PCNBW56YEBN3C7", + "id": "01M1X72203D14A1EREZ0AQGFY6", + "kind": "memory", + "score": 0.9999486207962036, + "summary": "project:fact - Willow staging HTTP listener binds TCP port 7421. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 365.4285, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 637943808, + "peak_working_set_bytes": 684875776 + }, + { + "query": "\u00bfD\u00f3nde deben escribirse los mensajes de diagn\u00f3stico de Willow?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71XZCP833VMQ93Q81HFVQ", + "id": "01M1X722BBG13MB6G9BK3D8777", + "kind": "memory", + "score": 0.9996838569641112, + "summary": "project:fact - Willow diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.4494, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 488, + "mcp_result_bytes": 569, + "wire_bytes": 604, + "reported_used_tokens": 569, + "working_set_bytes": 638386176, + "peak_working_set_bytes": 684875776 + }, + { + "query": "\u00bfQu\u00e9 comando revierte Willow a la versi\u00f3n anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71XZRTP3HGW6ZSS65GPNV", + "id": "01M1X722PFHZ2JX9PZ77MY4ZRP", + "kind": "memory", + "score": 0.98951655626297, + "summary": "project:fact - To roll back Willow to the previous release, run `willowctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.20550000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 497, + "mcp_result_bytes": 578, + "wire_bytes": 614, + "reported_used_tokens": 578, + "working_set_bytes": 638496768, + "peak_working_set_bytes": 684875776 + }, + { + "query": "\u00bfEn qu\u00e9 regi\u00f3n est\u00e1 desplegado Willow en producci\u00f3n?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71Y056WNBS97ATYCHCPAT", + "id": "01M1X7231M4V1F4WEJ2G6HEZGJ", + "kind": "memory", + "score": 0.9999579191207886, + "summary": "project:fact - Willow production runs in region us-west-2. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 359.7043, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 638529536, + "peak_working_set_bytes": 684875776 + }, + { + "query": "\u00bfA qu\u00e9 hora UTC empiezan las copias diarias de la base de datos de Willow?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71Y0G0RNA2GTQAB4W727W", + "id": "01M1X723CYHRGEEZRQKJMSS2X3", + "kind": "memory", + "score": 0.99997878074646, + "summary": "project:fact - Willow daily database backups start at 04:15 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 364.096, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 469, + "mcp_result_bytes": 550, + "wire_bytes": 586, + "reported_used_tokens": 550, + "working_set_bytes": 638775296, + "peak_working_set_bytes": 684875776 + }, + { + "query": "\u00bfQu\u00e9 base de datos y modo de registro usa Willow para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71Y0V0NW250F15FYE8BHG", + "id": "01M1X723R98F1WXZ4JFR6PT42B", + "kind": "memory", + "score": 0.999204695224762, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 360.98789999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 491, + "mcp_result_bytes": 572, + "wire_bytes": 608, + "reported_used_tokens": 572, + "working_set_bytes": 638816256, + "peak_working_set_bytes": 684875776 + }, + { + "query": "What authentication password is configured for the Willow staging listener?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71XYTC0PCNBW56YEBN3C7", + "id": "01M1X7243PBDSNRBFYEE6HRJYE", + "kind": "memory", + "score": 0.9932246804237366, + "summary": "project:fact - Willow staging HTTP listener binds TCP port 7421. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 361.0675, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 615, + "reported_used_tokens": 579, + "working_set_bytes": 638885888, + "peak_working_set_bytes": 684875776 + }, + { + "query": "What encryption key protects the Willow database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 363.61339999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644792320, + "peak_working_set_bytes": 684875776 + }, + { + "query": "How many production replicas run in the Willow deployment region?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71Y056WNBS97ATYCHCPAT", + "id": "01M1X724V0K8WVX8KG8BZHQ9S6", + "kind": "memory", + "score": 0.9349143505096436, + "summary": "project:fact - Willow production runs in region us-west-2. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 387.5478, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 644861952, + "peak_working_set_bytes": 684875776 + }, + { + "query": "\u00bfQu\u00e9 contrase\u00f1a exige el servidor de staging de Willow?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71XYTC0PCNBW56YEBN3C7", + "id": "01M1X72568T2CXRDA6B2T9JTS5", + "kind": "memory", + "score": 0.9681325554847716, + "summary": "project:fact - Willow staging HTTP listener binds TCP port 7421. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 353.5084, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 615, + "reported_used_tokens": 579, + "working_set_bytes": 644902912, + "peak_working_set_bytes": 684875776 + }, + { + "query": "\u00bfCu\u00e1ntos d\u00edas se conservan las copias de seguridad de Willow?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71Y0G0RNA2GTQAB4W727W", + "id": "01M1X725HBBCDFYW38N5MQGTZB", + "kind": "memory", + "score": 0.9746375679969788, + "summary": "project:fact - Willow daily database backups start at 04:15 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 356.54019999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 644939776, + "peak_working_set_bytes": 684875776 + }, + { + "query": "\u00bfQu\u00e9 versi\u00f3n de SQLite requiere Willow?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71Y0V0NW250F15FYE8BHG", + "id": "01M1X725WE1SX6RPB9NMDCWFMF", + "kind": "memory", + "score": 0.7611488103866577, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 352.27950000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 644993024, + "peak_working_set_bytes": 684875776 + } + ], + "id": "willow-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 0.7222222222222222, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.833 (n=6) positive-n=12 negative-n=6 (18 queries)" + }, + { + "observations": [ + { + "query": "Which TCP port should I connect to for Marble staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X727GMVC50GY8DKEFRE4M5", + "id": "01M1X729BDRG9DJ6ECVPD966YZ", + "kind": "memory", + "score": 0.9996737241744996, + "summary": "project:fact - Marble staging HTTP listener binds TCP port 8533. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1773.0963, + "first_query": true, + "server_startup_ms": 72.9162, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 633303040, + "peak_working_set_bytes": 685076480 + }, + { + "query": "Where should Marble diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X727H33NDEE3FB9WX65292", + "id": "01M1X729PSCDW4PSKQ5Y4KXT03", + "kind": "memory", + "score": 0.9971815347671508, + "summary": "project:fact - Marble diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X727JJK6XWTG4J8QMGY1ZP", + "id": "01M1X729PSDH23287N122YW9WG", + "kind": "memory", + "score": 0.6012999415397644, + "summary": "project:fact - Marble stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 363.1606, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 754, + "mcp_result_bytes": 853, + "wire_bytes": 888, + "reported_used_tokens": 853, + "working_set_bytes": 633843712, + "peak_working_set_bytes": 685076480 + }, + { + "query": "Which command rolls back Marble to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X727HE87E87FQHNCMYMXE1", + "id": "01M1X72A2GPHPY19S3HAZ15RM2", + "kind": "memory", + "score": 0.9997585415840148, + "summary": "project:fact - To roll back Marble to the previous release, run `marblectl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 370.149, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 633978880, + "peak_working_set_bytes": 685076480 + }, + { + "query": "Which region hosts Marble production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X727HTMRN9TYZZW1TD16HZ", + "id": "01M1X72ADGAQK37VXY2MQA08JC", + "kind": "memory", + "score": 0.999954104423523, + "summary": "project:fact - Marble production runs in region ap-south-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 347.05609999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 634007552, + "peak_working_set_bytes": 685076480 + }, + { + "query": "At what UTC time do daily Marble database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X727J6FTK6KWNT7T2YMVKY", + "id": "01M1X72ARB1VBXNTTQV2ZPBTMQ", + "kind": "memory", + "score": 0.999979853630066, + "summary": "project:fact - Marble daily database backups start at 01:25 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 358.711, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 587, + "reported_used_tokens": 552, + "working_set_bytes": 634150912, + "peak_working_set_bytes": 685076480 + }, + { + "query": "Which database and journal mode does Marble use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X727JJK6XWTG4J8QMGY1ZP", + "id": "01M1X72B3J4NNDH2JDTS4423T0", + "kind": "memory", + "score": 0.9999568462371826, + "summary": "project:fact - Marble stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 353.8916, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 634306560, + "peak_working_set_bytes": 685076480 + }, + { + "query": "\u00bfA qu\u00e9 puerto TCP debo conectarme para staging de Marble?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X727GMVC50GY8DKEFRE4M5", + "id": "01M1X72BERFS0YDB31ECN034JY", + "kind": "memory", + "score": 0.9998871088027954, + "summary": "project:fact - Marble staging HTTP listener binds TCP port 8533. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 368.4077, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 636669952, + "peak_working_set_bytes": 685076480 + }, + { + "query": "\u00bfD\u00f3nde deben escribirse los mensajes de diagn\u00f3stico de Marble?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X727H33NDEE3FB9WX65292", + "id": "01M1X72BT88PG7JT7HX31BH3GH", + "kind": "memory", + "score": 0.9992142915725708, + "summary": "project:fact - Marble diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 360.4196, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 488, + "mcp_result_bytes": 569, + "wire_bytes": 604, + "reported_used_tokens": 569, + "working_set_bytes": 637104128, + "peak_working_set_bytes": 685076480 + }, + { + "query": "\u00bfQu\u00e9 comando revierte Marble a la versi\u00f3n anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X727HE87E87FQHNCMYMXE1", + "id": "01M1X72C5F3QR58E5P5DST092S", + "kind": "memory", + "score": 0.976457178592682, + "summary": "project:fact - To roll back Marble to the previous release, run `marblectl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 362.1443, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 616, + "reported_used_tokens": 580, + "working_set_bytes": 637161472, + "peak_working_set_bytes": 685076480 + }, + { + "query": "\u00bfEn qu\u00e9 regi\u00f3n est\u00e1 desplegado Marble en producci\u00f3n?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X727HTMRN9TYZZW1TD16HZ", + "id": "01M1X72CHY07XP4KYH11F0YRCD", + "kind": "memory", + "score": 0.9999542236328124, + "summary": "project:fact - Marble production runs in region ap-south-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 401.4961, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 637186048, + "peak_working_set_bytes": 685076480 + }, + { + "query": "\u00bfA qu\u00e9 hora UTC empiezan las copias diarias de la base de datos de Marble?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X727J6FTK6KWNT7T2YMVKY", + "id": "01M1X72CYAFS14C4PDXTAHSA3N", + "kind": "memory", + "score": 0.9999665021896362, + "summary": "project:fact - Marble daily database backups start at 01:25 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 422.5777, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 637493248, + "peak_working_set_bytes": 685076480 + }, + { + "query": "\u00bfQu\u00e9 base de datos y modo de registro usa Marble para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X727JJK6XWTG4J8QMGY1ZP", + "id": "01M1X72DC1AQHZ2MRRVQWBESZ2", + "kind": "memory", + "score": 0.9953057169914246, + "summary": "project:fact - Marble stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 416.2232, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 637652992, + "peak_working_set_bytes": 685076480 + }, + { + "query": "What authentication password is configured for the Marble staging listener?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X727GMVC50GY8DKEFRE4M5", + "id": "01M1X72DR3M5NBVJ6CFKTPXMBC", + "kind": "memory", + "score": 0.9787366390228271, + "summary": "project:fact - Marble staging HTTP listener binds TCP port 8533. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 379.47999999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 615, + "reported_used_tokens": 579, + "working_set_bytes": 637722624, + "peak_working_set_bytes": 685076480 + }, + { + "query": "What encryption key protects the Marble database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 363.7939, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643563520, + "peak_working_set_bytes": 685076480 + }, + { + "query": "How many production replicas run in the Marble deployment region?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X727HTMRN9TYZZW1TD16HZ", + "id": "01M1X72EEQ3X1AR8446JZ5GSSV", + "kind": "memory", + "score": 0.8181904554367065, + "summary": "project:fact - Marble production runs in region ap-south-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 357.0978, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 643567616, + "peak_working_set_bytes": 685076480 + }, + { + "query": "\u00bfQu\u00e9 contrase\u00f1a exige el servidor de staging de Marble?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X727GMVC50GY8DKEFRE4M5", + "id": "01M1X72ET25H8HVNEK095TGVTB", + "kind": "memory", + "score": 0.8728806972503662, + "summary": "project:fact - Marble staging HTTP listener binds TCP port 8533. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 356.6876, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 615, + "reported_used_tokens": 579, + "working_set_bytes": 643657728, + "peak_working_set_bytes": 685076480 + }, + { + "query": "\u00bfCu\u00e1ntos d\u00edas se conservan las copias de seguridad de Marble?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X727J6FTK6KWNT7T2YMVKY", + "id": "01M1X72F53JBH1Y89MPZ8ZAHFA", + "kind": "memory", + "score": 0.9691649079322816, + "summary": "project:fact - Marble daily database backups start at 01:25 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 361.50120000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 643756032, + "peak_working_set_bytes": 685076480 + }, + { + "query": "\u00bfQu\u00e9 versi\u00f3n de SQLite requiere Marble?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 354.6435, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643760128, + "peak_working_set_bytes": 685076480 + } + ], + "id": "marble-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 0.7777777777777778, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.667 (n=6) positive-n=12 negative-n=6 (18 queries)" + }, + { + "observations": [ + { + "query": "Which TCP port should I connect to for Kestrel staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72GYXRMH0RZFYXMQAWJDG", + "id": "01M1X72JSNRHF8DPWJGBRR2X6N", + "kind": "memory", + "score": 0.9999393224716188, + "summary": "project:fact - Kestrel staging HTTP listener binds TCP port 9647. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1796.6894, + "first_query": true, + "server_startup_ms": 75.3927, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 633917440, + "peak_working_set_bytes": 684920832 + }, + { + "query": "Where should Kestrel diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72GZEW7S19KADK0PZBR2C", + "id": "01M1X72K54ZHSF6AZX85HPNCJ9", + "kind": "memory", + "score": 0.9987480640411376, + "summary": "project:fact - Kestrel diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X72H17WCT1WQJ24Q6MHHCV", + "id": "01M1X72K543EFWD2QVJ8PJ8F2W", + "kind": "memory", + "score": 0.9422296285629272, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 354.5085, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 756, + "mcp_result_bytes": 855, + "wire_bytes": 890, + "reported_used_tokens": 855, + "working_set_bytes": 636039168, + "peak_working_set_bytes": 684920832 + }, + { + "query": "Which command rolls back Kestrel to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72GZSVRN19QDYSCVB4Q15", + "id": "01M1X72KG6DRQEWPVCK77WB7PD", + "kind": "memory", + "score": 0.9997856020927428, + "summary": "project:fact - To roll back Kestrel to the previous release, run `kestrelctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 352.1329, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 501, + "mcp_result_bytes": 582, + "wire_bytes": 617, + "reported_used_tokens": 582, + "working_set_bytes": 636416000, + "peak_working_set_bytes": 684920832 + }, + { + "query": "Which region hosts Kestrel production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72H05N0636E65RDJQ4FQ8", + "id": "01M1X72KV1945BKJZ7RTYK7J76", + "kind": "memory", + "score": 0.9999779462814332, + "summary": "project:fact - Kestrel production runs in region eu-west-3. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 361.4742, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 609, + "reported_used_tokens": 574, + "working_set_bytes": 636456960, + "peak_working_set_bytes": 684920832 + }, + { + "query": "At what UTC time do daily Kestrel database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72H0W13A6GHA15KETR966", + "id": "01M1X72M6HQS0ZMJ2JW85DS071", + "kind": "memory", + "score": 0.999980330467224, + "summary": "project:fact - Kestrel daily database backups start at 03:50 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 359.24089999999995, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 472, + "mcp_result_bytes": 553, + "wire_bytes": 588, + "reported_used_tokens": 553, + "working_set_bytes": 636600320, + "peak_working_set_bytes": 684920832 + }, + { + "query": "Which database and journal mode does Kestrel use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72H17WCT1WQJ24Q6MHHCV", + "id": "01M1X72MHS3G2ERGKPH3KWFVVW", + "kind": "memory", + "score": 0.999975323677063, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 360.8176, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 636637184, + "peak_working_set_bytes": 684920832 + }, + { + "query": "\u00bfA qu\u00e9 puerto TCP debo conectarme para staging de Kestrel?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72GYXRMH0RZFYXMQAWJDG", + "id": "01M1X72MX5TN622A0MEJXHGAYT", + "kind": "memory", + "score": 0.9999423027038574, + "summary": "project:fact - Kestrel staging HTTP listener binds TCP port 9647. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 364.9794, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 639184896, + "peak_working_set_bytes": 684920832 + }, + { + "query": "\u00bfD\u00f3nde deben escribirse los mensajes de diagn\u00f3stico de Kestrel?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72GZEW7S19KADK0PZBR2C", + "id": "01M1X72N8QXXBNJT3WYEEGDGKJ", + "kind": "memory", + "score": 0.9997678399086, + "summary": "project:fact - Kestrel diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 380.63239999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 489, + "mcp_result_bytes": 570, + "wire_bytes": 605, + "reported_used_tokens": 570, + "working_set_bytes": 639291392, + "peak_working_set_bytes": 684920832 + }, + { + "query": "\u00bfQu\u00e9 comando revierte Kestrel a la versi\u00f3n anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72GZSVRN19QDYSCVB4Q15", + "id": "01M1X72NN2CEE8TDW5VVVRK69H", + "kind": "memory", + "score": 0.9766082763671876, + "summary": "project:fact - To roll back Kestrel to the previous release, run `kestrelctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 378.7312, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 501, + "mcp_result_bytes": 582, + "wire_bytes": 618, + "reported_used_tokens": 582, + "working_set_bytes": 639406080, + "peak_working_set_bytes": 684920832 + }, + { + "query": "\u00bfEn qu\u00e9 regi\u00f3n est\u00e1 desplegado Kestrel en producci\u00f3n?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72H05N0636E65RDJQ4FQ8", + "id": "01M1X72P053AX0Z8YWV87BMNZ3", + "kind": "memory", + "score": 0.999974250793457, + "summary": "project:fact - Kestrel production runs in region eu-west-3. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.9003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 639442944, + "peak_working_set_bytes": 684920832 + }, + { + "query": "\u00bfA qu\u00e9 hora UTC empiezan las copias diarias de la base de datos de Kestrel?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72H0W13A6GHA15KETR966", + "id": "01M1X72PBBD5GGRD0B4DJ3TWR7", + "kind": "memory", + "score": 0.9999799728393556, + "summary": "project:fact - Kestrel daily database backups start at 03:50 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 363.1298, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 472, + "mcp_result_bytes": 553, + "wire_bytes": 589, + "reported_used_tokens": 553, + "working_set_bytes": 639725568, + "peak_working_set_bytes": 684920832 + }, + { + "query": "\u00bfQu\u00e9 base de datos y modo de registro usa Kestrel para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72H17WCT1WQJ24Q6MHHCV", + "id": "01M1X72PPM5PRECK62TJ355WWF", + "kind": "memory", + "score": 0.9993937015533448, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 364.2815, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 639746048, + "peak_working_set_bytes": 684920832 + }, + { + "query": "What authentication password is configured for the Kestrel staging listener?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72GYXRMH0RZFYXMQAWJDG", + "id": "01M1X72Q21WXNDJWHS7Y3V6H77", + "kind": "memory", + "score": 0.9726881980895996, + "summary": "project:fact - Kestrel staging HTTP listener binds TCP port 9647. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 367.7531, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 616, + "reported_used_tokens": 580, + "working_set_bytes": 639885312, + "peak_working_set_bytes": 684920832 + }, + { + "query": "What encryption key protects the Kestrel database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 357.1373, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644059136, + "peak_working_set_bytes": 684920832 + }, + { + "query": "How many production replicas run in the Kestrel deployment region?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72H05N0636E65RDJQ4FQ8", + "id": "01M1X72QRRVMV3WP9DCZRK26E1", + "kind": "memory", + "score": 0.958982229232788, + "summary": "project:fact - Kestrel production runs in region eu-west-3. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 364.6513, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 644169728, + "peak_working_set_bytes": 684920832 + }, + { + "query": "\u00bfQu\u00e9 contrase\u00f1a exige el servidor de staging de Kestrel?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72GYXRMH0RZFYXMQAWJDG", + "id": "01M1X72R4CJ0XXCRST6213145H", + "kind": "memory", + "score": 0.9609549045562744, + "summary": "project:fact - Kestrel staging HTTP listener binds TCP port 9647. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 369.5267, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 616, + "reported_used_tokens": 580, + "working_set_bytes": 644268032, + "peak_working_set_bytes": 684920832 + }, + { + "query": "\u00bfCu\u00e1ntos d\u00edas se conservan las copias de seguridad de Kestrel?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72H0W13A6GHA15KETR966", + "id": "01M1X72RG3KNR3G3ZG5AN7CTCG", + "kind": "memory", + "score": 0.9748653173446656, + "summary": "project:fact - Kestrel daily database backups start at 03:50 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 384.18370000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 472, + "mcp_result_bytes": 553, + "wire_bytes": 589, + "reported_used_tokens": 553, + "working_set_bytes": 644440064, + "peak_working_set_bytes": 684920832 + }, + { + "query": "\u00bfQu\u00e9 versi\u00f3n de SQLite requiere Kestrel?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72H17WCT1WQJ24Q6MHHCV", + "id": "01M1X72RWF5FCGFVMQ0M3NSAT4", + "kind": "memory", + "score": 0.6894522309303284, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 868.6243000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 644513792, + "peak_working_set_bytes": 684920832 + } + ], + "id": "kestrel-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 0.7222222222222222, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.833 (n=6) positive-n=12 negative-n=6 (18 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 2.9444444444444446, + 4 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 0.7361111111111112, + "n": 4, + "ci95": 0.027222222222222234 + } + }, + "overall_index": 0.7361111111111112, + "scenario_weighted_index": 0.7361111111111112 +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/1-candidate.json b/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/1-candidate.json new file mode 100644 index 0000000..5ae1349 --- /dev/null +++ b/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/1-candidate.json @@ -0,0 +1,1921 @@ +{ + "generated_at": "2026-09-07T05:58:06.4898775Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-retrieval\\validation-frozen.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "Which TCP port should I connect to for Copper staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72TSZCTRBH5XP4A1SP4DA", + "id": "01M1X72WPD6D1WERKRDE9P516N", + "kind": "memory", + "score": 0.9998394250869752, + "summary": "project:fact - Copper staging HTTP listener binds TCP port 6319. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1874.9685, + "first_query": true, + "server_startup_ms": 74.1371, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 636510208, + "peak_working_set_bytes": 685019136 + }, + { + "query": "Where should Copper diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72TTGX8NYTSHETBSD9DG2", + "id": "01M1X72X31RZDTPC2E7ETJXTXQ", + "kind": "memory", + "score": 0.9978247880935668, + "summary": "project:fact - Copper diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X72TVYESSMSVGDYVF615B6", + "id": "01M1X72X318MSFCB1ER341VXZA", + "kind": "memory", + "score": 0.6390834450721741, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 378.31710000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 754, + "mcp_result_bytes": 853, + "wire_bytes": 888, + "reported_used_tokens": 853, + "working_set_bytes": 637009920, + "peak_working_set_bytes": 685019136 + }, + { + "query": "Which command rolls back Copper to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72TTW5WDNEBD9DPK8WDFY", + "id": "01M1X72XEQE18WAHQ7YR2M6AZT", + "kind": "memory", + "score": 0.9998852014541626, + "summary": "project:fact - To roll back Copper to the previous release, run `copperctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 384.0297, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 639234048, + "peak_working_set_bytes": 685019136 + }, + { + "query": "Which region hosts Copper production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72TV7A6DA6368D04C09DY", + "id": "01M1X72XY3GXR0436SQT2CG9ZD", + "kind": "memory", + "score": 0.9999468326568604, + "summary": "project:fact - Copper production runs in region eu-north-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 494.4572, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 609, + "reported_used_tokens": 574, + "working_set_bytes": 639328256, + "peak_working_set_bytes": 685019136 + }, + { + "query": "At what UTC time do daily Copper database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72TVKXMBA4VFV7DW0YEC7", + "id": "01M1X72YBAVGR6DG5FPCHPM17K", + "kind": "memory", + "score": 0.9999791383743286, + "summary": "project:fact - Copper daily database backups start at 02:40 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 420.66540000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 587, + "reported_used_tokens": 552, + "working_set_bytes": 639500288, + "peak_working_set_bytes": 685019136 + }, + { + "query": "Which database and journal mode does Copper use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72TVYESSMSVGDYVF615B6", + "id": "01M1X72YQPNQEG7VQ22YQ7R2WK", + "kind": "memory", + "score": 0.9999594688415528, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 399.2042, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 639782912, + "peak_working_set_bytes": 685019136 + }, + { + "query": "\u00bfA qu\u00e9 puerto TCP debo conectarme para staging de Copper?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72TSZCTRBH5XP4A1SP4DA", + "id": "01M1X72Z3JN29J0SZTP3BRPYNA", + "kind": "memory", + "score": 0.9998682737350464, + "summary": "project:fact - Copper staging HTTP listener binds TCP port 6319. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 368.17080000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 642162688, + "peak_working_set_bytes": 685019136 + }, + { + "query": "\u00bfD\u00f3nde deben escribirse los mensajes de diagn\u00f3stico de Copper?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72TTGX8NYTSHETBSD9DG2", + "id": "01M1X72ZEZYY5JN88XEJ1N3FPC", + "kind": "memory", + "score": 0.9995033740997314, + "summary": "project:fact - Copper diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 358.5598, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 488, + "mcp_result_bytes": 569, + "wire_bytes": 604, + "reported_used_tokens": 569, + "working_set_bytes": 642531328, + "peak_working_set_bytes": 685019136 + }, + { + "query": "\u00bfQu\u00e9 comando revierte Copper a la versi\u00f3n anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72TTW5WDNEBD9DPK8WDFY", + "id": "01M1X72ZT5HNKKN3A8XYZNYYKJ", + "kind": "memory", + "score": 0.9887914657592772, + "summary": "project:fact - To roll back Copper to the previous release, run `copperctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.8206, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 616, + "reported_used_tokens": 580, + "working_set_bytes": 642572288, + "peak_working_set_bytes": 685019136 + }, + { + "query": "\u00bfEn qu\u00e9 regi\u00f3n est\u00e1 desplegado Copper en producci\u00f3n?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72TV7A6DA6368D04C09DY", + "id": "01M1X7305F7B0FJ3N8H2SNYCCP", + "kind": "memory", + "score": 0.9999468326568604, + "summary": "project:fact - Copper production runs in region eu-north-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 364.0555, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 642666496, + "peak_working_set_bytes": 685019136 + }, + { + "query": "\u00bfA qu\u00e9 hora UTC empiezan las copias diarias de la base de datos de Copper?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72TVKXMBA4VFV7DW0YEC7", + "id": "01M1X730GPR4GP5Z2F58GAME0H", + "kind": "memory", + "score": 0.9999747276306152, + "summary": "project:fact - Copper daily database backups start at 02:40 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 367.7949, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 642969600, + "peak_working_set_bytes": 685019136 + }, + { + "query": "\u00bfQu\u00e9 base de datos y modo de registro usa Copper para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72TVYESSMSVGDYVF615B6", + "id": "01M1X730WDB54PQRD3TG576B8J", + "kind": "memory", + "score": 0.9983224272727966, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 368.5271, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 643047424, + "peak_working_set_bytes": 685019136 + }, + { + "query": "What authentication password is configured for the Copper staging listener?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 371.72139999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643117056, + "peak_working_set_bytes": 685019136 + }, + { + "query": "What encryption key protects the Copper database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 398.0603, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 648998912, + "peak_working_set_bytes": 685019136 + }, + { + "query": "How many production replicas run in the Copper deployment region?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 359.8829, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 649052160, + "peak_working_set_bytes": 685019136 + }, + { + "query": "\u00bfQu\u00e9 contrase\u00f1a exige el servidor de staging de Copper?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 358.9117, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 649179136, + "peak_working_set_bytes": 685019136 + }, + { + "query": "\u00bfCu\u00e1ntos d\u00edas se conservan las copias de seguridad de Copper?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 362.636, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 649416704, + "peak_working_set_bytes": 685019136 + }, + { + "query": "\u00bfQu\u00e9 versi\u00f3n de SQLite requiere Copper?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 355.341, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 649437184, + "peak_working_set_bytes": 685019136 + } + ], + "id": "copper-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.000 (n=6) positive-n=12 negative-n=6 (18 queries)" + }, + { + "observations": [ + { + "query": "Which TCP port should I connect to for Willow staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X734E3R7KBEAZ3G19MRD8Q", + "id": "01M1X7368S46HG6TB6Y7MQJCJA", + "kind": "memory", + "score": 0.9999420642852784, + "summary": "project:fact - Willow staging HTTP listener binds TCP port 7421. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 2127.8483, + "first_query": true, + "server_startup_ms": 82.55, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 633872384, + "peak_working_set_bytes": 684879872 + }, + { + "query": "Where should Willow diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X734EJ6Y5DKJRHZCDFJCA1", + "id": "01M1X737136NDBATAJ76NQZFD2", + "kind": "memory", + "score": 0.9971635937690736, + "summary": "project:fact - Willow diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X734G0SVTQ6A95YRS7W2HM", + "id": "01M1X73714YM8P84YNJEY5R2ZT", + "kind": "memory", + "score": 0.8971153497695923, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 452.9002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 754, + "mcp_result_bytes": 853, + "wire_bytes": 888, + "reported_used_tokens": 853, + "working_set_bytes": 634376192, + "peak_working_set_bytes": 684879872 + }, + { + "query": "Which command rolls back Willow to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X734EYZMGPXJJG798WB5EA", + "id": "01M1X737D3P0XK2GSJJ7EZ6VM2", + "kind": "memory", + "score": 0.9998542070388794, + "summary": "project:fact - To roll back Willow to the previous release, run `willowctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 391.2379, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 636596224, + "peak_working_set_bytes": 684879872 + }, + { + "query": "Which region hosts Willow production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X734FA553ERZT95KJRFZXA", + "id": "01M1X737SA7WTCX675SXMHDX1H", + "kind": "memory", + "score": 0.9999713897705078, + "summary": "project:fact - Willow production runs in region us-west-2. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 374.3574, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 636751872, + "peak_working_set_bytes": 684879872 + }, + { + "query": "At what UTC time do daily Willow database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X734FNC8X64Y4RDB7HMZD7", + "id": "01M1X7384EMTNYBA6AV6TCM6R1", + "kind": "memory", + "score": 0.9999792575836182, + "summary": "project:fact - Willow daily database backups start at 04:15 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 355.7612, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 587, + "reported_used_tokens": 552, + "working_set_bytes": 636911616, + "peak_working_set_bytes": 684879872 + }, + { + "query": "Which database and journal mode does Willow use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X734G0SVTQ6A95YRS7W2HM", + "id": "01M1X738FEJQVAEFEW428P9M71", + "kind": "memory", + "score": 0.9999637603759766, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 363.3105, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 636948480, + "peak_working_set_bytes": 684879872 + }, + { + "query": "\u00bfA qu\u00e9 puerto TCP debo conectarme para staging de Willow?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X734E3R7KBEAZ3G19MRD8Q", + "id": "01M1X738TX4TW1SC0SNWKYQ4S9", + "kind": "memory", + "score": 0.9999486207962036, + "summary": "project:fact - Willow staging HTTP listener binds TCP port 7421. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 373.5032, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 639356928, + "peak_working_set_bytes": 684879872 + }, + { + "query": "\u00bfD\u00f3nde deben escribirse los mensajes de diagn\u00f3stico de Willow?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X734EJ6Y5DKJRHZCDFJCA1", + "id": "01M1X7396XSMH1RXVT51QYV9SD", + "kind": "memory", + "score": 0.9996838569641112, + "summary": "project:fact - Willow diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 370.3033, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 488, + "mcp_result_bytes": 569, + "wire_bytes": 604, + "reported_used_tokens": 569, + "working_set_bytes": 639946752, + "peak_working_set_bytes": 684879872 + }, + { + "query": "\u00bfQu\u00e9 comando revierte Willow a la versi\u00f3n anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X734EYZMGPXJJG798WB5EA", + "id": "01M1X739J8711F6R7TZW1D3CWH", + "kind": "memory", + "score": 0.98951655626297, + "summary": "project:fact - To roll back Willow to the previous release, run `willowctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 360.3769, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 497, + "mcp_result_bytes": 578, + "wire_bytes": 614, + "reported_used_tokens": 578, + "working_set_bytes": 640045056, + "peak_working_set_bytes": 684879872 + }, + { + "query": "\u00bfEn qu\u00e9 regi\u00f3n est\u00e1 desplegado Willow en producci\u00f3n?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X734FA553ERZT95KJRFZXA", + "id": "01M1X739XJRGT4HPEYPEHK2JKD", + "kind": "memory", + "score": 0.9999579191207886, + "summary": "project:fact - Willow production runs in region us-west-2. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 368.3913, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 640237568, + "peak_working_set_bytes": 684879872 + }, + { + "query": "\u00bfA qu\u00e9 hora UTC empiezan las copias diarias de la base de datos de Willow?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X734FNC8X64Y4RDB7HMZD7", + "id": "01M1X73AA619RWHZFZ8N9R01Y1", + "kind": "memory", + "score": 0.99997878074646, + "summary": "project:fact - Willow daily database backups start at 04:15 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 400.6685, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 469, + "mcp_result_bytes": 550, + "wire_bytes": 586, + "reported_used_tokens": 550, + "working_set_bytes": 640577536, + "peak_working_set_bytes": 684879872 + }, + { + "query": "\u00bfQu\u00e9 base de datos y modo de registro usa Willow para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X734G0SVTQ6A95YRS7W2HM", + "id": "01M1X73ANS8NX905HF5ARPB72P", + "kind": "memory", + "score": 0.999204695224762, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 376.4599, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 491, + "mcp_result_bytes": 572, + "wire_bytes": 608, + "reported_used_tokens": 572, + "working_set_bytes": 640712704, + "peak_working_set_bytes": 684879872 + }, + { + "query": "What authentication password is configured for the Willow staging listener?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 375.4146, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 640991232, + "peak_working_set_bytes": 684879872 + }, + { + "query": "What encryption key protects the Willow database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 374.1562, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 646864896, + "peak_working_set_bytes": 684879872 + }, + { + "query": "How many production replicas run in the Willow deployment region?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 386.0922, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 647004160, + "peak_working_set_bytes": 684879872 + }, + { + "query": "\u00bfQu\u00e9 contrase\u00f1a exige el servidor de staging de Willow?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 463.5531, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 647122944, + "peak_working_set_bytes": 684879872 + }, + { + "query": "\u00bfCu\u00e1ntos d\u00edas se conservan las copias de seguridad de Willow?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 422.67139999999995, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 647299072, + "peak_working_set_bytes": 684879872 + }, + { + "query": "\u00bfQu\u00e9 versi\u00f3n de SQLite requiere Willow?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 407.32370000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 647389184, + "peak_working_set_bytes": 684879872 + } + ], + "id": "willow-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.000 (n=6) positive-n=12 negative-n=6 (18 queries)" + }, + { + "observations": [ + { + "query": "Which TCP port should I connect to for Marble staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73EFTPK7D5G6W0Q2R9ZHQ", + "id": "01M1X73GBJDV176BEXMHR317S2", + "kind": "memory", + "score": 0.9996737241744996, + "summary": "project:fact - Marble staging HTTP listener binds TCP port 8533. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1838.8174, + "first_query": true, + "server_startup_ms": 75.7183, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 633483264, + "peak_working_set_bytes": 684716032 + }, + { + "query": "Where should Marble diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73EG90V7J8TSY1YP6Z08S", + "id": "01M1X73GPM6J3KP5RG5K6TXBAS", + "kind": "memory", + "score": 0.9971815347671508, + "summary": "project:fact - Marble diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X73EHTVTVNW3MFSG09NYDT", + "id": "01M1X73GPNDDN09Y2J7K97VJC1", + "kind": "memory", + "score": 0.6012999415397644, + "summary": "project:fact - Marble stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 347.279, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 754, + "mcp_result_bytes": 853, + "wire_bytes": 888, + "reported_used_tokens": 853, + "working_set_bytes": 633958400, + "peak_working_set_bytes": 684716032 + }, + { + "query": "Which command rolls back Marble to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73EGPE9JSETHHQPQDDHTR", + "id": "01M1X73H1RTVW1S21R2PKP4C65", + "kind": "memory", + "score": 0.9997585415840148, + "summary": "project:fact - To roll back Marble to the previous release, run `marblectl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 359.86899999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 635793408, + "peak_working_set_bytes": 684716032 + }, + { + "query": "Which region hosts Marble production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73EH2E4SM8ZSMZZ5JNDX4", + "id": "01M1X73HCSCNQCCJ1Z5Y15F0EK", + "kind": "memory", + "score": 0.999954104423523, + "summary": "project:fact - Marble production runs in region ap-south-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 345.8051, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 636002304, + "peak_working_set_bytes": 684716032 + }, + { + "query": "At what UTC time do daily Marble database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73EHEASFR0B8WKNDYQ0JB", + "id": "01M1X73HR0WBS5SE7WVDKMAR5V", + "kind": "memory", + "score": 0.999979853630066, + "summary": "project:fact - Marble daily database backups start at 01:25 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 368.7463, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 587, + "reported_used_tokens": 552, + "working_set_bytes": 636166144, + "peak_working_set_bytes": 684716032 + }, + { + "query": "Which database and journal mode does Marble use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73EHTVTVNW3MFSG09NYDT", + "id": "01M1X73J3DR6R8ABA81X6V1H3G", + "kind": "memory", + "score": 0.9999568462371826, + "summary": "project:fact - Marble stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 368.8099, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 636227584, + "peak_working_set_bytes": 684716032 + }, + { + "query": "\u00bfA qu\u00e9 puerto TCP debo conectarme para staging de Marble?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73EFTPK7D5G6W0Q2R9ZHQ", + "id": "01M1X73JFGZ3QJTPGNAP8K0214", + "kind": "memory", + "score": 0.9998871088027954, + "summary": "project:fact - Marble staging HTTP listener binds TCP port 8533. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 381.834, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 638672896, + "peak_working_set_bytes": 684716032 + }, + { + "query": "\u00bfD\u00f3nde deben escribirse los mensajes de diagn\u00f3stico de Marble?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73EG90V7J8TSY1YP6Z08S", + "id": "01M1X73JTXXVB3FC35GG1X0FHR", + "kind": "memory", + "score": 0.9992142915725708, + "summary": "project:fact - Marble diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 362.8531, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 488, + "mcp_result_bytes": 569, + "wire_bytes": 604, + "reported_used_tokens": 569, + "working_set_bytes": 638984192, + "peak_working_set_bytes": 684716032 + }, + { + "query": "\u00bfQu\u00e9 comando revierte Marble a la versi\u00f3n anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73EGPE9JSETHHQPQDDHTR", + "id": "01M1X73K5YYN5PR11ZCBJ6Q3P7", + "kind": "memory", + "score": 0.976457178592682, + "summary": "project:fact - To roll back Marble to the previous release, run `marblectl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 351.7305, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 616, + "reported_used_tokens": 580, + "working_set_bytes": 639016960, + "peak_working_set_bytes": 684716032 + }, + { + "query": "\u00bfEn qu\u00e9 regi\u00f3n est\u00e1 desplegado Marble en producci\u00f3n?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73EH2E4SM8ZSMZZ5JNDX4", + "id": "01M1X73KH2QEAQSJ09NCPQQCK1", + "kind": "memory", + "score": 0.9999542236328124, + "summary": "project:fact - Marble production runs in region ap-south-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 357.1974, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 639098880, + "peak_working_set_bytes": 684716032 + }, + { + "query": "\u00bfA qu\u00e9 hora UTC empiezan las copias diarias de la base de datos de Marble?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73EHEASFR0B8WKNDYQ0JB", + "id": "01M1X73KW73HG1VJ76YCXFWTJV", + "kind": "memory", + "score": 0.9999665021896362, + "summary": "project:fact - Marble daily database backups start at 01:25 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 369.36429999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 639352832, + "peak_working_set_bytes": 684716032 + }, + { + "query": "\u00bfQu\u00e9 base de datos y modo de registro usa Marble para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73EHTVTVNW3MFSG09NYDT", + "id": "01M1X73M7SJW7WHGCP5HY7KPDT", + "kind": "memory", + "score": 0.9953057169914246, + "summary": "project:fact - Marble stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 358.5002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 639410176, + "peak_working_set_bytes": 684716032 + }, + { + "query": "What authentication password is configured for the Marble staging listener?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 354.3449, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 639455232, + "peak_working_set_bytes": 684716032 + }, + { + "query": "What encryption key protects the Marble database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 360.3377, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645300224, + "peak_working_set_bytes": 684716032 + }, + { + "query": "How many production replicas run in the Marble deployment region?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 365.1542, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645394432, + "peak_working_set_bytes": 684716032 + }, + { + "query": "\u00bfQu\u00e9 contrase\u00f1a exige el servidor de staging de Marble?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 382.7403, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645435392, + "peak_working_set_bytes": 684716032 + }, + { + "query": "\u00bfCu\u00e1ntos d\u00edas se conservan las copias de seguridad de Marble?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 392.1383, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645693440, + "peak_working_set_bytes": 684716032 + }, + { + "query": "\u00bfQu\u00e9 versi\u00f3n de SQLite requiere Marble?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 351.8322, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645718016, + "peak_working_set_bytes": 684716032 + } + ], + "id": "marble-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.000 (n=6) positive-n=12 negative-n=6 (18 queries)" + }, + { + "observations": [ + { + "query": "Which TCP port should I connect to for Kestrel staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73QRNJQ7DSC1JD09J6JB3", + "id": "01M1X73SJS1415PP6DFEKWR4V3", + "kind": "memory", + "score": 0.9999393224716188, + "summary": "project:fact - Kestrel staging HTTP listener binds TCP port 9647. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1830.7945000000002, + "first_query": true, + "server_startup_ms": 73.8767, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 635355136, + "peak_working_set_bytes": 684814336 + }, + { + "query": "Where should Kestrel diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73QS5ST5HDREKM31ANXES", + "id": "01M1X73T0113QVTCFV18ZC8AYZ", + "kind": "memory", + "score": 0.9987480640411376, + "summary": "project:fact - Kestrel diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X73QTMC0QMJGBCGAMKQZHV", + "id": "01M1X73T018HSGQ8DJ6G2EQFAT", + "kind": "memory", + "score": 0.9422296285629272, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 384.8595, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 756, + "mcp_result_bytes": 855, + "wire_bytes": 890, + "reported_used_tokens": 855, + "working_set_bytes": 637423616, + "peak_working_set_bytes": 684814336 + }, + { + "query": "Which command rolls back Kestrel to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73QSHYSH5J4F7VQZ5EMQD", + "id": "01M1X73TCBQR0EV478VBYBQ9V3", + "kind": "memory", + "score": 0.9997856020927428, + "summary": "project:fact - To roll back Kestrel to the previous release, run `kestrelctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 416.8803, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 501, + "mcp_result_bytes": 582, + "wire_bytes": 617, + "reported_used_tokens": 582, + "working_set_bytes": 639750144, + "peak_working_set_bytes": 684814336 + }, + { + "query": "Which region hosts Kestrel production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73QSW1BFDTY924DA22RSX", + "id": "01M1X73TRPSKF6XZAN42JBNB63", + "kind": "memory", + "score": 0.9999779462814332, + "summary": "project:fact - Kestrel production runs in region eu-west-3. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 363.543, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 609, + "reported_used_tokens": 574, + "working_set_bytes": 639905792, + "peak_working_set_bytes": 684814336 + }, + { + "query": "At what UTC time do daily Kestrel database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73QT8N6R1Y9HK4YJKSJ74", + "id": "01M1X73V3R5B4KPRGA9VJHCKZA", + "kind": "memory", + "score": 0.999980330467224, + "summary": "project:fact - Kestrel daily database backups start at 03:50 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 357.5294, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 472, + "mcp_result_bytes": 553, + "wire_bytes": 588, + "reported_used_tokens": 553, + "working_set_bytes": 640327680, + "peak_working_set_bytes": 684814336 + }, + { + "query": "Which database and journal mode does Kestrel use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73QTMC0QMJGBCGAMKQZHV", + "id": "01M1X73VEWHS67RNHNEYF2W2VJ", + "kind": "memory", + "score": 0.999975323677063, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 353.3806, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 640483328, + "peak_working_set_bytes": 684814336 + }, + { + "query": "\u00bfA qu\u00e9 puerto TCP debo conectarme para staging de Kestrel?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73QRNJQ7DSC1JD09J6JB3", + "id": "01M1X73VSZN3PHNES9C1VSQ2T9", + "kind": "memory", + "score": 0.9999423027038574, + "summary": "project:fact - Kestrel staging HTTP listener binds TCP port 9647. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 361.1123, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 642949120, + "peak_working_set_bytes": 684814336 + }, + { + "query": "\u00bfD\u00f3nde deben escribirse los mensajes de diagn\u00f3stico de Kestrel?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73QS5ST5HDREKM31ANXES", + "id": "01M1X73W5ENVPCKV8MRGGXT9MM", + "kind": "memory", + "score": 0.9997678399086, + "summary": "project:fact - Kestrel diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 364.33959999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 489, + "mcp_result_bytes": 570, + "wire_bytes": 605, + "reported_used_tokens": 570, + "working_set_bytes": 643252224, + "peak_working_set_bytes": 684814336 + }, + { + "query": "\u00bfQu\u00e9 comando revierte Kestrel a la versi\u00f3n anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73QSHYSH5J4F7VQZ5EMQD", + "id": "01M1X73WGKH2F1G88B84ZC1TMG", + "kind": "memory", + "score": 0.9766082763671876, + "summary": "project:fact - To roll back Kestrel to the previous release, run `kestrelctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 354.2321, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 501, + "mcp_result_bytes": 582, + "wire_bytes": 618, + "reported_used_tokens": 582, + "working_set_bytes": 643289088, + "peak_working_set_bytes": 684814336 + }, + { + "query": "\u00bfEn qu\u00e9 regi\u00f3n est\u00e1 desplegado Kestrel en producci\u00f3n?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73QSW1BFDTY924DA22RSX", + "id": "01M1X73WVPDR4YR5SZ100AGRK9", + "kind": "memory", + "score": 0.999974250793457, + "summary": "project:fact - Kestrel production runs in region eu-west-3. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 362.2926, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 643379200, + "peak_working_set_bytes": 684814336 + }, + { + "query": "\u00bfA qu\u00e9 hora UTC empiezan las copias diarias de la base de datos de Kestrel?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73QT8N6R1Y9HK4YJKSJ74", + "id": "01M1X73X73YWNRD37MHS20TF6N", + "kind": "memory", + "score": 0.9999799728393556, + "summary": "project:fact - Kestrel daily database backups start at 03:50 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.4208, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 472, + "mcp_result_bytes": 553, + "wire_bytes": 589, + "reported_used_tokens": 553, + "working_set_bytes": 643600384, + "peak_working_set_bytes": 684814336 + }, + { + "query": "\u00bfQu\u00e9 base de datos y modo de registro usa Kestrel para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73QTMC0QMJGBCGAMKQZHV", + "id": "01M1X73XJD09P4R2ZVQDXZQTQJ", + "kind": "memory", + "score": 0.9993937015533448, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 372.9329, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 643727360, + "peak_working_set_bytes": 684814336 + }, + { + "query": "What authentication password is configured for the Kestrel staging listener?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 385.90590000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643854336, + "peak_working_set_bytes": 684814336 + }, + { + "query": "What encryption key protects the Kestrel database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 359.2901, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 647958528, + "peak_working_set_bytes": 684814336 + }, + { + "query": "How many production replicas run in the Kestrel deployment region?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 361.19780000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 648044544, + "peak_working_set_bytes": 684814336 + }, + { + "query": "\u00bfQu\u00e9 contrase\u00f1a exige el servidor de staging de Kestrel?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 356.8509, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 648155136, + "peak_working_set_bytes": 684814336 + }, + { + "query": "\u00bfCu\u00e1ntos d\u00edas se conservan las copias de seguridad de Kestrel?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 364.7518, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 648404992, + "peak_working_set_bytes": 684814336 + }, + { + "query": "\u00bfQu\u00e9 versi\u00f3n de SQLite requiere Kestrel?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 352.72209999999995, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 648413184, + "peak_working_set_bytes": 684814336 + } + ], + "id": "kestrel-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.000 (n=6) positive-n=12 negative-n=6 (18 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 4.0, + 4 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 1.0, + "n": 4, + "ci95": 0.0 + } + }, + "overall_index": 1.0, + "scenario_weighted_index": 1.0 +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/2-baseline.json b/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/2-baseline.json new file mode 100644 index 0000000..6d5df2a --- /dev/null +++ b/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/2-baseline.json @@ -0,0 +1,2130 @@ +{ + "generated_at": "2026-09-07T05:59:27.5556696Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-retrieval\\validation-frozen.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "Which TCP port should I connect to for Copper staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7563WQ1C0TZD8N8F4JCZ1", + "id": "01M1X758HTMYMR0D13F2CED4VF", + "kind": "memory", + "score": 0.9998394250869752, + "summary": "project:fact - Copper staging HTTP listener binds TCP port 6319. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1930.3919999999998, + "first_query": true, + "server_startup_ms": 72.3665, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 638246912, + "peak_working_set_bytes": 685117440 + }, + { + "query": "Where should Copper diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7564AHEQ0AVGTD6N2QFGJ", + "id": "01M1X75923008J9BK98YDQ44K2", + "kind": "memory", + "score": 0.9978247880935668, + "summary": "project:fact - Copper diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X7565SHPQ73GM40GRQYYW5", + "id": "01M1X759235WQXQK5F2ZNY3PFV", + "kind": "memory", + "score": 0.6390834450721741, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 439.2912, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 754, + "mcp_result_bytes": 853, + "wire_bytes": 888, + "reported_used_tokens": 853, + "working_set_bytes": 638803968, + "peak_working_set_bytes": 685117440 + }, + { + "query": "Which command rolls back Copper to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7564N1Q9CB9RBRHA75XWD", + "id": "01M1X759FT6FAQ5M0PKTPY5YWY", + "kind": "memory", + "score": 0.9998852014541626, + "summary": "project:fact - To roll back Copper to the previous release, run `copperctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 515.6246, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 638926848, + "peak_working_set_bytes": 685117440 + }, + { + "query": "Which region hosts Copper production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X756514BAK04ATXQKHP4AA", + "id": "01M1X759ZYFR4JP3Y5YWBN88ZC", + "kind": "memory", + "score": 0.9999468326568604, + "summary": "project:fact - Copper production runs in region eu-north-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 509.92530000000005, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 609, + "reported_used_tokens": 574, + "working_set_bytes": 639033344, + "peak_working_set_bytes": 685117440 + }, + { + "query": "At what UTC time do daily Copper database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7565D2PG212EBJY65Q27V", + "id": "01M1X75AFZT3VR73HD1NXZ48MN", + "kind": "memory", + "score": 0.9999791383743286, + "summary": "project:fact - Copper daily database backups start at 02:40 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 516.5822, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 587, + "reported_used_tokens": 552, + "working_set_bytes": 639143936, + "peak_working_set_bytes": 685117440 + }, + { + "query": "Which database and journal mode does Copper use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7565SHPQ73GM40GRQYYW5", + "id": "01M1X75B0VQZ7ZA38QVAF1320S", + "kind": "memory", + "score": 0.9999594688415528, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 461.7396, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 639201280, + "peak_working_set_bytes": 685117440 + }, + { + "query": "\u00bfA qu\u00e9 puerto TCP debo conectarme para staging de Copper?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7563WQ1C0TZD8N8F4JCZ1", + "id": "01M1X75BEHBA1KZHBC4A1H2CSH", + "kind": "memory", + "score": 0.9998682737350464, + "summary": "project:fact - Copper staging HTTP listener binds TCP port 6319. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 545.5953999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 641671168, + "peak_working_set_bytes": 685117440 + }, + { + "query": "\u00bfD\u00f3nde deben escribirse los mensajes de diagn\u00f3stico de Copper?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7564AHEQ0AVGTD6N2QFGJ", + "id": "01M1X75BZKPCFSJFJGKD7823WM", + "kind": "memory", + "score": 0.9995033740997314, + "summary": "project:fact - Copper diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 511.65039999999993, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 488, + "mcp_result_bytes": 569, + "wire_bytes": 604, + "reported_used_tokens": 569, + "working_set_bytes": 642076672, + "peak_working_set_bytes": 685117440 + }, + { + "query": "\u00bfQu\u00e9 comando revierte Copper a la versi\u00f3n anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7564N1Q9CB9RBRHA75XWD", + "id": "01M1X75CFQFYY539M7SCP2F95X", + "kind": "memory", + "score": 0.9887914657592772, + "summary": "project:fact - To roll back Copper to the previous release, run `copperctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 508.6619, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 616, + "reported_used_tokens": 580, + "working_set_bytes": 642174976, + "peak_working_set_bytes": 685117440 + }, + { + "query": "\u00bfEn qu\u00e9 regi\u00f3n est\u00e1 desplegado Copper en producci\u00f3n?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X756514BAK04ATXQKHP4AA", + "id": "01M1X75CZEVRKNN8KYGYRDZ4D3", + "kind": "memory", + "score": 0.9999468326568604, + "summary": "project:fact - Copper production runs in region eu-north-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 506.68120000000005, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 642293760, + "peak_working_set_bytes": 685117440 + }, + { + "query": "\u00bfA qu\u00e9 hora UTC empiezan las copias diarias de la base de datos de Copper?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7565D2PG212EBJY65Q27V", + "id": "01M1X75DFHGX3TW4QCRDV4W3P4", + "kind": "memory", + "score": 0.9999747276306152, + "summary": "project:fact - Copper daily database backups start at 02:40 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 896.8049, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 642666496, + "peak_working_set_bytes": 685117440 + }, + { + "query": "\u00bfQu\u00e9 base de datos y modo de registro usa Copper para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7565SHPQ73GM40GRQYYW5", + "id": "01M1X75EFM556NHWCGEQ1R1A36", + "kind": "memory", + "score": 0.9983224272727966, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 827.5897, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 642813952, + "peak_working_set_bytes": 685117440 + }, + { + "query": "What authentication password is configured for the Copper staging listener?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7563WQ1C0TZD8N8F4JCZ1", + "id": "01M1X75F5D5GGZFD872RPVFY0W", + "kind": "memory", + "score": 0.9784963130950928, + "summary": "project:fact - Copper staging HTTP listener binds TCP port 6319. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 520.6096, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 615, + "reported_used_tokens": 579, + "working_set_bytes": 642908160, + "peak_working_set_bytes": 685117440 + }, + { + "query": "What encryption key protects the Copper database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 444.1019, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 648704000, + "peak_working_set_bytes": 685117440 + }, + { + "query": "How many production replicas run in the Copper deployment region?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X756514BAK04ATXQKHP4AA", + "id": "01M1X75G3YGK1Y0J3GQE6X1E8E", + "kind": "memory", + "score": 0.8394170999526978, + "summary": "project:fact - Copper production runs in region eu-north-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 472.55740000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 648708096, + "peak_working_set_bytes": 685117440 + }, + { + "query": "\u00bfQu\u00e9 contrase\u00f1a exige el servidor de staging de Copper?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7563WQ1C0TZD8N8F4JCZ1", + "id": "01M1X75GJ46RFFFZ4998K8F53H", + "kind": "memory", + "score": 0.898059606552124, + "summary": "project:fact - Copper staging HTTP listener binds TCP port 6319. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 512.1649, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 497, + "mcp_result_bytes": 578, + "wire_bytes": 614, + "reported_used_tokens": 578, + "working_set_bytes": 648736768, + "peak_working_set_bytes": 685117440 + }, + { + "query": "\u00bfCu\u00e1ntos d\u00edas se conservan las copias de seguridad de Copper?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7565D2PG212EBJY65Q27V", + "id": "01M1X75H28RW95CMR72ACGR1SP", + "kind": "memory", + "score": 0.9550348520278932, + "summary": "project:fact - Copper daily database backups start at 02:40 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 413.6865, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 648814592, + "peak_working_set_bytes": 685117440 + }, + { + "query": "\u00bfQu\u00e9 versi\u00f3n de SQLite requiere Copper?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7565SHPQ73GM40GRQYYW5", + "id": "01M1X75HF5CMGFNRA66ZHNA807", + "kind": "memory", + "score": 0.6008884310722351, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 364.2525, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 648835072, + "peak_working_set_bytes": 685117440 + } + ], + "id": "copper-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 0.7222222222222222, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.833 (n=6) positive-n=12 negative-n=6 (18 queries)" + }, + { + "observations": [ + { + "query": "Which TCP port should I connect to for Willow staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75JWNVQQDSDWVJ8N3HH7F", + "id": "01M1X75MSG42JKX0H7GS1B7288", + "kind": "memory", + "score": 0.9999420642852784, + "summary": "project:fact - Willow staging HTTP listener binds TCP port 7421. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1853.4305, + "first_query": true, + "server_startup_ms": 73.5093, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 634368000, + "peak_working_set_bytes": 684843008 + }, + { + "query": "Where should Willow diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75JX4SC14ZEJ070R675E5", + "id": "01M1X75N5P6A5C75VDPM8W55J0", + "kind": "memory", + "score": 0.9971635937690736, + "summary": "project:fact - Willow diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X75JYMAB5KS5W09YYCZF9N", + "id": "01M1X75N5PZXN7P66871BVGQ32", + "kind": "memory", + "score": 0.8971153497695923, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 384.993, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 754, + "mcp_result_bytes": 853, + "wire_bytes": 888, + "reported_used_tokens": 853, + "working_set_bytes": 634855424, + "peak_working_set_bytes": 684843008 + }, + { + "query": "Which command rolls back Willow to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75JXGRRZN2HGW4TY5BH73", + "id": "01M1X75NJ22NC60BK3KTJC3E7B", + "kind": "memory", + "score": 0.9998542070388794, + "summary": "project:fact - To roll back Willow to the previous release, run `willowctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 381.25730000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 634912768, + "peak_working_set_bytes": 684843008 + }, + { + "query": "Which region hosts Willow production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75JXXRBRXJFRQH85YCQGV", + "id": "01M1X75NX0F67TEGGDNVCBB81D", + "kind": "memory", + "score": 0.9999713897705078, + "summary": "project:fact - Willow production runs in region us-west-2. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 348.233, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 634937344, + "peak_working_set_bytes": 684843008 + }, + { + "query": "At what UTC time do daily Willow database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75JY9WAP59SK69BF3E7JS", + "id": "01M1X75PBGSGC8VYZ1Q30D6Q9R", + "kind": "memory", + "score": 0.9999792575836182, + "summary": "project:fact - Willow daily database backups start at 04:15 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 482.6424, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 587, + "reported_used_tokens": 552, + "working_set_bytes": 635076608, + "peak_working_set_bytes": 684843008 + }, + { + "query": "Which database and journal mode does Willow use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75JYMAB5KS5W09YYCZF9N", + "id": "01M1X75PQ0M3VVYBKJRY74Y5RK", + "kind": "memory", + "score": 0.9999637603759766, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.6245, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 635203584, + "peak_working_set_bytes": 684843008 + }, + { + "query": "\u00bfA qu\u00e9 puerto TCP debo conectarme para staging de Willow?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75JWNVQQDSDWVJ8N3HH7F", + "id": "01M1X75Q29E7VXNX77RMXB930S", + "kind": "memory", + "score": 0.9999486207962036, + "summary": "project:fact - Willow staging HTTP listener binds TCP port 7421. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 369.1739, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 637595648, + "peak_working_set_bytes": 684843008 + }, + { + "query": "\u00bfD\u00f3nde deben escribirse los mensajes de diagn\u00f3stico de Willow?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75JX4SC14ZEJ070R675E5", + "id": "01M1X75QDYZ73W1A6X4G44BJW2", + "kind": "memory", + "score": 0.9996838569641112, + "summary": "project:fact - Willow diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 400.4669, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 488, + "mcp_result_bytes": 569, + "wire_bytes": 604, + "reported_used_tokens": 569, + "working_set_bytes": 637894656, + "peak_working_set_bytes": 684843008 + }, + { + "query": "\u00bfQu\u00e9 comando revierte Willow a la versi\u00f3n anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75JXGRRZN2HGW4TY5BH73", + "id": "01M1X75QT676E20G0PSSQM7F74", + "kind": "memory", + "score": 0.98951655626297, + "summary": "project:fact - To roll back Willow to the previous release, run `willowctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 375.85360000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 497, + "mcp_result_bytes": 578, + "wire_bytes": 614, + "reported_used_tokens": 578, + "working_set_bytes": 637984768, + "peak_working_set_bytes": 684843008 + }, + { + "query": "\u00bfEn qu\u00e9 regi\u00f3n est\u00e1 desplegado Willow en producci\u00f3n?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75JXXRBRXJFRQH85YCQGV", + "id": "01M1X75R66HC56M7BSX1ZBPXK9", + "kind": "memory", + "score": 0.9999579191207886, + "summary": "project:fact - Willow production runs in region us-west-2. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 375.9858, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 638029824, + "peak_working_set_bytes": 684843008 + }, + { + "query": "\u00bfA qu\u00e9 hora UTC empiezan las copias diarias de la base de datos de Willow?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75JY9WAP59SK69BF3E7JS", + "id": "01M1X75RJ1MND8TE8HDNQEV34T", + "kind": "memory", + "score": 0.99997878074646, + "summary": "project:fact - Willow daily database backups start at 04:15 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 386.0068, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 469, + "mcp_result_bytes": 550, + "wire_bytes": 586, + "reported_used_tokens": 550, + "working_set_bytes": 638328832, + "peak_working_set_bytes": 684843008 + }, + { + "query": "\u00bfQu\u00e9 base de datos y modo de registro usa Willow para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75JYMAB5KS5W09YYCZF9N", + "id": "01M1X75RYY2FC66CTAW894185Q", + "kind": "memory", + "score": 0.999204695224762, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 397.0801, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 491, + "mcp_result_bytes": 572, + "wire_bytes": 608, + "reported_used_tokens": 572, + "working_set_bytes": 638406656, + "peak_working_set_bytes": 684843008 + }, + { + "query": "What authentication password is configured for the Willow staging listener?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75JWNVQQDSDWVJ8N3HH7F", + "id": "01M1X75SA7Y0RG2V9Q6AERV1VB", + "kind": "memory", + "score": 0.9932246804237366, + "summary": "project:fact - Willow staging HTTP listener binds TCP port 7421. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 363.0219, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 615, + "reported_used_tokens": 579, + "working_set_bytes": 638537728, + "peak_working_set_bytes": 684843008 + }, + { + "query": "What encryption key protects the Willow database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 373.0392, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644509696, + "peak_working_set_bytes": 684843008 + }, + { + "query": "How many production replicas run in the Willow deployment region?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75JXXRBRXJFRQH85YCQGV", + "id": "01M1X75T15FW4XQDSBBKX65SQD", + "kind": "memory", + "score": 0.9349143505096436, + "summary": "project:fact - Willow production runs in region us-west-2. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 866.9441, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 644538368, + "peak_working_set_bytes": 684843008 + }, + { + "query": "\u00bfQu\u00e9 contrase\u00f1a exige el servidor de staging de Willow?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75JWNVQQDSDWVJ8N3HH7F", + "id": "01M1X75TWAXPXZ25N8EVVYNQ24", + "kind": "memory", + "score": 0.9681325554847716, + "summary": "project:fact - Willow staging HTTP listener binds TCP port 7421. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 358.98429999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 615, + "reported_used_tokens": 579, + "working_set_bytes": 644603904, + "peak_working_set_bytes": 684843008 + }, + { + "query": "\u00bfCu\u00e1ntos d\u00edas se conservan las copias de seguridad de Willow?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75JY9WAP59SK69BF3E7JS", + "id": "01M1X75V7RWV4CD5094Y103E7Z", + "kind": "memory", + "score": 0.9746375679969788, + "summary": "project:fact - Willow daily database backups start at 04:15 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 367.15409999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 644657152, + "peak_working_set_bytes": 684843008 + }, + { + "query": "\u00bfQu\u00e9 versi\u00f3n de SQLite requiere Willow?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75JYMAB5KS5W09YYCZF9N", + "id": "01M1X75VK03X61510WSEF80G52", + "kind": "memory", + "score": 0.7611488103866577, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 363.61929999999995, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 644681728, + "peak_working_set_bytes": 684843008 + } + ], + "id": "willow-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 0.7222222222222222, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.833 (n=6) positive-n=12 negative-n=6 (18 queries)" + }, + { + "observations": [ + { + "query": "Which TCP port should I connect to for Marble staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75X0JK77HA6ASPC75RTCG", + "id": "01M1X75Z1M550X0AEJDG9S1557", + "kind": "memory", + "score": 0.9996737241744996, + "summary": "project:fact - Marble staging HTTP listener binds TCP port 8533. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1905.4247, + "first_query": true, + "server_startup_ms": 76.13109999999999, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 633688064, + "peak_working_set_bytes": 685088768 + }, + { + "query": "Where should Marble diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75X14DYRD8W9JBCR6X5QB", + "id": "01M1X75ZD83MDRF36BR60CFAR4", + "kind": "memory", + "score": 0.9971815347671508, + "summary": "project:fact - Marble diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X75X2JN17PRJWSCWFNWBK4", + "id": "01M1X75ZD8223YFNYA85A8ZG7M", + "kind": "memory", + "score": 0.6012999415397644, + "summary": "project:fact - Marble stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 352.64820000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 754, + "mcp_result_bytes": 853, + "wire_bytes": 888, + "reported_used_tokens": 853, + "working_set_bytes": 634216448, + "peak_working_set_bytes": 685088768 + }, + { + "query": "Which command rolls back Marble to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75X1G7W07TBCPS05X7Z5B", + "id": "01M1X75ZRHGD1N6X3DR6N2K18V", + "kind": "memory", + "score": 0.9997585415840148, + "summary": "project:fact - To roll back Marble to the previous release, run `marblectl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 362.99989999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 634277888, + "peak_working_set_bytes": 685088768 + }, + { + "query": "Which region hosts Marble production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75X1WC3N08WBGFPAXFMYV", + "id": "01M1X7603QRBKBHZ91FV1F12E8", + "kind": "memory", + "score": 0.999954104423523, + "summary": "project:fact - Marble production runs in region ap-south-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 365.5619, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 634499072, + "peak_working_set_bytes": 685088768 + }, + { + "query": "At what UTC time do daily Marble database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75X27PPN8PK15GERP157R", + "id": "01M1X760FKRCSVGF1VCP7BMDJ5", + "kind": "memory", + "score": 0.999979853630066, + "summary": "project:fact - Marble daily database backups start at 01:25 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 380.0497, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 587, + "reported_used_tokens": 552, + "working_set_bytes": 634638336, + "peak_working_set_bytes": 685088768 + }, + { + "query": "Which database and journal mode does Marble use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75X2JN17PRJWSCWFNWBK4", + "id": "01M1X760VKCZSRZQX15MBGZCS8", + "kind": "memory", + "score": 0.9999568462371826, + "summary": "project:fact - Marble stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 382.44620000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 634732544, + "peak_working_set_bytes": 685088768 + }, + { + "query": "\u00bfA qu\u00e9 puerto TCP debo conectarme para staging de Marble?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75X0JK77HA6ASPC75RTCG", + "id": "01M1X761795ATESB6RFYW0PJJA", + "kind": "memory", + "score": 0.9998871088027954, + "summary": "project:fact - Marble staging HTTP listener binds TCP port 8533. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 481.5004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 637079552, + "peak_working_set_bytes": 685088768 + }, + { + "query": "\u00bfD\u00f3nde deben escribirse los mensajes de diagn\u00f3stico de Marble?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75X14DYRD8W9JBCR6X5QB", + "id": "01M1X761PY7M61EAWJWKSKXA31", + "kind": "memory", + "score": 0.9992142915725708, + "summary": "project:fact - Marble diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 403.7398, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 488, + "mcp_result_bytes": 569, + "wire_bytes": 604, + "reported_used_tokens": 569, + "working_set_bytes": 637366272, + "peak_working_set_bytes": 685088768 + }, + { + "query": "\u00bfQu\u00e9 comando revierte Marble a la versi\u00f3n anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75X1G7W07TBCPS05X7Z5B", + "id": "01M1X7622RK36GK90Z72KG7D66", + "kind": "memory", + "score": 0.976457178592682, + "summary": "project:fact - To roll back Marble to the previous release, run `marblectl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 360.7405, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 616, + "reported_used_tokens": 580, + "working_set_bytes": 637382656, + "peak_working_set_bytes": 685088768 + }, + { + "query": "\u00bfEn qu\u00e9 regi\u00f3n est\u00e1 desplegado Marble en producci\u00f3n?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75X1WC3N08WBGFPAXFMYV", + "id": "01M1X762DZHXTD73SD7R98CEZG", + "kind": "memory", + "score": 0.9999542236328124, + "summary": "project:fact - Marble production runs in region ap-south-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 361.8626, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 637411328, + "peak_working_set_bytes": 685088768 + }, + { + "query": "\u00bfA qu\u00e9 hora UTC empiezan las copias diarias de la base de datos de Marble?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75X27PPN8PK15GERP157R", + "id": "01M1X762SQGF77DJW5XEWJ1VXW", + "kind": "memory", + "score": 0.9999665021896362, + "summary": "project:fact - Marble daily database backups start at 01:25 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 387.2454, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 637689856, + "peak_working_set_bytes": 685088768 + }, + { + "query": "\u00bfQu\u00e9 base de datos y modo de registro usa Marble para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75X2JN17PRJWSCWFNWBK4", + "id": "01M1X7635HG932WGX7NMAC7TYV", + "kind": "memory", + "score": 0.9953057169914246, + "summary": "project:fact - Marble stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 367.8838, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 637784064, + "peak_working_set_bytes": 685088768 + }, + { + "query": "What authentication password is configured for the Marble staging listener?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75X0JK77HA6ASPC75RTCG", + "id": "01M1X763N6JB6KH9G454TF4YPD", + "kind": "memory", + "score": 0.9787366390228271, + "summary": "project:fact - Marble staging HTTP listener binds TCP port 8533. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 515.4746, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 615, + "reported_used_tokens": 579, + "working_set_bytes": 637911040, + "peak_working_set_bytes": 685088768 + }, + { + "query": "What encryption key protects the Marble database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 395.02029999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643751936, + "peak_working_set_bytes": 685088768 + }, + { + "query": "How many production replicas run in the Marble deployment region?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75X1WC3N08WBGFPAXFMYV", + "id": "01M1X764ECGRSJXBJ4YREE37AJ", + "kind": "memory", + "score": 0.8181904554367065, + "summary": "project:fact - Marble production runs in region ap-south-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 404.31660000000005, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 643756032, + "peak_working_set_bytes": 685088768 + }, + { + "query": "\u00bfQu\u00e9 contrase\u00f1a exige el servidor de staging de Marble?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75X0JK77HA6ASPC75RTCG", + "id": "01M1X764TK9K5XXMPBP54ZY6CA", + "kind": "memory", + "score": 0.8728806972503662, + "summary": "project:fact - Marble staging HTTP listener binds TCP port 8533. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 381.6885, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 615, + "reported_used_tokens": 579, + "working_set_bytes": 643813376, + "peak_working_set_bytes": 685088768 + }, + { + "query": "\u00bfCu\u00e1ntos d\u00edas se conservan las copias de seguridad de Marble?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75X27PPN8PK15GERP157R", + "id": "01M1X7657118WDZ4GX6WR5T2SR", + "kind": "memory", + "score": 0.9691649079322816, + "summary": "project:fact - Marble daily database backups start at 01:25 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 400.4255, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 643837952, + "peak_working_set_bytes": 685088768 + }, + { + "query": "\u00bfQu\u00e9 versi\u00f3n de SQLite requiere Marble?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 351.05589999999995, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643944448, + "peak_working_set_bytes": 685088768 + } + ], + "id": "marble-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 0.7777777777777778, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.667 (n=6) positive-n=12 negative-n=6 (18 queries)" + }, + { + "observations": [ + { + "query": "Which TCP port should I connect to for Kestrel staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X766Y6NBRMCZ9BHNQ17JA4", + "id": "01M1X768RYRGTRVCR56XBKASWF", + "kind": "memory", + "score": 0.9999393224716188, + "summary": "project:fact - Kestrel staging HTTP listener binds TCP port 9647. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1790.7, + "first_query": true, + "server_startup_ms": 72.8235, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 634667008, + "peak_working_set_bytes": 685305856 + }, + { + "query": "Where should Kestrel diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X766YRTMYZCF5PG3BT7JQH", + "id": "01M1X7694BYX6CMH14ND1S6KMS", + "kind": "memory", + "score": 0.9987480640411376, + "summary": "project:fact - Kestrel diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X76706EEE39556ANJACXAJ", + "id": "01M1X7694BH7H4WSB82ECGC1F0", + "kind": "memory", + "score": 0.9422296285629272, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 358.4332, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 756, + "mcp_result_bytes": 855, + "wire_bytes": 890, + "reported_used_tokens": 855, + "working_set_bytes": 636854272, + "peak_working_set_bytes": 685305856 + }, + { + "query": "Which command rolls back Kestrel to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X766Z4XAGDB7XSES05G89V", + "id": "01M1X769FNYVHGGJT9ZFQMGMF4", + "kind": "memory", + "score": 0.9997856020927428, + "summary": "project:fact - To roll back Kestrel to the previous release, run `kestrelctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 359.9409, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 501, + "mcp_result_bytes": 582, + "wire_bytes": 617, + "reported_used_tokens": 582, + "working_set_bytes": 637124608, + "peak_working_set_bytes": 685305856 + }, + { + "query": "Which region hosts Kestrel production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X766ZG52TK6XWWPHSMY3Z9", + "id": "01M1X769V44N1CAE0JPHS6P7SN", + "kind": "memory", + "score": 0.9999779462814332, + "summary": "project:fact - Kestrel production runs in region eu-west-3. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 371.1159, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 609, + "reported_used_tokens": 574, + "working_set_bytes": 637173760, + "peak_working_set_bytes": 685305856 + }, + { + "query": "At what UTC time do daily Kestrel database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X766ZVMDC5RTTWY6TXAVPZ", + "id": "01M1X76A723Q18XPFHM8QG78RK", + "kind": "memory", + "score": 0.999980330467224, + "summary": "project:fact - Kestrel daily database backups start at 03:50 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 381.5114, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 472, + "mcp_result_bytes": 553, + "wire_bytes": 588, + "reported_used_tokens": 553, + "working_set_bytes": 637382656, + "peak_working_set_bytes": 685305856 + }, + { + "query": "Which database and journal mode does Kestrel use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X76706EEE39556ANJACXAJ", + "id": "01M1X76AJBB8BH47W2ME65A15P", + "kind": "memory", + "score": 0.999975323677063, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 363.2204, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 637456384, + "peak_working_set_bytes": 685305856 + }, + { + "query": "\u00bfA qu\u00e9 puerto TCP debo conectarme para staging de Kestrel?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X766Y6NBRMCZ9BHNQ17JA4", + "id": "01M1X76AXRA02VXFTZ0EFTZ5SX", + "kind": "memory", + "score": 0.9999423027038574, + "summary": "project:fact - Kestrel staging HTTP listener binds TCP port 9647. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 365.5343, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 639909888, + "peak_working_set_bytes": 685305856 + }, + { + "query": "\u00bfD\u00f3nde deben escribirse los mensajes de diagn\u00f3stico de Kestrel?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X766YRTMYZCF5PG3BT7JQH", + "id": "01M1X76B96MDVMSCDMPN5JMCY2", + "kind": "memory", + "score": 0.9997678399086, + "summary": "project:fact - Kestrel diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 368.7294, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 489, + "mcp_result_bytes": 570, + "wire_bytes": 605, + "reported_used_tokens": 570, + "working_set_bytes": 640208896, + "peak_working_set_bytes": 685305856 + }, + { + "query": "\u00bfQu\u00e9 comando revierte Kestrel a la versi\u00f3n anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X766Z4XAGDB7XSES05G89V", + "id": "01M1X76BMMWFDGZRFK2TE1VT3Y", + "kind": "memory", + "score": 0.9766082763671876, + "summary": "project:fact - To roll back Kestrel to the previous release, run `kestrelctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 366.2494, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 501, + "mcp_result_bytes": 582, + "wire_bytes": 618, + "reported_used_tokens": 582, + "working_set_bytes": 640241664, + "peak_working_set_bytes": 685305856 + }, + { + "query": "\u00bfEn qu\u00e9 regi\u00f3n est\u00e1 desplegado Kestrel en producci\u00f3n?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X766ZG52TK6XWWPHSMY3Z9", + "id": "01M1X76C07RSMVW13DBDGNFZXZ", + "kind": "memory", + "score": 0.999974250793457, + "summary": "project:fact - Kestrel production runs in region eu-west-3. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 365.05159999999995, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 640286720, + "peak_working_set_bytes": 685305856 + }, + { + "query": "\u00bfA qu\u00e9 hora UTC empiezan las copias diarias de la base de datos de Kestrel?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X766ZVMDC5RTTWY6TXAVPZ", + "id": "01M1X76CBH1BNBQ53041ATSN95", + "kind": "memory", + "score": 0.9999799728393556, + "summary": "project:fact - Kestrel daily database backups start at 03:50 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 362.0205, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 472, + "mcp_result_bytes": 553, + "wire_bytes": 589, + "reported_used_tokens": 553, + "working_set_bytes": 640540672, + "peak_working_set_bytes": 685305856 + }, + { + "query": "\u00bfQu\u00e9 base de datos y modo de registro usa Kestrel para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X76706EEE39556ANJACXAJ", + "id": "01M1X76CQ24DG1KQ097BJKZ8GX", + "kind": "memory", + "score": 0.9993937015533448, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 369.45050000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 640647168, + "peak_working_set_bytes": 685305856 + }, + { + "query": "What authentication password is configured for the Kestrel staging listener?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X766Y6NBRMCZ9BHNQ17JA4", + "id": "01M1X76D2H9K9BWGHKTMKEJ177", + "kind": "memory", + "score": 0.9726881980895996, + "summary": "project:fact - Kestrel staging HTTP listener binds TCP port 9647. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 377.0689, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 616, + "reported_used_tokens": 580, + "working_set_bytes": 640659456, + "peak_working_set_bytes": 685305856 + }, + { + "query": "What encryption key protects the Kestrel database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 389.61560000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644767744, + "peak_working_set_bytes": 685305856 + }, + { + "query": "How many production replicas run in the Kestrel deployment region?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X766ZG52TK6XWWPHSMY3Z9", + "id": "01M1X76DT9CQMJ0C7APX3WVN9N", + "kind": "memory", + "score": 0.958982229232788, + "summary": "project:fact - Kestrel production runs in region eu-west-3. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 362.2417, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 644886528, + "peak_working_set_bytes": 685305856 + }, + { + "query": "\u00bfQu\u00e9 contrase\u00f1a exige el servidor de staging de Kestrel?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X766Y6NBRMCZ9BHNQ17JA4", + "id": "01M1X76E5N62F63XHA1W2DW0J3", + "kind": "memory", + "score": 0.9609549045562744, + "summary": "project:fact - Kestrel staging HTTP listener binds TCP port 9647. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 360.3782, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 616, + "reported_used_tokens": 580, + "working_set_bytes": 644943872, + "peak_working_set_bytes": 685305856 + }, + { + "query": "\u00bfCu\u00e1ntos d\u00edas se conservan las copias de seguridad de Kestrel?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X766ZVMDC5RTTWY6TXAVPZ", + "id": "01M1X76EGXAFM71FMSFXVBSK90", + "kind": "memory", + "score": 0.9748653173446656, + "summary": "project:fact - Kestrel daily database backups start at 03:50 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 362.4495, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 472, + "mcp_result_bytes": 553, + "wire_bytes": 589, + "reported_used_tokens": 553, + "working_set_bytes": 645029888, + "peak_working_set_bytes": 685305856 + }, + { + "query": "\u00bfQu\u00e9 versi\u00f3n de SQLite requiere Kestrel?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X76706EEE39556ANJACXAJ", + "id": "01M1X76EW8PHJ9MQGJFAW7JX2Z", + "kind": "memory", + "score": 0.6894522309303284, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 358.80640000000005, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 645062656, + "peak_working_set_bytes": 685305856 + } + ], + "id": "kestrel-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 0.7222222222222222, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.833 (n=6) positive-n=12 negative-n=6 (18 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 2.9444444444444446, + 4 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 0.7361111111111112, + "n": 4, + "ci95": 0.027222222222222234 + } + }, + "overall_index": 0.7361111111111112, + "scenario_weighted_index": 0.7361111111111112 +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/2-candidate.json b/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/2-candidate.json new file mode 100644 index 0000000..3df3b03 --- /dev/null +++ b/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/2-candidate.json @@ -0,0 +1,1921 @@ +{ + "generated_at": "2026-09-07T05:58:44.3510252Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-retrieval\\validation-frozen.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "Which TCP port should I connect to for Copper staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7415BCHYJ174CNYY5ESP2", + "id": "01M1X742ZVMMQE6VQ0EZJS356N", + "kind": "memory", + "score": 0.9998394250869752, + "summary": "project:fact - Copper staging HTTP listener binds TCP port 6319. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1786.1039, + "first_query": true, + "server_startup_ms": 87.55749999999999, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 632578048, + "peak_working_set_bytes": 685015040 + }, + { + "query": "Where should Copper diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7415T05YGJHNYW88V2R17", + "id": "01M1X743AZ256F8S7YFMZXT44H", + "kind": "memory", + "score": 0.9978247880935668, + "summary": "project:fact - Copper diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X74179ZM8H9FMP4MPJ5YKW", + "id": "01M1X743AZ5TKWVV6YSX2C2E9R", + "kind": "memory", + "score": 0.6390834450721741, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 345.5503, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 754, + "mcp_result_bytes": 853, + "wire_bytes": 888, + "reported_used_tokens": 853, + "working_set_bytes": 633077760, + "peak_working_set_bytes": 685015040 + }, + { + "query": "Which command rolls back Copper to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X741659TTYD471RQW9GR23", + "id": "01M1X743NVY7RGD2X0JBA7A1WP", + "kind": "memory", + "score": 0.9998852014541626, + "summary": "project:fact - To roll back Copper to the previous release, run `copperctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 355.9703, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 635113472, + "peak_working_set_bytes": 685015040 + }, + { + "query": "Which region hosts Copper production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7416HKRPXN4Y5RCXM49VK", + "id": "01M1X7440X3Q45PRNE411B5GVH", + "kind": "memory", + "score": 0.9999468326568604, + "summary": "project:fact - Copper production runs in region eu-north-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 346.21439999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 609, + "reported_used_tokens": 574, + "working_set_bytes": 635363328, + "peak_working_set_bytes": 685015040 + }, + { + "query": "At what UTC time do daily Copper database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7416XVY2RTN8BSYY9MG0R", + "id": "01M1X744C0CGNQCMWEAKBMAFKV", + "kind": "memory", + "score": 0.9999791383743286, + "summary": "project:fact - Copper daily database backups start at 02:40 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 359.9774, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 587, + "reported_used_tokens": 552, + "working_set_bytes": 635772928, + "peak_working_set_bytes": 685015040 + }, + { + "query": "Which database and journal mode does Copper use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74179ZM8H9FMP4MPJ5YKW", + "id": "01M1X744Q3S2THTJK18Q06WSAE", + "kind": "memory", + "score": 0.9999594688415528, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.07280000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 635850752, + "peak_working_set_bytes": 685015040 + }, + { + "query": "\u00bfA qu\u00e9 puerto TCP debo conectarme para staging de Copper?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7415BCHYJ174CNYY5ESP2", + "id": "01M1X74525002Z7TVWB6FADKJ5", + "kind": "memory", + "score": 0.9998682737350464, + "summary": "project:fact - Copper staging HTTP listener binds TCP port 6319. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 366.4515, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 638296064, + "peak_working_set_bytes": 685015040 + }, + { + "query": "\u00bfD\u00f3nde deben escribirse los mensajes de diagn\u00f3stico de Copper?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7415T05YGJHNYW88V2R17", + "id": "01M1X745DKJK042H8ZD0D3C4NF", + "kind": "memory", + "score": 0.9995033740997314, + "summary": "project:fact - Copper diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 361.3282, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 488, + "mcp_result_bytes": 569, + "wire_bytes": 604, + "reported_used_tokens": 569, + "working_set_bytes": 638763008, + "peak_working_set_bytes": 685015040 + }, + { + "query": "\u00bfQu\u00e9 comando revierte Copper a la versi\u00f3n anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X741659TTYD471RQW9GR23", + "id": "01M1X745S3G744MX5E600GGCTJ", + "kind": "memory", + "score": 0.9887914657592772, + "summary": "project:fact - To roll back Copper to the previous release, run `copperctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 371.9409, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 616, + "reported_used_tokens": 580, + "working_set_bytes": 638824448, + "peak_working_set_bytes": 685015040 + }, + { + "query": "\u00bfEn qu\u00e9 regi\u00f3n est\u00e1 desplegado Copper en producci\u00f3n?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7416HKRPXN4Y5RCXM49VK", + "id": "01M1X7464Y88QSKW4SH5GESBQG", + "kind": "memory", + "score": 0.9999468326568604, + "summary": "project:fact - Copper production runs in region eu-north-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 366.9749, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 638898176, + "peak_working_set_bytes": 685015040 + }, + { + "query": "\u00bfA qu\u00e9 hora UTC empiezan las copias diarias de la base de datos de Copper?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7416XVY2RTN8BSYY9MG0R", + "id": "01M1X746G0X7W36GWE6RWP67G5", + "kind": "memory", + "score": 0.9999747276306152, + "summary": "project:fact - Copper daily database backups start at 02:40 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 358.07640000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 639193088, + "peak_working_set_bytes": 685015040 + }, + { + "query": "\u00bfQu\u00e9 base de datos y modo de registro usa Copper para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74179ZM8H9FMP4MPJ5YKW", + "id": "01M1X746V60P9773RMGEBS7V2S", + "kind": "memory", + "score": 0.9983224272727966, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 357.4445, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 639213568, + "peak_working_set_bytes": 685015040 + }, + { + "query": "What authentication password is configured for the Copper staging listener?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 354.8418, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 639352832, + "peak_working_set_bytes": 685015040 + }, + { + "query": "What encryption key protects the Copper database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 362.0191, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645271552, + "peak_working_set_bytes": 685015040 + }, + { + "query": "How many production replicas run in the Copper deployment region?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 354.29519999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645337088, + "peak_working_set_bytes": 685015040 + }, + { + "query": "\u00bfQu\u00e9 contrase\u00f1a exige el servidor de staging de Copper?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 363.89840000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645382144, + "peak_working_set_bytes": 685015040 + }, + { + "query": "\u00bfCu\u00e1ntos d\u00edas se conservan las copias de seguridad de Copper?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 358.7629, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645451776, + "peak_working_set_bytes": 685015040 + }, + { + "query": "\u00bfQu\u00e9 versi\u00f3n de SQLite requiere Copper?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 370.8095, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645459968, + "peak_working_set_bytes": 685015040 + } + ], + "id": "copper-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.000 (n=6) positive-n=12 negative-n=6 (18 queries)" + }, + { + "observations": [ + { + "query": "Which TCP port should I connect to for Willow staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74AAFXP73Y67EH78NA71F", + "id": "01M1X74C45J8836A8VAPWFYSNA", + "kind": "memory", + "score": 0.9999420642852784, + "summary": "project:fact - Willow staging HTTP listener binds TCP port 7421. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1774.9456, + "first_query": true, + "server_startup_ms": 72.9686, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 634089472, + "peak_working_set_bytes": 684953600 + }, + { + "query": "Where should Willow diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74AAYHQ3QAY2636X45HKB", + "id": "01M1X74CF9PE3DCF9AQC6RQM16", + "kind": "memory", + "score": 0.9971635937690736, + "summary": "project:fact - Willow diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X74ACDYT6BP5K98X3TTYF4", + "id": "01M1X74CF9970SJVEE5B0J5Y9H", + "kind": "memory", + "score": 0.8971153497695923, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 346.551, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 754, + "mcp_result_bytes": 853, + "wire_bytes": 888, + "reported_used_tokens": 853, + "working_set_bytes": 634552320, + "peak_working_set_bytes": 684953600 + }, + { + "query": "Which command rolls back Willow to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74ABABAVG84KTNSXNRMFG", + "id": "01M1X74CTBN182PNB05AT69CZP", + "kind": "memory", + "score": 0.9998542070388794, + "summary": "project:fact - To roll back Willow to the previous release, run `willowctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 360.9593, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 636661760, + "peak_working_set_bytes": 684953600 + }, + { + "query": "Which region hosts Willow production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74ABPP0X4QYY4CYR1CNZT", + "id": "01M1X74D5AHC83Q24D3RPJQ94X", + "kind": "memory", + "score": 0.9999713897705078, + "summary": "project:fact - Willow production runs in region us-west-2. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 342.2693, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 636747776, + "peak_working_set_bytes": 684953600 + }, + { + "query": "At what UTC time do daily Willow database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74AC110P4ZQRZHH6M5RDK", + "id": "01M1X74DGF1815KSB1FKJ3MSEA", + "kind": "memory", + "score": 0.9999792575836182, + "summary": "project:fact - Willow daily database backups start at 04:15 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 363.5498, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 587, + "reported_used_tokens": 552, + "working_set_bytes": 636882944, + "peak_working_set_bytes": 684953600 + }, + { + "query": "Which database and journal mode does Willow use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74ACDYT6BP5K98X3TTYF4", + "id": "01M1X74DVWMQWCKQ0F24Z94E8X", + "kind": "memory", + "score": 0.9999637603759766, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 384.21509999999995, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 636985344, + "peak_working_set_bytes": 684953600 + }, + { + "query": "\u00bfA qu\u00e9 puerto TCP debo conectarme para staging de Willow?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74AAFXP73Y67EH78NA71F", + "id": "01M1X74E7J5XPRYKWFC4CHDTYX", + "kind": "memory", + "score": 0.9999486207962036, + "summary": "project:fact - Willow staging HTTP listener binds TCP port 7421. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 359.0758, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 639369216, + "peak_working_set_bytes": 684953600 + }, + { + "query": "\u00bfD\u00f3nde deben escribirse los mensajes de diagn\u00f3stico de Willow?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74AAYHQ3QAY2636X45HKB", + "id": "01M1X74EJRBQ4MGAYKATF2ZZ8K", + "kind": "memory", + "score": 0.9996838569641112, + "summary": "project:fact - Willow diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 360.56149999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 488, + "mcp_result_bytes": 569, + "wire_bytes": 604, + "reported_used_tokens": 569, + "working_set_bytes": 639717376, + "peak_working_set_bytes": 684953600 + }, + { + "query": "\u00bfQu\u00e9 comando revierte Willow a la versi\u00f3n anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74ABABAVG84KTNSXNRMFG", + "id": "01M1X74EXYK9PF14Z7BPCJ45ZY", + "kind": "memory", + "score": 0.98951655626297, + "summary": "project:fact - To roll back Willow to the previous release, run `willowctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 350.4021, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 497, + "mcp_result_bytes": 578, + "wire_bytes": 614, + "reported_used_tokens": 578, + "working_set_bytes": 639905792, + "peak_working_set_bytes": 684953600 + }, + { + "query": "\u00bfEn qu\u00e9 regi\u00f3n est\u00e1 desplegado Willow en producci\u00f3n?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74ABPP0X4QYY4CYR1CNZT", + "id": "01M1X74F8ZHS7MRAZRPTJ2SGGQ", + "kind": "memory", + "score": 0.9999579191207886, + "summary": "project:fact - Willow production runs in region us-west-2. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 351.3888, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 639926272, + "peak_working_set_bytes": 684953600 + }, + { + "query": "\u00bfA qu\u00e9 hora UTC empiezan las copias diarias de la base de datos de Willow?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74AC110P4ZQRZHH6M5RDK", + "id": "01M1X74FM515R2P13D2VA4BCBC", + "kind": "memory", + "score": 0.99997878074646, + "summary": "project:fact - Willow daily database backups start at 04:15 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 364.3312, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 469, + "mcp_result_bytes": 550, + "wire_bytes": 586, + "reported_used_tokens": 550, + "working_set_bytes": 640262144, + "peak_working_set_bytes": 684953600 + }, + { + "query": "\u00bfQu\u00e9 base de datos y modo de registro usa Willow para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74ACDYT6BP5K98X3TTYF4", + "id": "01M1X74FZC3ZB6WF75B9NV678Z", + "kind": "memory", + "score": 0.999204695224762, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 395.9871, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 491, + "mcp_result_bytes": 572, + "wire_bytes": 608, + "reported_used_tokens": 572, + "working_set_bytes": 640331776, + "peak_working_set_bytes": 684953600 + }, + { + "query": "What authentication password is configured for the Willow staging listener?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 361.6691, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 640503808, + "peak_working_set_bytes": 684953600 + }, + { + "query": "What encryption key protects the Willow database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 363.5083, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 646361088, + "peak_working_set_bytes": 684953600 + }, + { + "query": "How many production replicas run in the Willow deployment region?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 376.35020000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 646516736, + "peak_working_set_bytes": 684953600 + }, + { + "query": "\u00bfQu\u00e9 contrase\u00f1a exige el servidor de staging de Willow?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 372.259, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 646598656, + "peak_working_set_bytes": 684953600 + }, + { + "query": "\u00bfCu\u00e1ntos d\u00edas se conservan las copias de seguridad de Willow?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 358.5102, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 646852608, + "peak_working_set_bytes": 684953600 + }, + { + "query": "\u00bfQu\u00e9 versi\u00f3n de SQLite requiere Willow?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 352.434, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 646914048, + "peak_working_set_bytes": 684953600 + } + ], + "id": "willow-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.000 (n=6) positive-n=12 negative-n=6 (18 queries)" + }, + { + "observations": [ + { + "query": "Which TCP port should I connect to for Marble staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74KGAVVH202MFDYYGDNH7", + "id": "01M1X74NABYXVFQBFS8VF19RMR", + "kind": "memory", + "score": 0.9996737241744996, + "summary": "project:fact - Marble staging HTTP listener binds TCP port 8533. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1798.0515, + "first_query": true, + "server_startup_ms": 74.2368, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 629190656, + "peak_working_set_bytes": 684789760 + }, + { + "query": "Where should Marble diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74KGVC5QW3FPTV1RDDZ1P", + "id": "01M1X74NNSGX4YMG5JQ6K6BRTP", + "kind": "memory", + "score": 0.9971815347671508, + "summary": "project:fact - Marble diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X74KJ9VG6PXF9GTMWAAVBF", + "id": "01M1X74NNT4YXP2GSHCZX446NE", + "kind": "memory", + "score": 0.6012999415397644, + "summary": "project:fact - Marble stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 354.2907, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 754, + "mcp_result_bytes": 853, + "wire_bytes": 888, + "reported_used_tokens": 853, + "working_set_bytes": 629678080, + "peak_working_set_bytes": 684789760 + }, + { + "query": "Which command rolls back Marble to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74KH6F8EC1RYSZFF50DPF", + "id": "01M1X74P0ZPSC38GWNA6JY52RX", + "kind": "memory", + "score": 0.9997585415840148, + "summary": "project:fact - To roll back Marble to the previous release, run `marblectl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 389.2509, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 632147968, + "peak_working_set_bytes": 684789760 + }, + { + "query": "Which region hosts Marble production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74KHJ5C1KZ61SCS4G982K", + "id": "01M1X74PCQMQG6BTFHZ68316MJ", + "kind": "memory", + "score": 0.999954104423523, + "summary": "project:fact - Marble production runs in region ap-south-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 346.4203, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 632340480, + "peak_working_set_bytes": 684789760 + }, + { + "query": "At what UTC time do daily Marble database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74KHXBV6MQ4J0QWN0DE2Q", + "id": "01M1X74PQNH3YXZSW2STQKSW90", + "kind": "memory", + "score": 0.999979853630066, + "summary": "project:fact - Marble daily database backups start at 01:25 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 353.64709999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 587, + "reported_used_tokens": 552, + "working_set_bytes": 632512512, + "peak_working_set_bytes": 684789760 + }, + { + "query": "Which database and journal mode does Marble use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74KJ9VG6PXF9GTMWAAVBF", + "id": "01M1X74Q40H3HFSJ36YQC6M0Y5", + "kind": "memory", + "score": 0.9999568462371826, + "summary": "project:fact - Marble stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 407.0996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 632619008, + "peak_working_set_bytes": 684789760 + }, + { + "query": "\u00bfA qu\u00e9 puerto TCP debo conectarme para staging de Marble?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74KGAVVH202MFDYYGDNH7", + "id": "01M1X74QGASZ4ZJZCPGFZBWKP3", + "kind": "memory", + "score": 0.9998871088027954, + "summary": "project:fact - Marble staging HTTP listener binds TCP port 8533. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 392.1516, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 634892288, + "peak_working_set_bytes": 684789760 + }, + { + "query": "\u00bfD\u00f3nde deben escribirse los mensajes de diagn\u00f3stico de Marble?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74KGVC5QW3FPTV1RDDZ1P", + "id": "01M1X74QWDHZEXW3T5H11E0HVD", + "kind": "memory", + "score": 0.9992142915725708, + "summary": "project:fact - Marble diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 389.017, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 488, + "mcp_result_bytes": 569, + "wire_bytes": 604, + "reported_used_tokens": 569, + "working_set_bytes": 635305984, + "peak_working_set_bytes": 684789760 + }, + { + "query": "\u00bfQu\u00e9 comando revierte Marble a la versi\u00f3n anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74KH6F8EC1RYSZFF50DPF", + "id": "01M1X74R7WKKGZZQM1DDJCKMEF", + "kind": "memory", + "score": 0.976457178592682, + "summary": "project:fact - To roll back Marble to the previous release, run `marblectl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.88239999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 616, + "reported_used_tokens": 580, + "working_set_bytes": 635351040, + "peak_working_set_bytes": 684789760 + }, + { + "query": "\u00bfEn qu\u00e9 regi\u00f3n est\u00e1 desplegado Marble en producci\u00f3n?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74KHJ5C1KZ61SCS4G982K", + "id": "01M1X74RK9N4ARHK3232KT21DX", + "kind": "memory", + "score": 0.9999542236328124, + "summary": "project:fact - Marble production runs in region ap-south-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 362.1453, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 635432960, + "peak_working_set_bytes": 684789760 + }, + { + "query": "\u00bfA qu\u00e9 hora UTC empiezan las copias diarias de la base de datos de Marble?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74KHXBV6MQ4J0QWN0DE2Q", + "id": "01M1X74RYF0RE6RFNA78ZGDHQ9", + "kind": "memory", + "score": 0.9999665021896362, + "summary": "project:fact - Marble daily database backups start at 01:25 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 369.9992, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 635744256, + "peak_working_set_bytes": 684789760 + }, + { + "query": "\u00bfQu\u00e9 base de datos y modo de registro usa Marble para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74KJ9VG6PXF9GTMWAAVBF", + "id": "01M1X74SA9SCYZJAEXAVKTEKHZ", + "kind": "memory", + "score": 0.9953057169914246, + "summary": "project:fact - Marble stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 383.40180000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 635809792, + "peak_working_set_bytes": 684789760 + }, + { + "query": "What authentication password is configured for the Marble staging listener?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 369.97999999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 635899904, + "peak_working_set_bytes": 684789760 + }, + { + "query": "What encryption key protects the Marble database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 353.71029999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 641761280, + "peak_working_set_bytes": 684789760 + }, + { + "query": "How many production replicas run in the Marble deployment region?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 356.7172, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 641781760, + "peak_working_set_bytes": 684789760 + }, + { + "query": "\u00bfQu\u00e9 contrase\u00f1a exige el servidor de staging de Marble?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 356.9958, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 641904640, + "peak_working_set_bytes": 684789760 + }, + { + "query": "\u00bfCu\u00e1ntos d\u00edas se conservan las copias de seguridad de Marble?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 367.3115, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 642134016, + "peak_working_set_bytes": 684789760 + }, + { + "query": "\u00bfQu\u00e9 versi\u00f3n de SQLite requiere Marble?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 352.53679999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 642207744, + "peak_working_set_bytes": 684789760 + } + ], + "id": "marble-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.000 (n=6) positive-n=12 negative-n=6 (18 queries)" + }, + { + "observations": [ + { + "query": "Which TCP port should I connect to for Kestrel staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74WT3G9E8X615FY1VQF2F", + "id": "01M1X74YN1MM2R48HPDYS8MMXA", + "kind": "memory", + "score": 0.9999393224716188, + "summary": "project:fact - Kestrel staging HTTP listener binds TCP port 9647. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1799.1631, + "first_query": true, + "server_startup_ms": 87.9836, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 634990592, + "peak_working_set_bytes": 685191168 + }, + { + "query": "Where should Kestrel diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74WTMDHA14XF6ZJP0SZ6A", + "id": "01M1X74Z05N8TCMQ2X5T4T7S5N", + "kind": "memory", + "score": 0.9987480640411376, + "summary": "project:fact - Kestrel diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X74WW4RWSB49A09HKGNN8F", + "id": "01M1X74Z05D0SS9PWPY0ZC1967", + "kind": "memory", + "score": 0.9422296285629272, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 348.8137, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 756, + "mcp_result_bytes": 855, + "wire_bytes": 890, + "reported_used_tokens": 855, + "working_set_bytes": 637157376, + "peak_working_set_bytes": 685191168 + }, + { + "query": "Which command rolls back Kestrel to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74WV0WHHXHYF9K582YARS", + "id": "01M1X74ZB416TPEYMPYKKC8F1G", + "kind": "memory", + "score": 0.9997856020927428, + "summary": "project:fact - To roll back Kestrel to the previous release, run `kestrelctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.0393, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 501, + "mcp_result_bytes": 582, + "wire_bytes": 617, + "reported_used_tokens": 582, + "working_set_bytes": 639414272, + "peak_working_set_bytes": 685191168 + }, + { + "query": "Which region hosts Kestrel production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74WVCSS6SA5FTTNZRRWT9", + "id": "01M1X74ZP5RFMJ4XTRA2ZKC06R", + "kind": "memory", + "score": 0.9999779462814332, + "summary": "project:fact - Kestrel production runs in region eu-west-3. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 346.9871, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 609, + "reported_used_tokens": 574, + "working_set_bytes": 639672320, + "peak_working_set_bytes": 685191168 + }, + { + "query": "At what UTC time do daily Kestrel database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74WVS4D1527GT7WDEA13K", + "id": "01M1X7501922Q3WDK6AGBR8C58", + "kind": "memory", + "score": 0.999980330467224, + "summary": "project:fact - Kestrel daily database backups start at 03:50 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 362.404, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 472, + "mcp_result_bytes": 553, + "wire_bytes": 588, + "reported_used_tokens": 553, + "working_set_bytes": 640102400, + "peak_working_set_bytes": 685191168 + }, + { + "query": "Which database and journal mode does Kestrel use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74WW4RWSB49A09HKGNN8F", + "id": "01M1X750CF7Q5G0HVQ91F0Q5JQ", + "kind": "memory", + "score": 0.999975323677063, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 358.2096, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 640221184, + "peak_working_set_bytes": 685191168 + }, + { + "query": "\u00bfA qu\u00e9 puerto TCP debo conectarme para staging de Kestrel?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74WT3G9E8X615FY1VQF2F", + "id": "01M1X750QPH89W9W2A5KXXJSB2", + "kind": "memory", + "score": 0.9999423027038574, + "summary": "project:fact - Kestrel staging HTTP listener binds TCP port 9647. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 369.2017, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 642719744, + "peak_working_set_bytes": 685191168 + }, + { + "query": "\u00bfD\u00f3nde deben escribirse los mensajes de diagn\u00f3stico de Kestrel?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74WTMDHA14XF6ZJP0SZ6A", + "id": "01M1X7513BFC7BRHFCVFD3AS9J", + "kind": "memory", + "score": 0.9997678399086, + "summary": "project:fact - Kestrel diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 370.25590000000005, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 489, + "mcp_result_bytes": 570, + "wire_bytes": 605, + "reported_used_tokens": 570, + "working_set_bytes": 642830336, + "peak_working_set_bytes": 685191168 + }, + { + "query": "\u00bfQu\u00e9 comando revierte Kestrel a la versi\u00f3n anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74WV0WHHXHYF9K582YARS", + "id": "01M1X751F0H9MKZG03M2W97Q88", + "kind": "memory", + "score": 0.9766082763671876, + "summary": "project:fact - To roll back Kestrel to the previous release, run `kestrelctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 384.2406, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 501, + "mcp_result_bytes": 582, + "wire_bytes": 618, + "reported_used_tokens": 582, + "working_set_bytes": 642985984, + "peak_working_set_bytes": 685191168 + }, + { + "query": "\u00bfEn qu\u00e9 regi\u00f3n est\u00e1 desplegado Kestrel en producci\u00f3n?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74WVCSS6SA5FTTNZRRWT9", + "id": "01M1X751TRPXKT395TGB0BXATF", + "kind": "memory", + "score": 0.999974250793457, + "summary": "project:fact - Kestrel production runs in region eu-west-3. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 357.7899, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 643039232, + "peak_working_set_bytes": 685191168 + }, + { + "query": "\u00bfA qu\u00e9 hora UTC empiezan las copias diarias de la base de datos de Kestrel?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74WVS4D1527GT7WDEA13K", + "id": "01M1X7525Y9WT6EBJ6WBMB65P5", + "kind": "memory", + "score": 0.9999799728393556, + "summary": "project:fact - Kestrel daily database backups start at 03:50 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.3876, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 472, + "mcp_result_bytes": 553, + "wire_bytes": 589, + "reported_used_tokens": 553, + "working_set_bytes": 643321856, + "peak_working_set_bytes": 685191168 + }, + { + "query": "\u00bfQu\u00e9 base de datos y modo de registro usa Kestrel para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74WW4RWSB49A09HKGNN8F", + "id": "01M1X752H5VX2RWE9BAHMV4H10", + "kind": "memory", + "score": 0.9993937015533448, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 362.36249999999995, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 643420160, + "peak_working_set_bytes": 685191168 + }, + { + "query": "What authentication password is configured for the Kestrel staging listener?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 358.2715, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643592192, + "peak_working_set_bytes": 685191168 + }, + { + "query": "What encryption key protects the Kestrel database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 364.0852, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 647688192, + "peak_working_set_bytes": 685191168 + }, + { + "query": "How many production replicas run in the Kestrel deployment region?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 357.2308, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 647901184, + "peak_working_set_bytes": 685191168 + }, + { + "query": "\u00bfQu\u00e9 contrase\u00f1a exige el servidor de staging de Kestrel?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 371.31399999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 647966720, + "peak_working_set_bytes": 685191168 + }, + { + "query": "\u00bfCu\u00e1ntos d\u00edas se conservan las copias de seguridad de Kestrel?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 370.6778, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 648126464, + "peak_working_set_bytes": 685191168 + }, + { + "query": "\u00bfQu\u00e9 versi\u00f3n de SQLite requiere Kestrel?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 377.43080000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 648179712, + "peak_working_set_bytes": 685191168 + } + ], + "id": "kestrel-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.000 (n=6) positive-n=12 negative-n=6 (18 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 4.0, + 4 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 1.0, + "n": 4, + "ci95": 0.0 + } + }, + "overall_index": 1.0, + "scenario_weighted_index": 1.0 +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/comparison.json b/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/comparison.json new file mode 100644 index 0000000..161d199 --- /dev/null +++ b/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/comparison.json @@ -0,0 +1,199 @@ +{ + "schema_version": 1, + "status": "complete", + "harness": { + "path": "E:\\Kimetsu\\bench\\target\\release\\kbench.exe", + "sha256": "33e0a3fe1c19aaed4d2fc4aad66653d5feeef8ec67c554c2ba9a77b8a6f39c38", + "bytes": 9348096 + }, + "runner": { + "path": "E:\\tmp\\kimetsu-brain-hardening\\bench\\scripts\\compare_brainbench.py", + "sha256": "738bad9404a4ec2b911fff661967ca56f48b584dfeb22f83823c972a1498df37", + "bytes": 24527 + }, + "binaries": { + "baseline": { + "path": "E:\\tmp\\kimetsu-brain-hardening\\tmp-tests\\kimetsu-answerability-candidate.exe", + "sha256": "405d3483fe320e76b0ec776bf9ada3b7771852b04a73f70f3b5da377a43d31c3", + "bytes": 47151104 + }, + "candidate": { + "path": "E:\\tmp\\kimetsu-brain-hardening\\tmp-tests\\kimetsu-answerability-candidate.exe", + "sha256": "405d3483fe320e76b0ec776bf9ada3b7771852b04a73f70f3b5da377a43d31c3", + "bytes": 47151104 + } + }, + "datasets": [ + { + "path": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-retrieval\\validation-frozen.json", + "sha256": "1a09270e09a9521c38f9dca14dfebe4349fed0e10b1537a92857eb18d3f6bb6f", + "bytes": 31222 + } + ], + "settings": { + "budget_tokens": 6000, + "dimensions": [ + "poisoning", + "render-contract", + "retrieval", + "workflow" + ], + "jobs": 1, + "warm_start": false, + "include_ambient": false, + "overrides": { + "KIMETSU_BRAIN_EMBEDDER": "bge-small-en-v1.5", + "KIMETSU_DETECT_CONFLICTS": "0", + "KIMETSU_RESOLVE_CONFLICTS": "0", + "FASTEMBED_CACHE_DIR": "E:/Kimetsu/.fastembed_cache", + "HF_HOME": "E:/tmp/kimetsu-brain-hardening/tmp-tests/hf-home" + }, + "baseline_threads": 0, + "candidate_threads": 0, + "baseline_reranker": "mmarco-minilm-l12-v2-int8", + "candidate_reranker": "mmarco-minilm-l12-v2-int8", + "baseline_rerank_floor": 0.55, + "candidate_rerank_floor": 0.55 + }, + "runs": [ + { + "label": "baseline", + "repeat": 1, + "intra_threads_override": null, + "rerank_floor_override": "0.55", + "explicit_fact_guard_override": "false", + "reranker_override": "mmarco-minilm-l12-v2-int8", + "wall_seconds": 39.08628879999742, + "report_file": "1-baseline.json" + }, + { + "label": "candidate", + "repeat": 1, + "intra_threads_override": null, + "rerank_floor_override": "0.55", + "explicit_fact_guard_override": "true", + "reranker_override": "mmarco-minilm-l12-v2-int8", + "wall_seconds": 39.26712530001532, + "report_file": "1-candidate.json" + }, + { + "label": "candidate", + "repeat": 2, + "intra_threads_override": null, + "rerank_floor_override": "0.55", + "explicit_fact_guard_override": "true", + "reranker_override": "mmarco-minilm-l12-v2-int8", + "wall_seconds": 37.855620399990585, + "report_file": "2-candidate.json" + }, + { + "label": "baseline", + "repeat": 2, + "intra_threads_override": null, + "rerank_floor_override": "0.55", + "explicit_fact_guard_override": "false", + "reranker_override": "mmarco-minilm-l12-v2-int8", + "wall_seconds": 43.19962289999239, + "report_file": "2-baseline.json" + } + ], + "comparison": { + "measurement_summary": { + "baseline": { + "unique_queries": 72, + "query_observations": 144, + "positive_queries": 48, + "negative_queries": 24, + "stale_queries": 8, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": 1, + "positive_mrr": 1.0, + "negative_injection_rate": 0.7916666666666666, + "stale_injection_rate": 0, + "first_query_mean_ms": 1837.635875, + "subsequent_query_p50_ms": 366.1139, + "subsequent_query_p95_ms": 516.5822, + "subsequent_observations": 136, + "mean_model_text_bytes": 486.7083333333333, + "mean_mcp_result_bytes": 567.4583333333334, + "memory_observations": 144, + "mean_mcp_working_set_bytes": 639144760.8888888, + "max_mcp_peak_working_set_bytes": 685305856, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + }, + "candidate": { + "unique_queries": 72, + "query_observations": 144, + "positive_queries": 48, + "negative_queries": 24, + "stale_queries": 8, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": 1, + "positive_mrr": 1.0, + "negative_injection_rate": 0, + "stale_injection_rate": 0, + "first_query_mean_ms": 1853.8366, + "subsequent_query_p50_ms": 362.636, + "subsequent_query_p95_ms": 407.32370000000003, + "subsequent_observations": 136, + "mean_model_text_bytes": 416.93055555555554, + "mean_mcp_result_bytes": 492.93055555555554, + "memory_observations": 144, + "mean_mcp_working_set_bytes": 640676266.6666666, + "max_mcp_peak_working_set_bytes": 685191168, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + } + }, + "by_dimension": { + "retrieval": { + "n_scenarios": 4, + "baseline": 0.7361111111111112, + "candidate": 1.0, + "mean_delta": 0.2638888888888889, + "ci95": [ + 0.2361111111111111, + 0.2777777777777778 + ], + "wins": 4, + "ties": 0, + "losses": 0 + } + }, + "scenarios": [ + { + "identity": "retrieval/copper-unseen-project", + "dimension": "retrieval", + "baseline": 0.7222222222222222, + "candidate": 1.0, + "delta": 0.2777777777777778 + }, + { + "identity": "retrieval/kestrel-unseen-project", + "dimension": "retrieval", + "baseline": 0.7222222222222222, + "candidate": 1.0, + "delta": 0.2777777777777778 + }, + { + "identity": "retrieval/marble-unseen-project", + "dimension": "retrieval", + "baseline": 0.7777777777777778, + "candidate": 1.0, + "delta": 0.2222222222222222 + }, + { + "identity": "retrieval/willow-unseen-project", + "dimension": "retrieval", + "baseline": 0.7222222222222222, + "candidate": 1.0, + "delta": 0.2777777777777778 + } + ], + "unpaired_scenarios": [], + "unpaired_details": [], + "baseline_errors": 0, + "candidate_errors": 0, + "repeats": 2, + "uncertainty_note": "Exploratory paired bootstrap over scenario IDs after averaging repeats; correlated task families require a separate grouped holdout." + } +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/comparison.md b/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/comparison.md new file mode 100644 index 0000000..e669aca --- /dev/null +++ b/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/comparison.md @@ -0,0 +1,26 @@ +# Paired BrainBench comparison + +Same harness and fixture; run order alternates. Positive delta favors the candidate. + +| Dimension | Scenarios | Baseline | Candidate | Delta | Exploratory 95% interval | +|---|---:|---:|---:|---:|---| +| retrieval | 4 | 0.736 | 1.000 | +0.264 | [+0.236, +0.278] | + +Errors: baseline 0, candidate 0. +Unpaired/skipped scenarios: 0. + +Exploratory paired bootstrap over scenario IDs after averaging repeats; correlated task families require a separate grouped holdout. + +Wall times include process/model startup, corpus seeding and queries; they are not warm inference latency. + +baseline: mean complete-run time 41.14 s (2 repeats). +candidate: mean complete-run time 38.56 s (2 repeats). + +Query measurements through persistent MCP (subsequent queries reuse the process): + +| Build | Positive hit@4 | Positive recall@4 | False injection | Subsequent p50 / p95 ms | Mean MCP result bytes | Peak MCP working set MiB | +|---|---:|---:|---:|---:|---:|---:| +| baseline | 1.000 | 1.000 | 0.792 | 366.114 / 516.582 | 567.458 | 653.559 | +| candidate | 1.000 | 1.000 | 0.000 | 362.636 / 407.324 | 492.931 | 653.449 | + +Measured bytes include JSON escaping; reported token estimates are retained per query but may use different accounting rules across builds. Query timing excludes the separately recorded MCP initialization and corpus seeding. diff --git a/docs/audits/2026-09-07-answerability/results/validation/1-baseline.json b/docs/audits/2026-09-07-answerability/results/validation/1-baseline.json new file mode 100644 index 0000000..0f7a036 --- /dev/null +++ b/docs/audits/2026-09-07-answerability/results/validation/1-baseline.json @@ -0,0 +1,1241 @@ +{ + "generated_at": "2026-09-07T05:55:24.9738733Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-answerability\\validation-frozen.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "What is the Orchid gateway timeout?", + "ranked": [ + "timeout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YDS6AM46ADZA20HCA3N7", + "id": "01M1X6YFKQJ4X437CDB542ZS2S", + "kind": "memory", + "score": 0.999908208847046, + "summary": "project:fact - Orchid gateway timeout is 45 seconds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1784.9897, + "first_query": true, + "server_startup_ms": 73.9532, + "model_text_bytes": 426, + "mcp_result_bytes": 507, + "wire_bytes": 542, + "reported_used_tokens": 507, + "working_set_bytes": 634707968, + "peak_working_set_bytes": 684695552 + }, + { + "query": "How many retries does the Orchid client use?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YDSPXE2KBVGTTFZPDRYZ", + "id": "01M1X6YFZD5FJ4WJAT50D6E5RR", + "kind": "memory", + "score": 0.9976400136947632, + "summary": "project:fact - Orchid client retry count is 5." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 365.2181, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 420, + "mcp_result_bytes": 501, + "wire_bytes": 536, + "reported_used_tokens": 501, + "working_set_bytes": 636993536, + "peak_working_set_bytes": 684695552 + }, + { + "query": "What is the Orchid worker memory limit?", + "ranked": [ + "memory" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YDSYVMWAYEKR8WC3A4CV", + "id": "01M1X6YGAT8X08SYBMZV5Z1S7K", + "kind": "memory", + "score": 0.9999785423278807, + "summary": "project:fact - Orchid worker memory limit is 768 MiB." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 359.4508, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 543, + "reported_used_tokens": 508, + "working_set_bytes": 642109440, + "peak_working_set_bytes": 684695552 + }, + { + "query": "What version does the Orchid worker run?", + "ranked": [ + "version" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YDT8N1XW0HMY7EPRHDAM", + "id": "01M1X6YGNNEPAMWHGE6Y0W7MX0", + "kind": "memory", + "score": 0.9998592138290404, + "summary": "project:fact - Orchid worker version 8.2 is installed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 348.2773, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 543, + "reported_used_tokens": 508, + "working_set_bytes": 642129920, + "peak_working_set_bytes": 684695552 + }, + { + "query": "What is `storage.page_bytes` in Orchid?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YDTKXBSR6WHV1VEVKQ92", + "id": "01M1X6YH0JB7ZV8J0AKZ5BX7TE", + "kind": "memory", + "score": 0.9999752044677734, + "summary": "project:fact - Orchid storage.page_bytes = 8192." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 358.5264, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 422, + "mcp_result_bytes": 503, + "wire_bytes": 538, + "reported_used_tokens": 503, + "working_set_bytes": 642551808, + "peak_working_set_bytes": 684695552 + }, + { + "query": "What password does the Orchid gateway require?", + "ranked": [ + "password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YDTWGPBD1DYZ2CN4XGWB", + "id": "01M1X6YHBY3NFCERWP7P1D81EQ", + "kind": "memory", + "score": 0.999871015548706, + "summary": "project:fact - No password is required for the Orchid gateway." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 358.1663, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 435, + "mcp_result_bytes": 516, + "wire_bytes": 551, + "reported_used_tokens": 516, + "working_set_bytes": 642576384, + "peak_working_set_bytes": 684695552 + }, + { + "query": "How long are Orchid backups retained?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YDV5N0NSTYAN792AB1CP", + "id": "01M1X6YHQ4488DJS38CTP3PPDH", + "kind": "memory", + "score": 0.9999786615371704, + "summary": "project:fact - Orchid backups are retained for 36 hours." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 353.8764, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 429, + "mcp_result_bytes": 510, + "wire_bytes": 545, + "reported_used_tokens": 510, + "working_set_bytes": 642613248, + "peak_working_set_bytes": 684695552 + }, + { + "query": "Which files configure the port for Orchid?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YDVEMNRTMRDVCEYDRC1B", + "id": "01M1X6YJ21Q2Q5M11QJBAZJF8Y", + "kind": "memory", + "score": 0.9955846667289734, + "summary": "project:fact - Orchid listener binds TCP port 7321. Configure its port in listener.toml." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 355.8541, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 462, + "mcp_result_bytes": 543, + "wire_bytes": 578, + "reported_used_tokens": 543, + "working_set_bytes": 642633728, + "peak_working_set_bytes": 684695552 + }, + { + "query": "What causes a version conflict in Orchid?", + "ranked": [ + "advice" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YDVT5V9PY7APWPRTZM1Z", + "id": "01M1X6YJD20YXX0Y87VBNXVXMK", + "kind": "memory", + "score": 0.9998210072517396, + "summary": "project:fact - Orchid version conflicts occur when lockfiles disagree. Regenerate the lockfile and check dependency constraints." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 369.6207, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 501, + "mcp_result_bytes": 582, + "wire_bytes": 618, + "reported_used_tokens": 582, + "working_set_bytes": 642654208, + "peak_working_set_bytes": 684695552 + }, + { + "query": "\u00bfQu\u00e9 versi\u00f3n usa el worker de Orchid?", + "ranked": [ + "version" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YDT8N1XW0HMY7EPRHDAM", + "id": "01M1X6YJRN1Y2PW82GX89YMMMT", + "kind": "memory", + "score": 0.999970316886902, + "summary": "project:fact - Orchid worker version 8.2 is installed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 350.5338, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 544, + "reported_used_tokens": 508, + "working_set_bytes": 642723840, + "peak_working_set_bytes": 684695552 + }, + { + "query": "\u00bfCu\u00e1nto tiempo se conservan las copias de seguridad de Orchid?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YDV5N0NSTYAN792AB1CP", + "id": "01M1X6YK43NWETW90N0DG5YJ3J", + "kind": "memory", + "score": 0.9995601773262024, + "summary": "project:fact - Orchid backups are retained for 36 hours." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 377.40439999999995, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 429, + "mcp_result_bytes": 510, + "wire_bytes": 546, + "reported_used_tokens": 510, + "working_set_bytes": 643182592, + "peak_working_set_bytes": 684695552 + }, + { + "query": "What is the timeout in seconds for the gateway in Orchid?", + "ranked": [ + "timeout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YDS6AM46ADZA20HCA3N7", + "id": "01M1X6YKG2EBKMJ6G9DHEAE7D0", + "kind": "memory", + "score": 0.9999822378158568, + "summary": "project:fact - Orchid gateway timeout is 45 seconds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 374.3332, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 426, + "mcp_result_bytes": 507, + "wire_bytes": 543, + "reported_used_tokens": 507, + "working_set_bytes": 643219456, + "peak_working_set_bytes": 684695552 + }, + { + "query": "What is the database timeout for Orchid?", + "ranked": [ + "timeout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YDS6AM46ADZA20HCA3N7", + "id": "01M1X6YKV41NABVJSVM3B2D85M", + "kind": "memory", + "score": 0.969832181930542, + "summary": "project:fact - Orchid gateway timeout is 45 seconds." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 348.1424, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 424, + "mcp_result_bytes": 505, + "wire_bytes": 541, + "reported_used_tokens": 505, + "working_set_bytes": 643235840, + "peak_working_set_bytes": 684695552 + }, + { + "query": "What encryption key does Orchid use?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 349.8092, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643244032, + "peak_working_set_bytes": 684695552 + }, + { + "query": "What is `storage.cache_bytes` in Orchid?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YDTKXBSR6WHV1VEVKQ92", + "id": "01M1X6YMH4ER27R12PV5VF5PA6", + "kind": "memory", + "score": 0.9991620779037476, + "summary": "project:fact - Orchid storage.page_bytes = 8192." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 364.9801, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 421, + "mcp_result_bytes": 502, + "wire_bytes": 538, + "reported_used_tokens": 502, + "working_set_bytes": 643248128, + "peak_working_set_bytes": 684695552 + }, + { + "query": "How many production replicas does Orchid run?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 374.0399, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643260416, + "peak_working_set_bytes": 684695552 + }, + { + "query": "What is the OpenSSL version for Orchid?", + "ranked": [ + "version" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YDT8N1XW0HMY7EPRHDAM", + "id": "01M1X6YN874S0AGNTPM3ZJR071", + "kind": "memory", + "score": 0.7756274342536926, + "summary": "project:fact - Orchid worker version 8.2 is installed." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 369.2296, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 544, + "reported_used_tokens": 508, + "working_set_bytes": 643264512, + "peak_working_set_bytes": 684695552 + }, + { + "query": "What is the database password for Orchid?", + "ranked": [ + "password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YDTWGPBD1DYZ2CN4XGWB", + "id": "01M1X6YNKM0JC8QNZKE6HSAB16", + "kind": "memory", + "score": 0.6111225485801697, + "summary": "project:fact - No password is required for the Orchid gateway." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 351.32039999999995, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 435, + "mcp_result_bytes": 516, + "wire_bytes": 552, + "reported_used_tokens": 516, + "working_set_bytes": 643330048, + "peak_working_set_bytes": 684695552 + }, + { + "query": "How long are logs retained for Orchid?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YDV5N0NSTYAN792AB1CP", + "id": "01M1X6YNYQCK6JR2HZHABC6SSH", + "kind": "memory", + "score": 0.9884384274482728, + "summary": "project:fact - Orchid backups are retained for 36 hours." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 363.88890000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 429, + "mcp_result_bytes": 510, + "wire_bytes": 546, + "reported_used_tokens": 510, + "working_set_bytes": 643416064, + "peak_working_set_bytes": 684695552 + }, + { + "query": "\u00bfQu\u00e9 contrase\u00f1a usa la base de datos de Orchid?", + "ranked": [ + "password", + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YDTWGPBD1DYZ2CN4XGWB", + "id": "01M1X6YPA6438KH3FXYK0GQTTY", + "kind": "memory", + "score": 0.8813819885253906, + "summary": "project:fact - No password is required for the Orchid gateway." + }, + { + "expansion_handle": "memory:01M1X6YDVEMNRTMRDVCEYDRC1B", + "id": "01M1X6YPA64W26MGG7PDFKGDE3", + "kind": "memory", + "score": 0.8048929572105408, + "summary": "project:fact - Orchid listener binds TCP port 7321. Configure its port in listener.toml." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 379.0547, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 670, + "mcp_result_bytes": 769, + "wire_bytes": 805, + "reported_used_tokens": 769, + "working_set_bytes": 643489792, + "peak_working_set_bytes": 684695552 + }, + { + "query": "\u00bfCu\u00e1ntas replicas de producci\u00f3n tiene Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 396.481, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643555328, + "peak_working_set_bytes": 684695552 + }, + { + "query": "Which region hosts Orchid production?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 682.9003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643670016, + "peak_working_set_bytes": 684695552 + } + ], + "id": "orchid-answerability-frozen", + "dimension": "retrieval", + "tier": "hard", + "score": 0.7272727272727273, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.600 (n=10) positive-n=12 negative-n=10 (22 queries)" + }, + { + "observations": [ + { + "query": "What is the Quartz gateway timeout?", + "ranked": [ + "timeout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YRW0EC8XYXJDJBVJCNX6", + "id": "01M1X6YTP6GFT865MD8XX1YBN7", + "kind": "memory", + "score": 0.9999661445617676, + "summary": "project:fact - Quartz gateway timeout is 45 seconds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1776.3401999999999, + "first_query": true, + "server_startup_ms": 72.87599999999999, + "model_text_bytes": 426, + "mcp_result_bytes": 507, + "wire_bytes": 542, + "reported_used_tokens": 507, + "working_set_bytes": 630677504, + "peak_working_set_bytes": 684937216 + }, + { + "query": "How many retries does the Quartz client use?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YRWEP5V7T2DRR5W191RY", + "id": "01M1X6YV16TQTXB1KFMFAPHGBA", + "kind": "memory", + "score": 0.9913354516029358, + "summary": "project:fact - Quartz client retry count is 5." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 350.932, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 420, + "mcp_result_bytes": 501, + "wire_bytes": 536, + "reported_used_tokens": 501, + "working_set_bytes": 630988800, + "peak_working_set_bytes": 684937216 + }, + { + "query": "What is the Quartz worker memory limit?", + "ranked": [ + "memory" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YRWQYM61QB54QCXY5M8S", + "id": "01M1X6YVCDBJHFR3V4CF38Q9DP", + "kind": "memory", + "score": 0.9999793767929076, + "summary": "project:fact - Quartz worker memory limit is 768 MiB." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 352.15049999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 543, + "reported_used_tokens": 508, + "working_set_bytes": 635928576, + "peak_working_set_bytes": 684937216 + }, + { + "query": "What version does the Quartz worker run?", + "ranked": [ + "version" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YRX0N2VZWG624EM98933", + "id": "01M1X6YVQBH48NYRBWS2GCWHCE", + "kind": "memory", + "score": 0.9998953342437744, + "summary": "project:fact - Quartz worker version 8.2 is installed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 355.2699, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 543, + "reported_used_tokens": 508, + "working_set_bytes": 636006400, + "peak_working_set_bytes": 684937216 + }, + { + "query": "What is `storage.page_bytes` in Quartz?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YRX9144J4G8J165CF3WK", + "id": "01M1X6YW30JX5Q1TAZZHQA4RQA", + "kind": "memory", + "score": 0.9999722242355348, + "summary": "project:fact - Quartz storage.page_bytes = 8192." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 374.8146, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 422, + "mcp_result_bytes": 503, + "wire_bytes": 538, + "reported_used_tokens": 503, + "working_set_bytes": 638234624, + "peak_working_set_bytes": 684937216 + }, + { + "query": "What password does the Quartz gateway require?", + "ranked": [ + "password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YRXJ8E92QTCNBSVRM7RQ", + "id": "01M1X6YWDZNFG1H7C4T71J6HD1", + "kind": "memory", + "score": 0.9998206496238708, + "summary": "project:fact - No password is required for the Quartz gateway." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 351.1381, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 436, + "mcp_result_bytes": 517, + "wire_bytes": 552, + "reported_used_tokens": 517, + "working_set_bytes": 638275584, + "peak_working_set_bytes": 684937216 + }, + { + "query": "How long are Quartz backups retained?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YRXWS06KM9HEA391XTW1", + "id": "01M1X6YWS1YRP5SRBBQVBXHFMM", + "kind": "memory", + "score": 0.999981164932251, + "summary": "project:fact - Quartz backups are retained for 36 hours." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 346.8805, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 428, + "mcp_result_bytes": 509, + "wire_bytes": 544, + "reported_used_tokens": 509, + "working_set_bytes": 638300160, + "peak_working_set_bytes": 684937216 + }, + { + "query": "Which files configure the port for Quartz?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YRY6PW9FEZA7EK8NW7B2", + "id": "01M1X6YX3XJ56PPBFMPSXGC6H7", + "kind": "memory", + "score": 0.9969274401664734, + "summary": "project:fact - Quartz listener binds TCP port 8452. Configure its port in listener.toml." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 349.4505, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 462, + "mcp_result_bytes": 543, + "wire_bytes": 578, + "reported_used_tokens": 543, + "working_set_bytes": 638337024, + "peak_working_set_bytes": 684937216 + }, + { + "query": "What causes a version conflict in Quartz?", + "ranked": [ + "advice" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YRYJJ4SGYR06FREHSYQP", + "id": "01M1X6YXESJ7CG1XRMA1Z2AM7Q", + "kind": "memory", + "score": 0.9998512268066406, + "summary": "project:fact - Quartz version conflicts occur when lockfiles disagree. Regenerate the lockfile and check dependency constraints." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 357.88160000000005, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 501, + "mcp_result_bytes": 582, + "wire_bytes": 618, + "reported_used_tokens": 582, + "working_set_bytes": 638468096, + "peak_working_set_bytes": 684937216 + }, + { + "query": "\u00bfQu\u00e9 versi\u00f3n usa el worker de Quartz?", + "ranked": [ + "version" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YRX0N2VZWG624EM98933", + "id": "01M1X6YXSX9NJ1A4F1Y2Q30CVQ", + "kind": "memory", + "score": 0.9999598264694214, + "summary": "project:fact - Quartz worker version 8.2 is installed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 349.2778, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 544, + "reported_used_tokens": 508, + "working_set_bytes": 638562304, + "peak_working_set_bytes": 684937216 + }, + { + "query": "\u00bfCu\u00e1nto tiempo se conservan las copias de seguridad de Quartz?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YRXWS06KM9HEA391XTW1", + "id": "01M1X6YY4XTGDFXBEJJ29M6QDW", + "kind": "memory", + "score": 0.9996949434280396, + "summary": "project:fact - Quartz backups are retained for 36 hours." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 353.0143, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 429, + "mcp_result_bytes": 510, + "wire_bytes": 546, + "reported_used_tokens": 510, + "working_set_bytes": 638988288, + "peak_working_set_bytes": 684937216 + }, + { + "query": "What is the timeout in seconds for the gateway in Quartz?", + "ranked": [ + "timeout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YRW0EC8XYXJDJBVJCNX6", + "id": "01M1X6YYG92D4YHB9NM73ADAFD", + "kind": "memory", + "score": 0.9999818801879884, + "summary": "project:fact - Quartz gateway timeout is 45 seconds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 363.98190000000005, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 426, + "mcp_result_bytes": 507, + "wire_bytes": 543, + "reported_used_tokens": 507, + "working_set_bytes": 639008768, + "peak_working_set_bytes": 684937216 + }, + { + "query": "What is the database timeout for Quartz?", + "ranked": [ + "timeout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YRW0EC8XYXJDJBVJCNX6", + "id": "01M1X6YYVF4SHBSEKPK7NNBCEH", + "kind": "memory", + "score": 0.9865899085998536, + "summary": "project:fact - Quartz gateway timeout is 45 seconds." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 365.71340000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 425, + "mcp_result_bytes": 506, + "wire_bytes": 542, + "reported_used_tokens": 506, + "working_set_bytes": 639062016, + "peak_working_set_bytes": 684937216 + }, + { + "query": "What encryption key does Quartz use?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 373.8856, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 639082496, + "peak_working_set_bytes": 684937216 + }, + { + "query": "What is `storage.cache_bytes` in Quartz?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YRX9144J4G8J165CF3WK", + "id": "01M1X6YZJFNMY4TQRY724AHQR8", + "kind": "memory", + "score": 0.998980700969696, + "summary": "project:fact - Quartz storage.page_bytes = 8192." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 353.7404, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 420, + "mcp_result_bytes": 501, + "wire_bytes": 537, + "reported_used_tokens": 501, + "working_set_bytes": 639082496, + "peak_working_set_bytes": 684937216 + }, + { + "query": "How many production replicas does Quartz run?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 349.1059, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 639143936, + "peak_working_set_bytes": 684937216 + }, + { + "query": "What is the OpenSSL version for Quartz?", + "ranked": [ + "version" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YRX0N2VZWG624EM98933", + "id": "01M1X6Z08FFM5T6EDF1RT3SE3X", + "kind": "memory", + "score": 0.7910260558128357, + "summary": "project:fact - Quartz worker version 8.2 is installed." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 356.2742, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 544, + "reported_used_tokens": 508, + "working_set_bytes": 639148032, + "peak_working_set_bytes": 684937216 + }, + { + "query": "What is the database password for Quartz?", + "ranked": [ + "password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YRXJ8E92QTCNBSVRM7RQ", + "id": "01M1X6Z0KDY5CADXF1A7GKK0Z1", + "kind": "memory", + "score": 0.5618340969085693, + "summary": "project:fact - No password is required for the Quartz gateway." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 346.1132, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 435, + "mcp_result_bytes": 516, + "wire_bytes": 552, + "reported_used_tokens": 516, + "working_set_bytes": 639148032, + "peak_working_set_bytes": 684937216 + }, + { + "query": "How long are logs retained for Quartz?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YRXWS06KM9HEA391XTW1", + "id": "01M1X6Z0YFD0R9SQGMGTM4YYVM", + "kind": "memory", + "score": 0.9967792630195618, + "summary": "project:fact - Quartz backups are retained for 36 hours." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 352.7833, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 429, + "mcp_result_bytes": 510, + "wire_bytes": 546, + "reported_used_tokens": 510, + "working_set_bytes": 639160320, + "peak_working_set_bytes": 684937216 + }, + { + "query": "\u00bfQu\u00e9 contrase\u00f1a usa la base de datos de Quartz?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YRY6PW9FEZA7EK8NW7B2", + "id": "01M1X6Z19EXZRA3MPY84DWRXAK", + "kind": "memory", + "score": 0.8378869891166687, + "summary": "project:fact - Quartz listener binds TCP port 8452. Configure its port in listener.toml." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 353.2962, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 461, + "mcp_result_bytes": 542, + "wire_bytes": 578, + "reported_used_tokens": 542, + "working_set_bytes": 639213568, + "peak_working_set_bytes": 684937216 + }, + { + "query": "\u00bfCu\u00e1ntas replicas de producci\u00f3n tiene Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 363.99350000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 639266816, + "peak_working_set_bytes": 684937216 + }, + { + "query": "Which region hosts Quartz production?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 348.8584, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 639365120, + "peak_working_set_bytes": 684937216 + } + ], + "id": "quartz-answerability-frozen", + "dimension": "retrieval", + "tier": "hard", + "score": 0.7272727272727273, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.600 (n=10) positive-n=12 negative-n=10 (22 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 1.4545454545454546, + 2 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 0.7272727272727273, + "n": 2, + "ci95": 0.0 + } + }, + "overall_index": 0.7272727272727273, + "scenario_weighted_index": 0.7272727272727273 +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-answerability/results/validation/1-candidate.json b/docs/audits/2026-09-07-answerability/results/validation/1-candidate.json new file mode 100644 index 0000000..79b48b7 --- /dev/null +++ b/docs/audits/2026-09-07-answerability/results/validation/1-candidate.json @@ -0,0 +1,1101 @@ +{ + "generated_at": "2026-09-07T05:55:47.2818094Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-answerability\\validation-frozen.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "What is the Orchid gateway timeout?", + "ranked": [ + "timeout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Z3F20JFPE952W3SBHNMM", + "id": "01M1X6Z5AKAJM6PC09XA6N6J8G", + "kind": "memory", + "score": 0.999908208847046, + "summary": "project:fact - Orchid gateway timeout is 45 seconds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1791.2393, + "first_query": true, + "server_startup_ms": 72.32669999999999, + "model_text_bytes": 426, + "mcp_result_bytes": 507, + "wire_bytes": 542, + "reported_used_tokens": 507, + "working_set_bytes": 632143872, + "peak_working_set_bytes": 685117440 + }, + { + "query": "How many retries does the Orchid client use?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Z3FHE516M190ES87Y9TB", + "id": "01M1X6Z5P5YB53B577DWC7B7HK", + "kind": "memory", + "score": 0.9976400136947632, + "summary": "project:fact - Orchid client retry count is 5." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 352.8681, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 420, + "mcp_result_bytes": 501, + "wire_bytes": 536, + "reported_used_tokens": 501, + "working_set_bytes": 634351616, + "peak_working_set_bytes": 685117440 + }, + { + "query": "What is the Orchid worker memory limit?", + "ranked": [ + "memory" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Z3FSY9PHQ7X50YEXP864", + "id": "01M1X6Z61GGR4ZMQJR3Y8HE5ND", + "kind": "memory", + "score": 0.9999785423278807, + "summary": "project:fact - Orchid worker memory limit is 768 MiB." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 362.1267, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 543, + "reported_used_tokens": 508, + "working_set_bytes": 639639552, + "peak_working_set_bytes": 685117440 + }, + { + "query": "What version does the Orchid worker run?", + "ranked": [ + "version" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Z3G208YWQXYFF8T42A96", + "id": "01M1X6Z6CFQ9EN9A97J0MNWERX", + "kind": "memory", + "score": 0.9998592138290404, + "summary": "project:fact - Orchid worker version 8.2 is installed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 360.7787, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 543, + "reported_used_tokens": 508, + "working_set_bytes": 639807488, + "peak_working_set_bytes": 685117440 + }, + { + "query": "What is `storage.page_bytes` in Orchid?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Z3GCCQ19YWCBQEN82V0W", + "id": "01M1X6Z6QZ9KD66JK8485335SM", + "kind": "memory", + "score": 0.9999752044677734, + "summary": "project:fact - Orchid storage.page_bytes = 8192." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 363.8144, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 422, + "mcp_result_bytes": 503, + "wire_bytes": 538, + "reported_used_tokens": 503, + "working_set_bytes": 640425984, + "peak_working_set_bytes": 685117440 + }, + { + "query": "What password does the Orchid gateway require?", + "ranked": [ + "password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Z3GNFA7389JQSPX5DJHS", + "id": "01M1X6Z738D3PK3Y419X4B2D1Q", + "kind": "memory", + "score": 0.999871015548706, + "summary": "project:fact - No password is required for the Orchid gateway." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 397.2996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 435, + "mcp_result_bytes": 516, + "wire_bytes": 551, + "reported_used_tokens": 516, + "working_set_bytes": 640483328, + "peak_working_set_bytes": 685117440 + }, + { + "query": "How long are Orchid backups retained?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Z3GYMK45Y974HY2F9E6M", + "id": "01M1X6Z7G3N0BMHM48XD2NC87B", + "kind": "memory", + "score": 0.9999786615371704, + "summary": "project:fact - Orchid backups are retained for 36 hours." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 380.8821, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 429, + "mcp_result_bytes": 510, + "wire_bytes": 545, + "reported_used_tokens": 510, + "working_set_bytes": 640827392, + "peak_working_set_bytes": 685117440 + }, + { + "query": "Which files configure the port for Orchid?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Z3H6NYDEZMFYZV1QS0EW", + "id": "01M1X6Z7W3NEZ3BEHTZAPMVHEG", + "kind": "memory", + "score": 0.9955846667289734, + "summary": "project:fact - Orchid listener binds TCP port 7321. Configure its port in listener.toml." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 379.6163, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 462, + "mcp_result_bytes": 543, + "wire_bytes": 578, + "reported_used_tokens": 543, + "working_set_bytes": 641015808, + "peak_working_set_bytes": 685117440 + }, + { + "query": "What causes a version conflict in Orchid?", + "ranked": [ + "advice" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Z3HJKHAVKPZRJ96TYHHW", + "id": "01M1X6Z87NQ6KVM955VNAD7N5E", + "kind": "memory", + "score": 0.9998210072517396, + "summary": "project:fact - Orchid version conflicts occur when lockfiles disagree. Regenerate the lockfile and check dependency constraints." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 361.979, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 501, + "mcp_result_bytes": 582, + "wire_bytes": 618, + "reported_used_tokens": 582, + "working_set_bytes": 641089536, + "peak_working_set_bytes": 685117440 + }, + { + "query": "\u00bfQu\u00e9 versi\u00f3n usa el worker de Orchid?", + "ranked": [ + "version" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Z3G208YWQXYFF8T42A96", + "id": "01M1X6Z8JH22NMQVT6AE2A8MC0", + "kind": "memory", + "score": 0.999970316886902, + "summary": "project:fact - Orchid worker version 8.2 is installed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 351.7429, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 544, + "reported_used_tokens": 508, + "working_set_bytes": 641204224, + "peak_working_set_bytes": 685117440 + }, + { + "query": "\u00bfCu\u00e1nto tiempo se conservan las copias de seguridad de Orchid?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Z3GYMK45Y974HY2F9E6M", + "id": "01M1X6Z8XVCWZ3VGRXJCKDF4E7", + "kind": "memory", + "score": 0.9995601773262024, + "summary": "project:fact - Orchid backups are retained for 36 hours." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 366.82, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 429, + "mcp_result_bytes": 510, + "wire_bytes": 546, + "reported_used_tokens": 510, + "working_set_bytes": 641654784, + "peak_working_set_bytes": 685117440 + }, + { + "query": "What is the timeout in seconds for the gateway in Orchid?", + "ranked": [ + "timeout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Z3F20JFPE952W3SBHNMM", + "id": "01M1X6Z995F1KWS65M169P9FA5", + "kind": "memory", + "score": 0.9999822378158568, + "summary": "project:fact - Orchid gateway timeout is 45 seconds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 360.3417, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 426, + "mcp_result_bytes": 507, + "wire_bytes": 543, + "reported_used_tokens": 507, + "working_set_bytes": 641716224, + "peak_working_set_bytes": 685117440 + }, + { + "query": "What is the database timeout for Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 355.3289, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 641748992, + "peak_working_set_bytes": 685117440 + }, + { + "query": "What encryption key does Orchid use?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 350.5031, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 641785856, + "peak_working_set_bytes": 685117440 + }, + { + "query": "What is `storage.cache_bytes` in Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 370.5801, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 641847296, + "peak_working_set_bytes": 685117440 + }, + { + "query": "How many production replicas does Orchid run?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 377.8243, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 641867776, + "peak_working_set_bytes": 685117440 + }, + { + "query": "What is the OpenSSL version for Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 350.1353, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 641867776, + "peak_working_set_bytes": 685117440 + }, + { + "query": "What is the database password for Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 350.3474, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 641867776, + "peak_working_set_bytes": 685117440 + }, + { + "query": "How long are logs retained for Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 349.6879, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 641900544, + "peak_working_set_bytes": 685117440 + }, + { + "query": "\u00bfQu\u00e9 contrase\u00f1a usa la base de datos de Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 359.72900000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 642015232, + "peak_working_set_bytes": 685117440 + }, + { + "query": "\u00bfCu\u00e1ntas replicas de producci\u00f3n tiene Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 360.4404, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 642039808, + "peak_working_set_bytes": 685117440 + }, + { + "query": "Which region hosts Orchid production?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 348.4604, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 642105344, + "peak_working_set_bytes": 685117440 + } + ], + "id": "orchid-answerability-frozen", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.000 (n=10) positive-n=12 negative-n=10 (22 queries)" + }, + { + "observations": [ + { + "query": "What is the Quartz gateway timeout?", + "ranked": [ + "timeout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6ZE6VFD30HKMB332K594Q", + "id": "01M1X6ZG1H73DTGWB37HQKM9K8", + "kind": "memory", + "score": 0.9999661445617676, + "summary": "project:fact - Quartz gateway timeout is 45 seconds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1781.2711000000002, + "first_query": true, + "server_startup_ms": 76.8679, + "model_text_bytes": 426, + "mcp_result_bytes": 507, + "wire_bytes": 542, + "reported_used_tokens": 507, + "working_set_bytes": 636243968, + "peak_working_set_bytes": 684883968 + }, + { + "query": "How many retries does the Quartz client use?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6ZE7AN6KPGQGVSNQJF234", + "id": "01M1X6ZGCTYXKJFGQBMSBED164", + "kind": "memory", + "score": 0.9913354516029358, + "summary": "project:fact - Quartz client retry count is 5." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 345.8044, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 420, + "mcp_result_bytes": 501, + "wire_bytes": 536, + "reported_used_tokens": 501, + "working_set_bytes": 636764160, + "peak_working_set_bytes": 684883968 + }, + { + "query": "What is the Quartz worker memory limit?", + "ranked": [ + "memory" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6ZE7JWXQPWNRX3X85AAWB", + "id": "01M1X6ZGQKB4KBYY3EDJ3FQ53N", + "kind": "memory", + "score": 0.9999793767929076, + "summary": "project:fact - Quartz worker memory limit is 768 MiB." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 350.2877, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 543, + "reported_used_tokens": 508, + "working_set_bytes": 641699840, + "peak_working_set_bytes": 684883968 + }, + { + "query": "What version does the Quartz worker run?", + "ranked": [ + "version" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6ZE7V6VNTTEC8TA972H3N", + "id": "01M1X6ZH2NHFM0ESWFE1WAZ5Y5", + "kind": "memory", + "score": 0.9998953342437744, + "summary": "project:fact - Quartz worker version 8.2 is installed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.8625, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 543, + "reported_used_tokens": 508, + "working_set_bytes": 641925120, + "peak_working_set_bytes": 684883968 + }, + { + "query": "What is `storage.page_bytes` in Quartz?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6ZE85JSAKW61ZZMAY2M8Q", + "id": "01M1X6ZHDTGECWBEANASXWH4R3", + "kind": "memory", + "score": 0.9999722242355348, + "summary": "project:fact - Quartz storage.page_bytes = 8192." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 353.9498, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 422, + "mcp_result_bytes": 503, + "wire_bytes": 538, + "reported_used_tokens": 503, + "working_set_bytes": 644141056, + "peak_working_set_bytes": 684883968 + }, + { + "query": "What password does the Quartz gateway require?", + "ranked": [ + "password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6ZE8EBNQW8YRKA45WT0NC", + "id": "01M1X6ZHRSHMCM1YKSFYEGQC3J", + "kind": "memory", + "score": 0.9998206496238708, + "summary": "project:fact - No password is required for the Quartz gateway." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 349.2876, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 436, + "mcp_result_bytes": 517, + "wire_bytes": 552, + "reported_used_tokens": 517, + "working_set_bytes": 644337664, + "peak_working_set_bytes": 684883968 + }, + { + "query": "How long are Quartz backups retained?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6ZE8RV9GGEDFWRMZGAF79", + "id": "01M1X6ZJ3XC2S2NK34S193SRGJ", + "kind": "memory", + "score": 0.999981164932251, + "summary": "project:fact - Quartz backups are retained for 36 hours." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 358.7388, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 428, + "mcp_result_bytes": 509, + "wire_bytes": 544, + "reported_used_tokens": 509, + "working_set_bytes": 644538368, + "peak_working_set_bytes": 684883968 + }, + { + "query": "Which files configure the port for Quartz?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6ZE92HRG2G06CQRM5852W", + "id": "01M1X6ZJF58ZSMC79HZSM1TTPJ", + "kind": "memory", + "score": 0.9969274401664734, + "summary": "project:fact - Quartz listener binds TCP port 8452. Configure its port in listener.toml." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 363.7395, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 462, + "mcp_result_bytes": 543, + "wire_bytes": 578, + "reported_used_tokens": 543, + "working_set_bytes": 644767744, + "peak_working_set_bytes": 684883968 + }, + { + "query": "What causes a version conflict in Quartz?", + "ranked": [ + "advice" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6ZE9EP1KC8GDQ35GKNH3T", + "id": "01M1X6ZJTWABMWKYKFC84RZEV9", + "kind": "memory", + "score": 0.9998512268066406, + "summary": "project:fact - Quartz version conflicts occur when lockfiles disagree. Regenerate the lockfile and check dependency constraints." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 368.7004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 501, + "mcp_result_bytes": 582, + "wire_bytes": 618, + "reported_used_tokens": 582, + "working_set_bytes": 644907008, + "peak_working_set_bytes": 684883968 + }, + { + "query": "\u00bfQu\u00e9 versi\u00f3n usa el worker de Quartz?", + "ranked": [ + "version" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6ZE7V6VNTTEC8TA972H3N", + "id": "01M1X6ZK5VPNB3ZTBPTY5JZJJV", + "kind": "memory", + "score": 0.9999598264694214, + "summary": "project:fact - Quartz worker version 8.2 is installed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 352.2954, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 544, + "reported_used_tokens": 508, + "working_set_bytes": 645013504, + "peak_working_set_bytes": 684883968 + }, + { + "query": "\u00bfCu\u00e1nto tiempo se conservan las copias de seguridad de Quartz?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6ZE8RV9GGEDFWRMZGAF79", + "id": "01M1X6ZKGZ3REYC1F4XXBT51SJ", + "kind": "memory", + "score": 0.9996949434280396, + "summary": "project:fact - Quartz backups are retained for 36 hours." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 363.1407, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 429, + "mcp_result_bytes": 510, + "wire_bytes": 546, + "reported_used_tokens": 510, + "working_set_bytes": 645574656, + "peak_working_set_bytes": 684883968 + }, + { + "query": "What is the timeout in seconds for the gateway in Quartz?", + "ranked": [ + "timeout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6ZE6VFD30HKMB332K594Q", + "id": "01M1X6ZKWB20RB0JWVWD74Z46J", + "kind": "memory", + "score": 0.9999818801879884, + "summary": "project:fact - Quartz gateway timeout is 45 seconds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 359.199, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 426, + "mcp_result_bytes": 507, + "wire_bytes": 543, + "reported_used_tokens": 507, + "working_set_bytes": 645701632, + "peak_working_set_bytes": 684883968 + }, + { + "query": "What is the database timeout for Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 363.9108, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645775360, + "peak_working_set_bytes": 684883968 + }, + { + "query": "What encryption key does Quartz use?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 351.41040000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645779456, + "peak_working_set_bytes": 684883968 + }, + { + "query": "What is `storage.cache_bytes` in Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 359.0007, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645783552, + "peak_working_set_bytes": 684883968 + }, + { + "query": "How many production replicas does Quartz run?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 357.4308, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645799936, + "peak_working_set_bytes": 684883968 + }, + { + "query": "What is the OpenSSL version for Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 754.8161, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645804032, + "peak_working_set_bytes": 684883968 + }, + { + "query": "What is the database password for Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 372.7692, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645820416, + "peak_working_set_bytes": 684883968 + }, + { + "query": "How long are logs retained for Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 347.4746, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645861376, + "peak_working_set_bytes": 684883968 + }, + { + "query": "\u00bfQu\u00e9 contrase\u00f1a usa la base de datos de Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 353.0142, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645922816, + "peak_working_set_bytes": 684883968 + }, + { + "query": "\u00bfCu\u00e1ntas replicas de producci\u00f3n tiene Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 357.3481, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 646070272, + "peak_working_set_bytes": 684883968 + }, + { + "query": "Which region hosts Quartz production?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 359.3071, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 646152192, + "peak_working_set_bytes": 684883968 + } + ], + "id": "quartz-answerability-frozen", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.000 (n=10) positive-n=12 negative-n=10 (22 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 2.0, + 2 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 1.0, + "n": 2, + "ci95": 0.0 + } + }, + "overall_index": 1.0, + "scenario_weighted_index": 1.0 +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-answerability/results/validation/comparison.json b/docs/audits/2026-09-07-answerability/results/validation/comparison.json new file mode 100644 index 0000000..7304d2f --- /dev/null +++ b/docs/audits/2026-09-07-answerability/results/validation/comparison.json @@ -0,0 +1,165 @@ +{ + "schema_version": 1, + "status": "complete", + "harness": { + "path": "E:\\Kimetsu\\bench\\target\\release\\kbench.exe", + "sha256": "33e0a3fe1c19aaed4d2fc4aad66653d5feeef8ec67c554c2ba9a77b8a6f39c38", + "bytes": 9348096 + }, + "runner": { + "path": "E:\\tmp\\kimetsu-brain-hardening\\bench\\scripts\\compare_brainbench.py", + "sha256": "738bad9404a4ec2b911fff661967ca56f48b584dfeb22f83823c972a1498df37", + "bytes": 24527 + }, + "binaries": { + "baseline": { + "path": "E:\\tmp\\kimetsu-brain-hardening\\tmp-tests\\kimetsu-answerability-candidate.exe", + "sha256": "405d3483fe320e76b0ec776bf9ada3b7771852b04a73f70f3b5da377a43d31c3", + "bytes": 47151104 + }, + "candidate": { + "path": "E:\\tmp\\kimetsu-brain-hardening\\tmp-tests\\kimetsu-answerability-candidate.exe", + "sha256": "405d3483fe320e76b0ec776bf9ada3b7771852b04a73f70f3b5da377a43d31c3", + "bytes": 47151104 + } + }, + "datasets": [ + { + "path": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-answerability\\validation-frozen.json", + "sha256": "ea4452872956beed1030ec071572db89435780474180b17a7bf7fe8f481e5d7e", + "bytes": 10754 + } + ], + "settings": { + "budget_tokens": 6000, + "dimensions": [ + "poisoning", + "render-contract", + "retrieval", + "workflow" + ], + "jobs": 1, + "warm_start": false, + "include_ambient": false, + "overrides": { + "KIMETSU_BRAIN_EMBEDDER": "bge-small-en-v1.5", + "KIMETSU_DETECT_CONFLICTS": "0", + "KIMETSU_RESOLVE_CONFLICTS": "0", + "FASTEMBED_CACHE_DIR": "E:/Kimetsu/.fastembed_cache", + "HF_HOME": "E:\\tmp\\kimetsu-brain-hardening/tmp-tests/hf-home" + }, + "baseline_threads": 0, + "candidate_threads": 0, + "baseline_reranker": "mmarco-minilm-l12-v2-int8", + "candidate_reranker": "mmarco-minilm-l12-v2-int8", + "baseline_rerank_floor": 0.55, + "candidate_rerank_floor": 0.55 + }, + "runs": [ + { + "label": "baseline", + "repeat": 1, + "intra_threads_override": null, + "rerank_floor_override": "0.55", + "explicit_fact_guard_override": "false", + "reranker_override": "mmarco-minilm-l12-v2-int8", + "wall_seconds": 22.235235999978613, + "report_file": "1-baseline.json" + }, + { + "label": "candidate", + "repeat": 1, + "intra_threads_override": null, + "rerank_floor_override": "0.55", + "explicit_fact_guard_override": "true", + "reranker_override": "mmarco-minilm-l12-v2-int8", + "wall_seconds": 22.292007600015495, + "report_file": "1-candidate.json" + } + ], + "comparison": { + "measurement_summary": { + "baseline": { + "unique_queries": 44, + "query_observations": 44, + "positive_queries": 24, + "negative_queries": 20, + "stale_queries": 0, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": 1, + "positive_mrr": 1.0, + "negative_injection_rate": 0.6, + "stale_injection_rate": null, + "first_query_mean_ms": 1780.6649499999999, + "subsequent_query_p50_ms": 355.8541, + "subsequent_query_p95_ms": 379.0547, + "subsequent_observations": 42, + "mean_model_text_bytes": 401.65909090909093, + "mean_mcp_result_bytes": 479.79545454545456, + "memory_observations": 44, + "mean_mcp_working_set_bytes": 640119528.7272727, + "max_mcp_peak_working_set_bytes": 684937216, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + }, + "candidate": { + "unique_queries": 44, + "query_observations": 44, + "positive_queries": 24, + "negative_queries": 20, + "stale_queries": 0, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": 1, + "positive_mrr": 1.0, + "negative_injection_rate": 0, + "stale_injection_rate": null, + "first_query_mean_ms": 1786.2552, + "subsequent_query_p50_ms": 359.0007, + "subsequent_query_p95_ms": 380.8821, + "subsequent_observations": 42, + "mean_model_text_bytes": 340.5, + "mean_mcp_result_bytes": 413.3181818181818, + "memory_observations": 44, + "mean_mcp_working_set_bytes": 642449780.3636364, + "max_mcp_peak_working_set_bytes": 685117440, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + } + }, + "by_dimension": { + "retrieval": { + "n_scenarios": 2, + "baseline": 0.7272727272727273, + "candidate": 1.0, + "mean_delta": 0.2727272727272727, + "ci95": [ + 0.2727272727272727, + 0.2727272727272727 + ], + "wins": 2, + "ties": 0, + "losses": 0 + } + }, + "scenarios": [ + { + "identity": "retrieval/orchid-answerability-frozen", + "dimension": "retrieval", + "baseline": 0.7272727272727273, + "candidate": 1.0, + "delta": 0.2727272727272727 + }, + { + "identity": "retrieval/quartz-answerability-frozen", + "dimension": "retrieval", + "baseline": 0.7272727272727273, + "candidate": 1.0, + "delta": 0.2727272727272727 + } + ], + "unpaired_scenarios": [], + "unpaired_details": [], + "baseline_errors": 0, + "candidate_errors": 0, + "repeats": 1, + "uncertainty_note": "Exploratory paired bootstrap over scenario IDs after averaging repeats; correlated task families require a separate grouped holdout." + } +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-answerability/results/validation/comparison.md b/docs/audits/2026-09-07-answerability/results/validation/comparison.md new file mode 100644 index 0000000..05e1c81 --- /dev/null +++ b/docs/audits/2026-09-07-answerability/results/validation/comparison.md @@ -0,0 +1,26 @@ +# Paired BrainBench comparison + +Same harness and fixture; run order alternates. Positive delta favors the candidate. + +| Dimension | Scenarios | Baseline | Candidate | Delta | Exploratory 95% interval | +|---|---:|---:|---:|---:|---| +| retrieval | 2 | 0.727 | 1.000 | +0.273 | [+0.273, +0.273] | + +Errors: baseline 0, candidate 0. +Unpaired/skipped scenarios: 0. + +Exploratory paired bootstrap over scenario IDs after averaging repeats; correlated task families require a separate grouped holdout. + +Wall times include process/model startup, corpus seeding and queries; they are not warm inference latency. + +baseline: mean complete-run time 22.24 s (1 repeats). +candidate: mean complete-run time 22.29 s (1 repeats). + +Query measurements through persistent MCP (subsequent queries reuse the process): + +| Build | Positive hit@4 | Positive recall@4 | False injection | Subsequent p50 / p95 ms | Mean MCP result bytes | Peak MCP working set MiB | +|---|---:|---:|---:|---:|---:|---:| +| baseline | 1.000 | 1.000 | 0.600 | 355.854 / 379.055 | 479.795 | 653.207 | +| candidate | 1.000 | 1.000 | 0.000 | 359.001 / 380.882 | 413.318 | 653.379 | + +Measured bytes include JSON escaping; reported token estimates are retained per query but may use different accounting rules across builds. Query timing excludes the separately recorded MCP initialization and corpus seeding. diff --git a/docs/audits/2026-09-07-answerability/run-comparisons.ps1 b/docs/audits/2026-09-07-answerability/run-comparisons.ps1 new file mode 100644 index 0000000..aa942f0 --- /dev/null +++ b/docs/audits/2026-09-07-answerability/run-comparisons.ps1 @@ -0,0 +1,40 @@ +param( + [Parameter(Mandatory=$true)][string]$Binary, + [Parameter(Mandatory=$true)][string]$Harness, + [Parameter(Mandatory=$true)][string]$OutputRoot, + [string]$ModelCache='E:/Kimetsu/.fastembed_cache', + [string]$HfHome, + [switch]$TimingFollowup +) +$ErrorActionPreference = 'Stop' +$binaryPath = (Resolve-Path -LiteralPath $Binary).Path +$harnessPath = (Resolve-Path -LiteralPath $Harness).Path +if (Test-Path -LiteralPath $OutputRoot) { throw 'Use a new output directory.' } +New-Item -ItemType Directory -Path $OutputRoot | Out-Null +$outputPath = (Resolve-Path -LiteralPath $OutputRoot).Path +$repo = (Resolve-Path "$PSScriptRoot/../../..").Path +$env:FASTEMBED_CACHE_DIR=(Resolve-Path -LiteralPath $ModelCache).Path +if (-not $HfHome) { $HfHome="$repo/tmp-tests/hf-home" } +$env:HF_HOME=(Resolve-Path -LiteralPath $HfHome).Path +$env:HF_HUB_OFFLINE='1' +$env:KIMETSU_USER_BRAIN='0' +$env:KIMETSU_BRAIN_EMBEDDER='bge-small-en-v1.5' +$env:KIMETSU_DETECT_CONFLICTS='0' +$env:KIMETSU_RESOLVE_CONFLICTS='0' +Remove-Item Env:KIMETSU_ABSTAIN_EVIDENCE -ErrorAction SilentlyContinue +$fixture="$PSScriptRoot/validation-frozen.json" +if ((Get-FileHash -LiteralPath $fixture -Algorithm SHA256).Hash.ToLowerInvariant() -ne 'ea4452872956beed1030ec071572db89435780474180b17a7bf7fe8f481e5d7e') { throw 'Frozen validation changed' } +$experiments=@( + @{name='development'; fixture="$PSScriptRoot/../2026-09-07-retrieval/development-100.json"; model='ms-marco-tinybert-l-2-v2'; floor=0.30}, + @{name='missing-fact-development'; fixture="$PSScriptRoot/../2026-09-07-retrieval/validation-frozen.json"; model='mmarco-minilm-l12-v2-int8'; floor=0.55}, + @{name='validation'; fixture=$fixture; model='mmarco-minilm-l12-v2-int8'; floor=0.55} +) +foreach ($experiment in $experiments) { + python "$repo/bench/scripts/compare_brainbench.py" --kbench $harnessPath --baseline $binaryPath --candidate $binaryPath --dataset $experiment.fixture --budget-tokens 6000 --repeats 1 --out "$outputPath/$($experiment.name)" --baseline-threads 0 --candidate-threads 0 --baseline-reranker $experiment.model --candidate-reranker $experiment.model --baseline-rerank-floor $experiment.floor --candidate-rerank-floor $experiment.floor --baseline-explicit-fact-guard false --candidate-explicit-fact-guard true + if ($LASTEXITCODE -ne 0) { throw "Comparison failed: $($experiment.name)" } +} + +if ($TimingFollowup) { + python "$repo/bench/scripts/compare_brainbench.py" --kbench $harnessPath --baseline $binaryPath --candidate $binaryPath --dataset "$PSScriptRoot/../2026-09-07-retrieval/validation-frozen.json" --budget-tokens 6000 --repeats 2 --out "$outputPath/missing-fact-timing-followup" --baseline-threads 0 --candidate-threads 0 --baseline-reranker mmarco-minilm-l12-v2-int8 --candidate-reranker mmarco-minilm-l12-v2-int8 --baseline-rerank-floor 0.55 --candidate-rerank-floor 0.55 --baseline-explicit-fact-guard false --candidate-explicit-fact-guard true + if ($LASTEXITCODE -ne 0) { throw 'Timing follow-up failed' } +} diff --git a/docs/audits/2026-09-07-answerability/summarize.py b/docs/audits/2026-09-07-answerability/summarize.py new file mode 100644 index 0000000..c66fbad --- /dev/null +++ b/docs/audits/2026-09-07-answerability/summarize.py @@ -0,0 +1,36 @@ +"""Summarize the paired saved observations; never runs inference.""" +import json +from pathlib import Path +root = Path(__file__).parent +result = {} +for name in ["development", "missing-fact-development", "validation", "missing-fact-timing-followup"]: + folder = root / "results" / name + comparison = json.loads((folder / "comparison.json").read_text(encoding="utf-8")) + assert comparison["status"] == "complete" + paired = comparison["comparison"] + assert not paired["baseline_errors"] and not paired["candidate_errors"] and not paired["unpaired_scenarios"] + observations = {} + counts = {} + variation = {} + for side in ["baseline", "candidate"]: + report = json.loads((folder / f"1-{side}.json").read_text(encoding="utf-8")) + rows = {(s["id"], q["query"]): q for s in report["scenarios"] for q in s.get("observations", [])} + observations[side] = rows + repeated = [] + for path in sorted(folder.glob(f"*-{side}.json")): + report_repeat = json.loads(path.read_text(encoding="utf-8")) + repeat_rows = {(s["id"], q["query"]): q for s in report_repeat["scenarios"] for q in s.get("observations", [])} + assert repeat_rows.keys() == rows.keys() + repeated.append(repeat_rows) + variation[side] = sum(any(repeat[key]["ranked"] != row["ranked"] for repeat in repeated) for key, row in rows.items()) + counts[side] = { + "positive_queries": sum(q["positive_hit_at_4"] is not None for q in rows.values()), + "positive_hits": sum(q["positive_hit_at_4"] is True for q in rows.values()), + "negative_queries": sum(q["negative_injection"] is not None for q in rows.values()), + "negative_injections": sum(q["negative_injection"] is True for q in rows.values()), + } + assert observations["baseline"].keys() == observations["candidate"].keys() + losses = [list(key) for key, q in observations["baseline"].items() + if q["positive_hit_at_4"] is True and observations["candidate"][key]["positive_hit_at_4"] is False] + result[name] = dict(counts=counts, positive_losses=losses, queries_with_repeat_ranking_variation=variation, measurements=paired["measurement_summary"]) +print(json.dumps(result, indent=2)) diff --git a/docs/audits/2026-09-07-answerability/summary.json b/docs/audits/2026-09-07-answerability/summary.json new file mode 100644 index 0000000..23c953b --- /dev/null +++ b/docs/audits/2026-09-07-answerability/summary.json @@ -0,0 +1,270 @@ +{ + "development": { + "counts": { + "baseline": { + "positive_queries": 197, + "positive_hits": 169, + "negative_queries": 13, + "negative_injections": 7 + }, + "candidate": { + "positive_queries": 197, + "positive_hits": 169, + "negative_queries": 13, + "negative_injections": 7 + } + }, + "positive_losses": [], + "queries_with_repeat_ranking_variation": { + "baseline": 0, + "candidate": 0 + }, + "measurements": { + "baseline": { + "unique_queries": 210, + "query_observations": 210, + "positive_queries": 197, + "negative_queries": 13, + "stale_queries": 0, + "positive_recall_at_4": 0.8417935702199661, + "positive_hit_at_4": 0.8578680203045685, + "positive_mrr": 0.850253807106599, + "negative_injection_rate": 0.5384615384615384, + "stale_injection_rate": null, + "first_query_mean_ms": 1064.0558, + "subsequent_query_p50_ms": 914.585, + "subsequent_query_p95_ms": 980.4788, + "subsequent_observations": 209, + "mean_model_text_bytes": 1167.5666666666666, + "mean_mcp_result_bytes": 1262.395238095238, + "memory_observations": 210, + "mean_mcp_working_set_bytes": 287334263.46666664, + "max_mcp_peak_working_set_bytes": 293224448, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + }, + "candidate": { + "unique_queries": 210, + "query_observations": 210, + "positive_queries": 197, + "negative_queries": 13, + "stale_queries": 0, + "positive_recall_at_4": 0.8417935702199661, + "positive_hit_at_4": 0.8578680203045685, + "positive_mrr": 0.850253807106599, + "negative_injection_rate": 0.5384615384615384, + "stale_injection_rate": null, + "first_query_mean_ms": 1041.7926, + "subsequent_query_p50_ms": 912.2396, + "subsequent_query_p95_ms": 988.5865, + "subsequent_observations": 209, + "mean_model_text_bytes": 1167.5666666666666, + "mean_mcp_result_bytes": 1262.395238095238, + "memory_observations": 210, + "mean_mcp_working_set_bytes": 289011829.0285714, + "max_mcp_peak_working_set_bytes": 296357888, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + } + } + }, + "missing-fact-development": { + "counts": { + "baseline": { + "positive_queries": 48, + "positive_hits": 48, + "negative_queries": 24, + "negative_injections": 19 + }, + "candidate": { + "positive_queries": 48, + "positive_hits": 48, + "negative_queries": 24, + "negative_injections": 0 + } + }, + "positive_losses": [], + "queries_with_repeat_ranking_variation": { + "baseline": 0, + "candidate": 0 + }, + "measurements": { + "baseline": { + "unique_queries": 72, + "query_observations": 72, + "positive_queries": 48, + "negative_queries": 24, + "stale_queries": 8, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": 1, + "positive_mrr": 1.0, + "negative_injection_rate": 0.7916666666666666, + "stale_injection_rate": 0, + "first_query_mean_ms": 1787.59345, + "subsequent_query_p50_ms": 358.5179, + "subsequent_query_p95_ms": 381.91740000000004, + "subsequent_observations": 68, + "mean_model_text_bytes": 486.7083333333333, + "mean_mcp_result_bytes": 567.4583333333334, + "memory_observations": 72, + "mean_mcp_working_set_bytes": 639916828.4444444, + "max_mcp_peak_working_set_bytes": 685101056, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + }, + "candidate": { + "unique_queries": 72, + "query_observations": 72, + "positive_queries": 48, + "negative_queries": 24, + "stale_queries": 8, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": 1, + "positive_mrr": 1.0, + "negative_injection_rate": 0, + "stale_injection_rate": 0, + "first_query_mean_ms": 1815.040475, + "subsequent_query_p50_ms": 364.5638, + "subsequent_query_p95_ms": 662.1342, + "subsequent_observations": 68, + "mean_model_text_bytes": 416.93055555555554, + "mean_mcp_result_bytes": 492.93055555555554, + "memory_observations": 72, + "mean_mcp_working_set_bytes": 641893432.8888888, + "max_mcp_peak_working_set_bytes": 684945408, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + } + } + }, + "validation": { + "counts": { + "baseline": { + "positive_queries": 24, + "positive_hits": 24, + "negative_queries": 20, + "negative_injections": 12 + }, + "candidate": { + "positive_queries": 24, + "positive_hits": 24, + "negative_queries": 20, + "negative_injections": 0 + } + }, + "positive_losses": [], + "queries_with_repeat_ranking_variation": { + "baseline": 0, + "candidate": 0 + }, + "measurements": { + "baseline": { + "unique_queries": 44, + "query_observations": 44, + "positive_queries": 24, + "negative_queries": 20, + "stale_queries": 0, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": 1, + "positive_mrr": 1.0, + "negative_injection_rate": 0.6, + "stale_injection_rate": null, + "first_query_mean_ms": 1780.6649499999999, + "subsequent_query_p50_ms": 355.8541, + "subsequent_query_p95_ms": 379.0547, + "subsequent_observations": 42, + "mean_model_text_bytes": 401.65909090909093, + "mean_mcp_result_bytes": 479.79545454545456, + "memory_observations": 44, + "mean_mcp_working_set_bytes": 640119528.7272727, + "max_mcp_peak_working_set_bytes": 684937216, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + }, + "candidate": { + "unique_queries": 44, + "query_observations": 44, + "positive_queries": 24, + "negative_queries": 20, + "stale_queries": 0, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": 1, + "positive_mrr": 1.0, + "negative_injection_rate": 0, + "stale_injection_rate": null, + "first_query_mean_ms": 1786.2552, + "subsequent_query_p50_ms": 359.0007, + "subsequent_query_p95_ms": 380.8821, + "subsequent_observations": 42, + "mean_model_text_bytes": 340.5, + "mean_mcp_result_bytes": 413.3181818181818, + "memory_observations": 44, + "mean_mcp_working_set_bytes": 642449780.3636364, + "max_mcp_peak_working_set_bytes": 685117440, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + } + } + }, + "missing-fact-timing-followup": { + "counts": { + "baseline": { + "positive_queries": 48, + "positive_hits": 48, + "negative_queries": 24, + "negative_injections": 19 + }, + "candidate": { + "positive_queries": 48, + "positive_hits": 48, + "negative_queries": 24, + "negative_injections": 0 + } + }, + "positive_losses": [], + "queries_with_repeat_ranking_variation": { + "baseline": 0, + "candidate": 0 + }, + "measurements": { + "baseline": { + "unique_queries": 72, + "query_observations": 144, + "positive_queries": 48, + "negative_queries": 24, + "stale_queries": 8, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": 1, + "positive_mrr": 1.0, + "negative_injection_rate": 0.7916666666666666, + "stale_injection_rate": 0, + "first_query_mean_ms": 1837.635875, + "subsequent_query_p50_ms": 366.1139, + "subsequent_query_p95_ms": 516.5822, + "subsequent_observations": 136, + "mean_model_text_bytes": 486.7083333333333, + "mean_mcp_result_bytes": 567.4583333333334, + "memory_observations": 144, + "mean_mcp_working_set_bytes": 639144760.8888888, + "max_mcp_peak_working_set_bytes": 685305856, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + }, + "candidate": { + "unique_queries": 72, + "query_observations": 144, + "positive_queries": 48, + "negative_queries": 24, + "stale_queries": 8, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": 1, + "positive_mrr": 1.0, + "negative_injection_rate": 0, + "stale_injection_rate": 0, + "first_query_mean_ms": 1853.8366, + "subsequent_query_p50_ms": 362.636, + "subsequent_query_p95_ms": 407.32370000000003, + "subsequent_observations": 136, + "mean_model_text_bytes": 416.93055555555554, + "mean_mcp_result_bytes": 492.93055555555554, + "memory_observations": 144, + "mean_mcp_working_set_bytes": 640676266.6666666, + "max_mcp_peak_working_set_bytes": 685191168, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + } + } + } +} diff --git a/docs/audits/2026-09-07-answerability/validation-frozen.json b/docs/audits/2026-09-07-answerability/validation-frozen.json new file mode 100644 index 0000000..a19a3c8 --- /dev/null +++ b/docs/audits/2026-09-07-answerability/validation-frozen.json @@ -0,0 +1,365 @@ +{ + "protocol": "Frozen before answerability candidate inference. Assistant-authored synthetic fixture; not independent certification. Do not tune on results. Preflight revision before any fixture inference: replace literal passwords (redacted by ingestion) with explicit absence. Original retained as validation-preflight-invalid.json.", + "scenarios": [ + { + "id": "orchid-answerability-frozen", + "dimension": "retrieval", + "tier": "hard", + "description": "Synthetic single-project fact requests plus broad-task and unsupported-attribute controls.", + "memories": [ + { + "key": "timeout", + "text": "Orchid gateway timeout is 45 seconds." + }, + { + "key": "retries", + "text": "Orchid client retry count is 5." + }, + { + "key": "memory", + "text": "Orchid worker memory limit is 768 MiB." + }, + { + "key": "version", + "text": "Orchid worker version 8.2 is installed." + }, + { + "key": "literal", + "text": "Orchid storage.page_bytes = 8192." + }, + { + "key": "password", + "text": "No password is required for the Orchid gateway." + }, + { + "key": "backup", + "text": "Orchid backups are retained for 36 hours." + }, + { + "key": "port", + "text": "Orchid listener binds TCP port 7321. Configure its port in listener.toml." + }, + { + "key": "advice", + "text": "Orchid version conflicts occur when lockfiles disagree. Regenerate the lockfile and check dependency constraints." + } + ], + "queries": [ + { + "query": "What is the Orchid gateway timeout?", + "relevant": [ + "timeout" + ], + "judgment": "answerable" + }, + { + "query": "How many retries does the Orchid client use?", + "relevant": [ + "retries" + ], + "judgment": "answerable" + }, + { + "query": "What is the Orchid worker memory limit?", + "relevant": [ + "memory" + ], + "judgment": "answerable" + }, + { + "query": "What version does the Orchid worker run?", + "relevant": [ + "version" + ], + "judgment": "answerable" + }, + { + "query": "What is `storage.page_bytes` in Orchid?", + "relevant": [ + "literal" + ], + "judgment": "answerable" + }, + { + "query": "What password does the Orchid gateway require?", + "relevant": [ + "password" + ], + "judgment": "answerable" + }, + { + "query": "How long are Orchid backups retained?", + "relevant": [ + "backup" + ], + "judgment": "answerable" + }, + { + "query": "Which files configure the port for Orchid?", + "relevant": [ + "port" + ], + "judgment": "answerable" + }, + { + "query": "What causes a version conflict in Orchid?", + "relevant": [ + "advice" + ], + "judgment": "answerable" + }, + { + "query": "¿Qué versión usa el worker de Orchid?", + "relevant": [ + "version" + ], + "judgment": "answerable" + }, + { + "query": "¿Cuánto tiempo se conservan las copias de seguridad de Orchid?", + "relevant": [ + "backup" + ], + "judgment": "answerable" + }, + { + "query": "What is the timeout in seconds for the gateway in Orchid?", + "relevant": [ + "timeout" + ], + "judgment": "answerable" + }, + { + "query": "What is the database timeout for Orchid?", + "relevant": [], + "judgment": "unanswerable" + }, + { + "query": "What encryption key does Orchid use?", + "relevant": [], + "judgment": "unanswerable" + }, + { + "query": "What is `storage.cache_bytes` in Orchid?", + "relevant": [], + "judgment": "unanswerable" + }, + { + "query": "How many production replicas does Orchid run?", + "relevant": [], + "judgment": "unanswerable" + }, + { + "query": "What is the OpenSSL version for Orchid?", + "relevant": [], + "judgment": "unanswerable" + }, + { + "query": "What is the database password for Orchid?", + "relevant": [], + "judgment": "unanswerable" + }, + { + "query": "How long are logs retained for Orchid?", + "relevant": [], + "judgment": "unanswerable" + }, + { + "query": "¿Qué contraseña usa la base de datos de Orchid?", + "relevant": [], + "judgment": "unanswerable" + }, + { + "query": "¿Cuántas replicas de producción tiene Orchid?", + "relevant": [], + "judgment": "unanswerable" + }, + { + "query": "Which region hosts Orchid production?", + "relevant": [], + "judgment": "unanswerable" + } + ] + }, + { + "id": "quartz-answerability-frozen", + "dimension": "retrieval", + "tier": "hard", + "description": "Synthetic single-project fact requests plus broad-task and unsupported-attribute controls.", + "memories": [ + { + "key": "timeout", + "text": "Quartz gateway timeout is 45 seconds." + }, + { + "key": "retries", + "text": "Quartz client retry count is 5." + }, + { + "key": "memory", + "text": "Quartz worker memory limit is 768 MiB." + }, + { + "key": "version", + "text": "Quartz worker version 8.2 is installed." + }, + { + "key": "literal", + "text": "Quartz storage.page_bytes = 8192." + }, + { + "key": "password", + "text": "No password is required for the Quartz gateway." + }, + { + "key": "backup", + "text": "Quartz backups are retained for 36 hours." + }, + { + "key": "port", + "text": "Quartz listener binds TCP port 8452. Configure its port in listener.toml." + }, + { + "key": "advice", + "text": "Quartz version conflicts occur when lockfiles disagree. Regenerate the lockfile and check dependency constraints." + } + ], + "queries": [ + { + "query": "What is the Quartz gateway timeout?", + "relevant": [ + "timeout" + ], + "judgment": "answerable" + }, + { + "query": "How many retries does the Quartz client use?", + "relevant": [ + "retries" + ], + "judgment": "answerable" + }, + { + "query": "What is the Quartz worker memory limit?", + "relevant": [ + "memory" + ], + "judgment": "answerable" + }, + { + "query": "What version does the Quartz worker run?", + "relevant": [ + "version" + ], + "judgment": "answerable" + }, + { + "query": "What is `storage.page_bytes` in Quartz?", + "relevant": [ + "literal" + ], + "judgment": "answerable" + }, + { + "query": "What password does the Quartz gateway require?", + "relevant": [ + "password" + ], + "judgment": "answerable" + }, + { + "query": "How long are Quartz backups retained?", + "relevant": [ + "backup" + ], + "judgment": "answerable" + }, + { + "query": "Which files configure the port for Quartz?", + "relevant": [ + "port" + ], + "judgment": "answerable" + }, + { + "query": "What causes a version conflict in Quartz?", + "relevant": [ + "advice" + ], + "judgment": "answerable" + }, + { + "query": "¿Qué versión usa el worker de Quartz?", + "relevant": [ + "version" + ], + "judgment": "answerable" + }, + { + "query": "¿Cuánto tiempo se conservan las copias de seguridad de Quartz?", + "relevant": [ + "backup" + ], + "judgment": "answerable" + }, + { + "query": "What is the timeout in seconds for the gateway in Quartz?", + "relevant": [ + "timeout" + ], + "judgment": "answerable" + }, + { + "query": "What is the database timeout for Quartz?", + "relevant": [], + "judgment": "unanswerable" + }, + { + "query": "What encryption key does Quartz use?", + "relevant": [], + "judgment": "unanswerable" + }, + { + "query": "What is `storage.cache_bytes` in Quartz?", + "relevant": [], + "judgment": "unanswerable" + }, + { + "query": "How many production replicas does Quartz run?", + "relevant": [], + "judgment": "unanswerable" + }, + { + "query": "What is the OpenSSL version for Quartz?", + "relevant": [], + "judgment": "unanswerable" + }, + { + "query": "What is the database password for Quartz?", + "relevant": [], + "judgment": "unanswerable" + }, + { + "query": "How long are logs retained for Quartz?", + "relevant": [], + "judgment": "unanswerable" + }, + { + "query": "¿Qué contraseña usa la base de datos de Quartz?", + "relevant": [], + "judgment": "unanswerable" + }, + { + "query": "¿Cuántas replicas de producción tiene Quartz?", + "relevant": [], + "judgment": "unanswerable" + }, + { + "query": "Which region hosts Quartz production?", + "relevant": [], + "judgment": "unanswerable" + } + ] + } + ] +} diff --git a/docs/audits/2026-09-07-answerability/validation-preflight-invalid.json b/docs/audits/2026-09-07-answerability/validation-preflight-invalid.json new file mode 100644 index 0000000..c83b1d0 --- /dev/null +++ b/docs/audits/2026-09-07-answerability/validation-preflight-invalid.json @@ -0,0 +1,365 @@ +{ + "protocol": "Frozen before answerability candidate inference. Assistant-authored synthetic fixture; not independent certification. Do not tune on results.", + "scenarios": [ + { + "id": "orchid-answerability-frozen", + "dimension": "retrieval", + "tier": "hard", + "description": "Synthetic single-project fact requests plus broad-task and unsupported-attribute controls.", + "memories": [ + { + "key": "timeout", + "text": "Orchid gateway timeout is 45 seconds." + }, + { + "key": "retries", + "text": "Orchid client retry count is 5." + }, + { + "key": "memory", + "text": "Orchid worker memory limit is 768 MiB." + }, + { + "key": "version", + "text": "Orchid worker version 8.2 is installed." + }, + { + "key": "literal", + "text": "Orchid storage.page_bytes = 8192." + }, + { + "key": "password", + "text": "Orchid gateway password = `test-fixture-only-42`." + }, + { + "key": "backup", + "text": "Orchid backups are retained for 36 hours." + }, + { + "key": "port", + "text": "Orchid listener binds TCP port 7321. Configure its port in listener.toml." + }, + { + "key": "advice", + "text": "Orchid version conflicts occur when lockfiles disagree. Regenerate the lockfile and check dependency constraints." + } + ], + "queries": [ + { + "query": "What is the Orchid gateway timeout?", + "relevant": [ + "timeout" + ], + "judgment": "answerable" + }, + { + "query": "How many retries does the Orchid client use?", + "relevant": [ + "retries" + ], + "judgment": "answerable" + }, + { + "query": "What is the Orchid worker memory limit?", + "relevant": [ + "memory" + ], + "judgment": "answerable" + }, + { + "query": "What version does the Orchid worker run?", + "relevant": [ + "version" + ], + "judgment": "answerable" + }, + { + "query": "What is `storage.page_bytes` in Orchid?", + "relevant": [ + "literal" + ], + "judgment": "answerable" + }, + { + "query": "What password does the Orchid gateway require?", + "relevant": [ + "password" + ], + "judgment": "answerable" + }, + { + "query": "How long are Orchid backups retained?", + "relevant": [ + "backup" + ], + "judgment": "answerable" + }, + { + "query": "Which files configure the port for Orchid?", + "relevant": [ + "port" + ], + "judgment": "answerable" + }, + { + "query": "What causes a version conflict in Orchid?", + "relevant": [ + "advice" + ], + "judgment": "answerable" + }, + { + "query": "¿Qué versión usa el worker de Orchid?", + "relevant": [ + "version" + ], + "judgment": "answerable" + }, + { + "query": "¿Cuánto tiempo se conservan las copias de seguridad de Orchid?", + "relevant": [ + "backup" + ], + "judgment": "answerable" + }, + { + "query": "What is the timeout in seconds for the gateway in Orchid?", + "relevant": [ + "timeout" + ], + "judgment": "answerable" + }, + { + "query": "What is the database timeout for Orchid?", + "relevant": [], + "judgment": "unanswerable" + }, + { + "query": "What encryption key does Orchid use?", + "relevant": [], + "judgment": "unanswerable" + }, + { + "query": "What is `storage.cache_bytes` in Orchid?", + "relevant": [], + "judgment": "unanswerable" + }, + { + "query": "How many production replicas does Orchid run?", + "relevant": [], + "judgment": "unanswerable" + }, + { + "query": "What is the OpenSSL version for Orchid?", + "relevant": [], + "judgment": "unanswerable" + }, + { + "query": "What is the database password for Orchid?", + "relevant": [], + "judgment": "unanswerable" + }, + { + "query": "How long are logs retained for Orchid?", + "relevant": [], + "judgment": "unanswerable" + }, + { + "query": "¿Qué contraseña usa la base de datos de Orchid?", + "relevant": [], + "judgment": "unanswerable" + }, + { + "query": "¿Cuántas replicas de producción tiene Orchid?", + "relevant": [], + "judgment": "unanswerable" + }, + { + "query": "Which region hosts Orchid production?", + "relevant": [], + "judgment": "unanswerable" + } + ] + }, + { + "id": "quartz-answerability-frozen", + "dimension": "retrieval", + "tier": "hard", + "description": "Synthetic single-project fact requests plus broad-task and unsupported-attribute controls.", + "memories": [ + { + "key": "timeout", + "text": "Quartz gateway timeout is 45 seconds." + }, + { + "key": "retries", + "text": "Quartz client retry count is 5." + }, + { + "key": "memory", + "text": "Quartz worker memory limit is 768 MiB." + }, + { + "key": "version", + "text": "Quartz worker version 8.2 is installed." + }, + { + "key": "literal", + "text": "Quartz storage.page_bytes = 8192." + }, + { + "key": "password", + "text": "Quartz gateway password = `test-fixture-only-42`." + }, + { + "key": "backup", + "text": "Quartz backups are retained for 36 hours." + }, + { + "key": "port", + "text": "Quartz listener binds TCP port 8452. Configure its port in listener.toml." + }, + { + "key": "advice", + "text": "Quartz version conflicts occur when lockfiles disagree. Regenerate the lockfile and check dependency constraints." + } + ], + "queries": [ + { + "query": "What is the Quartz gateway timeout?", + "relevant": [ + "timeout" + ], + "judgment": "answerable" + }, + { + "query": "How many retries does the Quartz client use?", + "relevant": [ + "retries" + ], + "judgment": "answerable" + }, + { + "query": "What is the Quartz worker memory limit?", + "relevant": [ + "memory" + ], + "judgment": "answerable" + }, + { + "query": "What version does the Quartz worker run?", + "relevant": [ + "version" + ], + "judgment": "answerable" + }, + { + "query": "What is `storage.page_bytes` in Quartz?", + "relevant": [ + "literal" + ], + "judgment": "answerable" + }, + { + "query": "What password does the Quartz gateway require?", + "relevant": [ + "password" + ], + "judgment": "answerable" + }, + { + "query": "How long are Quartz backups retained?", + "relevant": [ + "backup" + ], + "judgment": "answerable" + }, + { + "query": "Which files configure the port for Quartz?", + "relevant": [ + "port" + ], + "judgment": "answerable" + }, + { + "query": "What causes a version conflict in Quartz?", + "relevant": [ + "advice" + ], + "judgment": "answerable" + }, + { + "query": "¿Qué versión usa el worker de Quartz?", + "relevant": [ + "version" + ], + "judgment": "answerable" + }, + { + "query": "¿Cuánto tiempo se conservan las copias de seguridad de Quartz?", + "relevant": [ + "backup" + ], + "judgment": "answerable" + }, + { + "query": "What is the timeout in seconds for the gateway in Quartz?", + "relevant": [ + "timeout" + ], + "judgment": "answerable" + }, + { + "query": "What is the database timeout for Quartz?", + "relevant": [], + "judgment": "unanswerable" + }, + { + "query": "What encryption key does Quartz use?", + "relevant": [], + "judgment": "unanswerable" + }, + { + "query": "What is `storage.cache_bytes` in Quartz?", + "relevant": [], + "judgment": "unanswerable" + }, + { + "query": "How many production replicas does Quartz run?", + "relevant": [], + "judgment": "unanswerable" + }, + { + "query": "What is the OpenSSL version for Quartz?", + "relevant": [], + "judgment": "unanswerable" + }, + { + "query": "What is the database password for Quartz?", + "relevant": [], + "judgment": "unanswerable" + }, + { + "query": "How long are logs retained for Quartz?", + "relevant": [], + "judgment": "unanswerable" + }, + { + "query": "¿Qué contraseña usa la base de datos de Quartz?", + "relevant": [], + "judgment": "unanswerable" + }, + { + "query": "¿Cuántas replicas de producción tiene Quartz?", + "relevant": [], + "judgment": "unanswerable" + }, + { + "query": "Which region hosts Quartz production?", + "relevant": [], + "judgment": "unanswerable" + } + ] + } + ] +} From ab4480076ffb2ce9942bf7eefdcc0569a5052393 Mon Sep 17 00:00:00 2001 From: RodCor Date: Mon, 7 Sep 2026 03:01:36 -0300 Subject: [PATCH 27/34] Preserve verification logs referenced by answerability manifest --- .gitattributes | 2 +- .../results/development/1-baseline.stderr.log | 4 + .../results/development/1-baseline.stdout.log | 6811 +++++++++++++++++ .../development/1-candidate.stderr.log | 4 + .../development/1-candidate.stdout.log | 6811 +++++++++++++++++ .../1-baseline.stderr.log | 10 + .../1-baseline.stdout.log | 2130 ++++++ .../1-candidate.stderr.log | 10 + .../1-candidate.stdout.log | 1921 +++++ .../1-baseline.stderr.log | 10 + .../1-baseline.stdout.log | 2130 ++++++ .../1-candidate.stderr.log | 10 + .../1-candidate.stdout.log | 1921 +++++ .../2-baseline.stderr.log | 10 + .../2-baseline.stdout.log | 2130 ++++++ .../2-candidate.stderr.log | 10 + .../2-candidate.stdout.log | 1921 +++++ .../results/validation/1-baseline.stderr.log | 6 + .../results/validation/1-baseline.stdout.log | 1241 +++ .../results/validation/1-candidate.stderr.log | 6 + .../results/validation/1-candidate.stdout.log | 1101 +++ .../verification/benchmark-python.log | 15 + .../verification/benchmark-rust.log | 148 + .../verification/hook.log | 3 + .../verification/release-build.log | 11 + .../verification/review-regressions-red.log | 43 + .../verification/scope-green.log | 18 + .../verification/workspace.log | 1552 ++++ 28 files changed, 29988 insertions(+), 1 deletion(-) create mode 100644 docs/audits/2026-09-07-answerability/results/development/1-baseline.stderr.log create mode 100644 docs/audits/2026-09-07-answerability/results/development/1-baseline.stdout.log create mode 100644 docs/audits/2026-09-07-answerability/results/development/1-candidate.stderr.log create mode 100644 docs/audits/2026-09-07-answerability/results/development/1-candidate.stdout.log create mode 100644 docs/audits/2026-09-07-answerability/results/missing-fact-development/1-baseline.stderr.log create mode 100644 docs/audits/2026-09-07-answerability/results/missing-fact-development/1-baseline.stdout.log create mode 100644 docs/audits/2026-09-07-answerability/results/missing-fact-development/1-candidate.stderr.log create mode 100644 docs/audits/2026-09-07-answerability/results/missing-fact-development/1-candidate.stdout.log create mode 100644 docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/1-baseline.stderr.log create mode 100644 docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/1-baseline.stdout.log create mode 100644 docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/1-candidate.stderr.log create mode 100644 docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/1-candidate.stdout.log create mode 100644 docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/2-baseline.stderr.log create mode 100644 docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/2-baseline.stdout.log create mode 100644 docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/2-candidate.stderr.log create mode 100644 docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/2-candidate.stdout.log create mode 100644 docs/audits/2026-09-07-answerability/results/validation/1-baseline.stderr.log create mode 100644 docs/audits/2026-09-07-answerability/results/validation/1-baseline.stdout.log create mode 100644 docs/audits/2026-09-07-answerability/results/validation/1-candidate.stderr.log create mode 100644 docs/audits/2026-09-07-answerability/results/validation/1-candidate.stdout.log create mode 100644 docs/audits/2026-09-07-answerability/verification/benchmark-python.log create mode 100644 docs/audits/2026-09-07-answerability/verification/benchmark-rust.log create mode 100644 docs/audits/2026-09-07-answerability/verification/hook.log create mode 100644 docs/audits/2026-09-07-answerability/verification/release-build.log create mode 100644 docs/audits/2026-09-07-answerability/verification/review-regressions-red.log create mode 100644 docs/audits/2026-09-07-answerability/verification/scope-green.log create mode 100644 docs/audits/2026-09-07-answerability/verification/workspace.log diff --git a/.gitattributes b/.gitattributes index 967055e..3a5f38d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,3 @@ # Preserve the exact bytes of reproducible audit artifacts. docs/audits/2026-09-07-retrieval/** -text whitespace=cr-at-eol -docs/audits/2026-09-07-answerability/** -text whitespace=cr-at-eol +docs/audits/2026-09-07-answerability/** -text whitespace=cr-at-eol,-blank-at-eof diff --git a/docs/audits/2026-09-07-answerability/results/development/1-baseline.stderr.log b/docs/audits/2026-09-07-answerability/results/development/1-baseline.stderr.log new file mode 100644 index 0000000..3f941db --- /dev/null +++ b/docs/audits/2026-09-07-answerability/results/development/1-baseline.stderr.log @@ -0,0 +1,4 @@ +brainbench: 1 scenario(s) to run + [1/1] existing-development-100 | dim=retrieval tier=hard ... + -> score=0.82 | positive-recall@4=0.84 mrr=0.85 stale-hit=n/a resolution=n/a false-injection=0.538 (n=13) positive-n=197 negative-n=13 (210 queries) +kbench brainbench: report saved -> E:\tmp\kimetsu-brain-hardening\bench\local\runs\brainbench\2026-09-07T05-50-26.1265486Z.json diff --git a/docs/audits/2026-09-07-answerability/results/development/1-baseline.stdout.log b/docs/audits/2026-09-07-answerability/results/development/1-baseline.stdout.log new file mode 100644 index 0000000..193798b --- /dev/null +++ b/docs/audits/2026-09-07-answerability/results/development/1-baseline.stdout.log @@ -0,0 +1,6811 @@ +{ + "generated_at": "2026-09-07T05:50:26.125556Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-retrieval\\development-100.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "test_env_lock inside with_user_brain_disabled deadlock", + "ranked": [ + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBK795VCKKWK7JEKPT4J", + "id": "01M1X6G6HT1E3191G8DDZ1BB0G", + "kind": "memory", + "score": 0.9999488592147828, + "summary": "project:fact - [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure — `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1064.0558, + "first_query": true, + "server_startup_ms": 77.69460000000001, + "model_text_bytes": 796, + "mcp_result_bytes": 877, + "wire_bytes": 912, + "reported_used_tokens": 877, + "working_set_bytes": 227401728, + "peak_working_set_bytes": 248041472 + }, + { + "query": "why does my test hang after calling with_user_brain_disabled when I also lock test_env_lock?", + "ranked": [ + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBK795VCKKWK7JEKPT4J", + "id": "01M1X6G78VAMQM1FA042FM8J79", + "kind": "memory", + "score": 0.9990190267562866, + "summary": "project:fact - [2026-09-07] [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure — `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 831.6073, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 808, + "mcp_result_bytes": 889, + "wire_bytes": 924, + "reported_used_tokens": 889, + "working_set_bytes": 229339136, + "peak_working_set_bytes": 248041472 + }, + { + "query": "ingest_repo_at_root brain_root files_root kimetsu remote", + "ranked": [ + "remote-ingest-split-roots", + "kimetsu-write-tools-gate", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBMAHT4JHT1S9T1YAQCN", + "id": "01M1X6G82SZDATB3KJDX7DYTAR", + "kind": "memory", + "score": 0.999886393547058, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1X6G5C7NRN6660DTHDS58ZS", + "id": "01M1X6G82S70W5ZDFWB37ZRPK2", + "kind": "memory", + "score": 0.8439717888832092, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level — disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1X6FBP8RS75FNREKQZ0WD3Q", + "id": "01M1X6G82S92TJKRHCGAK1R3K4", + "kind": "memory", + "score": 0.8363722562789917, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 950.3385, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2726, + "mcp_result_bytes": 2891, + "wire_bytes": 2926, + "reported_used_tokens": 2891, + "working_set_bytes": 252162048, + "peak_working_set_bytes": 253071360 + }, + { + "query": "why does the remote server index the wrong directory when I run kimetsu brain ingest?", + "ranked": [ + "remote-ingest-split-roots", + "onnx-dim-mismatch" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBMAHT4JHT1S9T1YAQCN", + "id": "01M1X6G90VD0EBFVR54J94YG9M", + "kind": "memory", + "score": 0.9836117625236512, + "summary": "project:fact - [2026-09-07] [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1X6FG3R3G9YXMC4MA14S64D", + "id": "01M1X6G90VJ386CN1GSSWEG69P", + "kind": "memory", + "score": 0.3657674789428711, + "summary": "project:fact - [2026-09-07] [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results — the ANN index shape mismatch isn't always caught at runtime." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 934.1104, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1800, + "mcp_result_bytes": 1899, + "wire_bytes": 1934, + "reported_used_tokens": 1899, + "working_set_bytes": 257900544, + "peak_working_set_bytes": 258822144 + }, + { + "query": "kimetsu plugin install --remote mcp.json authorization bearer token", + "ranked": [ + "remote-mcp-host-wiring", + "mcp-stdout-protocol" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBP8RS75FNREKQZ0WD3Q", + "id": "01M1X6G9XRBEYXA378PCFE9JR1", + "kind": "memory", + "score": 0.999605119228363, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + }, + { + "expansion_handle": "memory:01M1X6G1W0ZBD86TBRPQESXKTX", + "id": "01M1X6G9XSA4W90E9T0B4ZRZX3", + "kind": "memory", + "score": 0.3375842869281769, + "summary": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 855.5303, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1472, + "mcp_result_bytes": 1619, + "wire_bytes": 1654, + "reported_used_tokens": 1619, + "working_set_bytes": 258347008, + "peak_working_set_bytes": 259272704 + }, + { + "query": "how do I wire a remote kimetsu brain into Claude Code without storing the token in the config file?", + "ranked": [ + "remote-mcp-host-wiring", + "mcp-tool-naming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBP8RS75FNREKQZ0WD3Q", + "id": "01M1X6GARJ0ZZ2KQKV4NBD7JY8", + "kind": "memory", + "score": 0.9963359832763672, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + }, + { + "expansion_handle": "memory:01M1X6G20244N0FWHDXK4YPMG6", + "id": "01M1X6GARJB67WWWJB2B5V42HK", + "kind": "memory", + "score": 0.831425666809082, + "summary": "project:fact - [tags: mcp tool naming convention kimetsu] MCP tool names must be valid identifiers for all host agents. Claude Code restricts tool names to `[a-zA-Z0-9_-]` and max 64 chars. Use `snake_case` (kimetsu_brain_context, kimetsu_brain_record) — hyphen is technically allowed but some hosts reject it." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 856.1008, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1454, + "mcp_result_bytes": 1601, + "wire_bytes": 1636, + "reported_used_tokens": 1601, + "working_set_bytes": 258940928, + "peak_working_set_bytes": 259862528 + }, + { + "query": "cargo feature unification kimetsu-brain embeddings fastembed test failure", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-profile-override", + "clap-version-build-flavor" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBR4K1EDGA6RZKFR1659", + "id": "01M1X6GBKD5FEW1YV6R296225T", + "kind": "memory", + "score": 0.9996790885925292, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X6FCT0SQT9SH5HPBEB6HH8", + "id": "01M1X6GBKDXWMC9W6PXCR80FK8", + "kind": "memory", + "score": 0.9923595786094666, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1X6FC2JTJ2W47SX7Q8Z01AV", + "id": "01M1X6GBKD828YFJ51YSH10K58", + "kind": "memory", + "score": 0.585203230381012, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 843.476, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2387, + "mcp_result_bytes": 2524, + "wire_bytes": 2559, + "reported_used_tokens": 2524, + "working_set_bytes": 260677632, + "peak_working_set_bytes": 261599232 + }, + { + "query": "my integration tests pass in isolation but break when I run cargo test --workspace — embedder changed?", + "ranked": [ + "cargo-feature-unification-embeddings", + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBR4K1EDGA6RZKFR1659", + "id": "01M1X6GCE4MN580GYYAZCSFFD5", + "kind": "memory", + "score": 0.9943140745162964, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X6FC1PYTV8EEQVV2FCY4FV", + "id": "01M1X6GCE4M20JYF5C76SVEFEH", + "kind": "memory", + "score": 0.31398114562034607, + "summary": "project:fact - [2026-09-07] [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 916.6398999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1714, + "mcp_result_bytes": 1817, + "wire_bytes": 1852, + "reported_used_tokens": 1817, + "working_set_bytes": 261357568, + "peak_working_set_bytes": 262279168 + }, + { + "query": "build_anthropic_body bedrock-2023-05-31 InvokeModel blocking reqwest", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBSFGZG953DWGDC5CQJC", + "id": "01M1X6GDB0PCK1CSQR0JT69G9P", + "kind": "memory", + "score": 0.9973788261413574, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X6FBYZ5JT46Z87176E5AER", + "id": "01M1X6GDB0KCR0TJ644574Y4T2", + "kind": "memory", + "score": 0.6916899085044861, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 718.4043, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2193, + "mcp_result_bytes": 2320, + "wire_bytes": 2356, + "reported_used_tokens": 2320, + "working_set_bytes": 261681152, + "peak_working_set_bytes": 262594560 + }, + { + "query": "how do I add AWS Bedrock as a model provider in Kimetsu without pulling in the aws-sdk?", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-region-resolution", + "aws-credentials-chain", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBSFGZG953DWGDC5CQJC", + "id": "01M1X6GE0XBEQD8EFDJ70T25J2", + "kind": "memory", + "score": 0.9998898506164552, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X6G23CEN8Y7G8DV4SNBYQB", + "id": "01M1X6GE0XF34XFNAMYWJKWKFZ", + "kind": "memory", + "score": 0.995676338672638, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X6G225TRC4EVAFGXT9RHRX", + "id": "01M1X6GE0XH08FWA6GZ18S7P2T", + "kind": "memory", + "score": 0.987064242362976, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + }, + { + "expansion_handle": "memory:01M1X6FBYZ5JT46Z87176E5AER", + "id": "01M1X6GE0XCXNMCF0Q6F7PX9H5", + "kind": "memory", + "score": 0.9493880867958068, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 857.4794999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3455, + "mcp_result_bytes": 3618, + "wire_bytes": 3654, + "reported_used_tokens": 3618, + "working_set_bytes": 269643776, + "peak_working_set_bytes": 270561280 + }, + { + "query": "BridgeTarget enum seams plugin_install_inner plugin_status_inner resolve_setup_hosts", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBV74YJ9W6XP288GKM7H", + "id": "01M1X6GEVT1H2XCTD5KYY4WCV4", + "kind": "memory", + "score": 0.9997583031654358, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 722.1896, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1060, + "mcp_result_bytes": 1141, + "wire_bytes": 1177, + "reported_used_tokens": 1141, + "working_set_bytes": 279629824, + "peak_working_set_bytes": 280543232 + }, + { + "query": "I added a new host to the bridge enum but cargo gives me compile errors in five different match arms — what did I miss?", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBV74YJ9W6XP288GKM7H", + "id": "01M1X6GFJ9S8K6PT5W0A9P8GW4", + "kind": "memory", + "score": 0.9977060556411744, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 931.4042999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1059, + "mcp_result_bytes": 1140, + "wire_bytes": 1176, + "reported_used_tokens": 1140, + "working_set_bytes": 280158208, + "peak_working_set_bytes": 281071616 + }, + { + "query": "Pi extension factory defineExtension agent_end session_shutdown kimetsu.ts", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBWBQK6S520BBWHP1EHF", + "id": "01M1X6GGFHDE82W1470TQHWVA1", + "kind": "memory", + "score": 0.9990354776382446, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1113.4763, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 804, + "mcp_result_bytes": 893, + "wire_bytes": 929, + "reported_used_tokens": 893, + "working_set_bytes": 280326144, + "peak_working_set_bytes": 281239552 + }, + { + "query": "how does Pi (earendil-works/pi) load plugins and what lifecycle hooks does it expose?", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBWBQK6S520BBWHP1EHF", + "id": "01M1X6GHMEX0WDHBJYKZWWHBDG", + "kind": "memory", + "score": 0.9934834837913512, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1071.9474, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 803, + "mcp_result_bytes": 892, + "wire_bytes": 928, + "reported_used_tokens": 892, + "working_set_bytes": 280891392, + "peak_working_set_bytes": 281800704 + }, + { + "query": "aws-sigv4 SigningParams apply_to_request_http1x reqwest sign-http", + "ranked": [ + "aws-sigv4-bedrock-blocking", + "aws-presigned-urls", + "bedrock-kimetsu-provider", + "aws-credentials-chain" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBYZ5JT46Z87176E5AER", + "id": "01M1X6GJMFQER8GQC6M0J71NQH", + "kind": "memory", + "score": 0.9995608925819396, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1X6G25DB3HRGVQYHSTK90X7", + "id": "01M1X6GJMF26JM262MXET2Q0TB", + "kind": "memory", + "score": 0.984916627407074, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time — clock skew > 15 minutes causes `RequestTimeTooSkewed`." + }, + { + "expansion_handle": "memory:01M1X6FBSFGZG953DWGDC5CQJC", + "id": "01M1X6GJMFHS3VA39V00AXNQ92", + "kind": "memory", + "score": 0.983895778656006, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X6G225TRC4EVAFGXT9RHRX", + "id": "01M1X6GJMFY3KRJ14NHX8Y09NF", + "kind": "memory", + "score": 0.8592692017555237, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 759.3734, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3507, + "mcp_result_bytes": 3670, + "wire_bytes": 3706, + "reported_used_tokens": 3670, + "working_set_bytes": 280977408, + "peak_working_set_bytes": 281878528 + }, + { + "query": "how do I sign a Bedrock InvokeModel request with aws-sigv4 in blocking Rust?", + "ranked": [ + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider", + "aws-region-resolution", + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBYZ5JT46Z87176E5AER", + "id": "01M1X6GKC3ANKVW4TM679NFEBZ", + "kind": "memory", + "score": 0.9998323917388916, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1X6FBSFGZG953DWGDC5CQJC", + "id": "01M1X6GKC3JDBDGVVM0EMDRMTK", + "kind": "memory", + "score": 0.9970844388008118, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X6G23CEN8Y7G8DV4SNBYQB", + "id": "01M1X6GKC34VZT92HZ7QZ4WE7Z", + "kind": "memory", + "score": 0.9468621611595154, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X6G25DB3HRGVQYHSTK90X7", + "id": "01M1X6GKC34E3915P40FRVTMF0", + "kind": "memory", + "score": 0.9210098385810852, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time — clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 883.6748, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3434, + "mcp_result_bytes": 3597, + "wire_bytes": 3633, + "reported_used_tokens": 3597, + "working_set_bytes": 281382912, + "peak_working_set_bytes": 282300416 + }, + { + "query": "KIMETSU_RUNS_GC env opt-out TraceWriter create gc_old_runs caller", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC0TBZ3RC6PJTY71XESF", + "id": "01M1X6GM7DHM43VG3HCEA15089", + "kind": "memory", + "score": 0.999936580657959, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 840.2226, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 761, + "mcp_result_bytes": 842, + "wire_bytes": 878, + "reported_used_tokens": 842, + "working_set_bytes": 281440256, + "peak_working_set_bytes": 282353664 + }, + { + "query": "where should I put the KIMETSU_RUNS_GC=0 guard — inside the GC function or at the call site?", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC0TBZ3RC6PJTY71XESF", + "id": "01M1X6GN1XYCW7NMZXDB2X5SQ6", + "kind": "memory", + "score": 0.9971211552619934, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 930.0327000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 762, + "mcp_result_bytes": 843, + "wire_bytes": 879, + "reported_used_tokens": 843, + "working_set_bytes": 281997312, + "peak_working_set_bytes": 282918912 + }, + { + "query": "git_init_boundary ProjectPaths::discover temp dir user brain isolation", + "ranked": [ + "init-project-git-boundary", + "git-worktree-brain-isolation", + "testing-temp-dirs-ci", + "kimetsu-memory-scopes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC1PYTV8EEQVV2FCY4FV", + "id": "01M1X6GNYHH5SW7K5GB679CXPW", + "kind": "memory", + "score": 0.9997712969779968, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + }, + { + "expansion_handle": "memory:01M1X6FSA7MZHYF9BQ9B8J0E8G", + "id": "01M1X6GNYHYWNAJ781GYHWY48J", + "kind": "memory", + "score": 0.9962491393089294, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root — if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + }, + { + "expansion_handle": "memory:01M1X6FXWMDSNDYDFJ8T7ES5D5", + "id": "01M1X6GNYHT0FFP2NRYF78M81R", + "kind": "memory", + "score": 0.9682154655456544, + "summary": "project:fact - [tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure." + }, + { + "expansion_handle": "memory:01M1X6G596BN9AAJMY7GJTXTHF", + "id": "01M1X6GNYHFRP23A0NGN4P6QMV", + "kind": "memory", + "score": 0.3057229816913605, + "summary": "project:fact - [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available — if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 784.2253000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2580, + "mcp_result_bytes": 2715, + "wire_bytes": 2751, + "reported_used_tokens": 2715, + "working_set_bytes": 282034176, + "peak_working_set_bytes": 282947584 + }, + { + "query": "my test calls init_project but it writes to the real ~/.kimetsu instead of the temp folder — why?", + "ranked": [ + "init-project-git-boundary", + "cargo-feature-unification-embeddings", + "testing-fixture-drift", + "tokio-runtime-in-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC1PYTV8EEQVV2FCY4FV", + "id": "01M1X6GPQ42YVCEKA03DWXDCHA", + "kind": "memory", + "score": 0.9995088577270508, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + }, + { + "expansion_handle": "memory:01M1X6FBR4K1EDGA6RZKFR1659", + "id": "01M1X6GPQ4BMNHP31MS9EP7PRK", + "kind": "memory", + "score": 0.7287850975990295, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X6G1V5C0B2FRN6AHK9JQJW", + "id": "01M1X6GPQ5Q5WMPT9Q6HGB1TS9", + "kind": "memory", + "score": 0.6596062183380127, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + }, + { + "expansion_handle": "memory:01M1X6FSH3ZJYWBSJKZKZYY4Z4", + "id": "01M1X6GPQ4ED1432NCG867HTPJ", + "kind": "memory", + "score": 0.3297702968120575, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 933.5029999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2833, + "mcp_result_bytes": 2980, + "wire_bytes": 3016, + "reported_used_tokens": 2980, + "working_set_bytes": 282288128, + "peak_working_set_bytes": 283201536 + }, + { + "query": "clap command version KIMETSU_VERSION_DISPLAY cfg feature embeddings", + "ranked": [ + "clap-version-build-flavor", + "cargo-feature-unification-embeddings" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC2JTJ2W47SX7Q8Z01AV", + "id": "01M1X6GQN2GQK76N77G7T6KE2H", + "kind": "memory", + "score": 0.9996613264083862, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + }, + { + "expansion_handle": "memory:01M1X6FBR4K1EDGA6RZKFR1659", + "id": "01M1X6GQN2PPQCZ3H4YART30XC", + "kind": "memory", + "score": 0.3973360061645508, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 741.758, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1922, + "mcp_result_bytes": 2041, + "wire_bytes": 2077, + "reported_used_tokens": 2041, + "working_set_bytes": 282505216, + "peak_working_set_bytes": 283410432 + }, + { + "query": "how do I show the build flavor (lean vs embeddings) in the kimetsu --version output?", + "ranked": [ + "clap-version-build-flavor", + "cargo-feature-unification-embeddings", + "onnx-quantization-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC2JTJ2W47SX7Q8Z01AV", + "id": "01M1X6GRBV1V2S65H7SDY8V6B8", + "kind": "memory", + "score": 0.9978312849998474, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + }, + { + "expansion_handle": "memory:01M1X6FBR4K1EDGA6RZKFR1659", + "id": "01M1X6GRBVRV6MM3SPFX3HDAA7", + "kind": "memory", + "score": 0.8926984667778015, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X6FFZW8Z92NY5W7JM4TSR9", + "id": "01M1X6GRBVWZXX9F4W6F5ZXCN3", + "kind": "memory", + "score": 0.8877003192901611, + "summary": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals — cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 945.3303, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2672, + "mcp_result_bytes": 2809, + "wire_bytes": 2845, + "reported_used_tokens": 2809, + "working_set_bytes": 282533888, + "peak_working_set_bytes": 283451392 + }, + { + "query": "Harbor pyiceberg os.getcwd stale WSL2 DrvFs worker-result subprocess re-exec", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC3TT3SRMESXFHKJXD7R", + "id": "01M1X6GS939YQF5CJ90F7YADFF", + "kind": "memory", + "score": 0.9998155236244202, + "summary": "project:fact - [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 931.061, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1026, + "mcp_result_bytes": 1107, + "wire_bytes": 1143, + "reported_used_tokens": 1107, + "working_set_bytes": 282628096, + "peak_working_set_bytes": 283537408 + }, + { + "query": "why does my kbench sweep crash after the first trial with 'result.json missing' on WSL2?", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC3TT3SRMESXFHKJXD7R", + "id": "01M1X6GT68KSJFRSGYWNEP0RK6", + "kind": "memory", + "score": 0.998451828956604, + "summary": "project:fact - [2026-09-07] [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 944.5233, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1038, + "mcp_result_bytes": 1119, + "wire_bytes": 1155, + "reported_used_tokens": 1119, + "working_set_bytes": 282644480, + "peak_working_set_bytes": 283566080 + }, + { + "query": "rusqlite VACUUM transaction WAL checkpoint wal_checkpoint TRUNCATE", + "ranked": [ + "sqlite-vacuum-wal-checkpoint", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC54FN6W8DZFC2Z4V0V4", + "id": "01M1X6GV3RMQK6ZK8AEPBM486G", + "kind": "memory", + "score": 0.9996871948242188, + "summary": "project:fact - [tags: rust sqlite vacuum rusqlite windows] When implementing SQLite VACUUM in rusqlite: VACUUM cannot run inside a transaction. rusqlite's Connection does not hold an implicit transaction, so `conn.execute_batch(\"VACUUM;\")` works directly. After VACUUM, run `PRAGMA wal_checkpoint(TRUNCATE);` before measuring file size — on Windows the WAL file can hold significant space that isn't reflected in the main db file until the checkpoint runs." + }, + { + "expansion_handle": "memory:01M1X6FCB5829A6ZF2C5FB9VZW", + "id": "01M1X6GV3RW3TAP86M1Q639T4Y", + "kind": "memory", + "score": 0.5274003744125366, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 723.9436000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1507, + "mcp_result_bytes": 1610, + "wire_bytes": 1646, + "reported_used_tokens": 1610, + "working_set_bytes": 282648576, + "peak_working_set_bytes": 283566080 + }, + { + "query": "my SQLite VACUUM reports the file shrank but the disk usage stayed the same — Windows WAL?", + "ranked": [ + "sqlite-vacuum-wal-checkpoint", + "sqlite-wal-network-drive" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC54FN6W8DZFC2Z4V0V4", + "id": "01M1X6GVTKT26CCH3SKKT94C7R", + "kind": "memory", + "score": 0.9155893921852112, + "summary": "project:fact - [tags: rust sqlite vacuum rusqlite windows] When implementing SQLite VACUUM in rusqlite: VACUUM cannot run inside a transaction. rusqlite's Connection does not hold an implicit transaction, so `conn.execute_batch(\"VACUUM;\")` works directly. After VACUUM, run `PRAGMA wal_checkpoint(TRUNCATE);` before measuring file size — on Windows the WAL file can hold significant space that isn't reflected in the main db file until the checkpoint runs." + }, + { + "expansion_handle": "memory:01M1X6FCDYEQS880XHFGF7HHKN", + "id": "01M1X6GVTK26A8V6FM1XJ8T317", + "kind": "memory", + "score": 0.902395486831665, + "summary": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 985.3858, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1357, + "mcp_result_bytes": 1460, + "wire_bytes": 1496, + "reported_used_tokens": 1460, + "working_set_bytes": 282660864, + "peak_working_set_bytes": 283578368 + }, + { + "query": "add_memory import dedup seen_ids snapshot pre-existing active memory IDs", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC5XF447XNVWDTA1KX30", + "id": "01M1X6GWSAXJCVQKX0AVYRHDAW", + "kind": "memory", + "score": 0.9999133348464966, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount — both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 820.4101, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 966, + "mcp_result_bytes": 1047, + "wire_bytes": 1083, + "reported_used_tokens": 1047, + "working_set_bytes": 282714112, + "peak_working_set_bytes": 283623424 + }, + { + "query": "brain import re-imports the same JSON file but the deduplication counter is wrong — why?", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC5XF447XNVWDTA1KX30", + "id": "01M1X6GXJWPBMSAPSEFY7380FC", + "kind": "memory", + "score": 0.9254016876220704, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount — both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 860.1193999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 965, + "mcp_result_bytes": 1046, + "wire_bytes": 1082, + "reported_used_tokens": 1046, + "working_set_bytes": 282865664, + "peak_working_set_bytes": 283783168 + }, + { + "query": "toml::from_str Value parse document unexpected content str.parse", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC706HRRZF8YZH8DDCVM", + "id": "01M1X6GYDSRWXNGN2CJ8WD1P8M", + "kind": "memory", + "score": 0.9991866946220398, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 749.0263, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 734, + "mcp_result_bytes": 815, + "wire_bytes": 851, + "reported_used_tokens": 815, + "working_set_bytes": 282951680, + "peak_working_set_bytes": 283865088 + }, + { + "query": "how do I parse a TOML configuration file into a toml::Value in toml 0.9?", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC706HRRZF8YZH8DDCVM", + "id": "01M1X6GZ5ARC8XFTH8HAWNHVY0", + "kind": "memory", + "score": 0.9992641806602478, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 886.9189, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 733, + "mcp_result_bytes": 814, + "wire_bytes": 850, + "reported_used_tokens": 814, + "working_set_bytes": 282959872, + "peak_working_set_bytes": 283873280 + }, + { + "query": "CIM CreationDate DMTF WMI ps etimes started_at assess_mcp_skew", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC7XP8VRJ8NHE6MZGS3H", + "id": "01M1X6H015QVVFHBY1GF76Z2GY", + "kind": "memory", + "score": 0.9957948923110962, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 695.6131, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 924, + "mcp_result_bytes": 1013, + "wire_bytes": 1049, + "reported_used_tokens": 1013, + "working_set_bytes": 283045888, + "peak_working_set_bytes": 283951104 + }, + { + "query": "how do I read a process start time on both Windows and Linux in pure Rust?", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC7XP8VRJ8NHE6MZGS3H", + "id": "01M1X6H0PWEF03DSGGDX6EGJJS", + "kind": "memory", + "score": 0.99687659740448, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 901.9759, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 921, + "mcp_result_bytes": 1010, + "wire_bytes": 1046, + "reported_used_tokens": 1010, + "working_set_bytes": 283074560, + "peak_working_set_bytes": 284000256 + }, + { + "query": "processes_locking_target decide_preflight_action BufRead Write update.rs", + "ranked": [ + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC93AQB8S92QXHHFQGQ9", + "id": "01M1X6H1JZ0MCP8YH21MP1A33N", + "kind": "memory", + "score": 0.9995336532592772, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics — mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 755.7758, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1133, + "mcp_result_bytes": 1214, + "wire_bytes": 1250, + "reported_used_tokens": 1214, + "working_set_bytes": 283107328, + "peak_working_set_bytes": 284024832 + }, + { + "query": "how should I reuse the existing process enumerator in the update preflight check to avoid a second PowerShell query?", + "ranked": [ + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC93AQB8S92QXHHFQGQ9", + "id": "01M1X6H2APTS5AQZDCK7MQMTKQ", + "kind": "memory", + "score": 0.9973384737968444, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics — mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 946.4978, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1132, + "mcp_result_bytes": 1213, + "wire_bytes": 1249, + "reported_used_tokens": 1213, + "working_set_bytes": 283414528, + "peak_working_set_bytes": 284332032 + }, + { + "query": "cfg_attr windows allow dead_code parse_unix_ps cross-platform tests", + "ranked": [ + "cfg-cross-platform-dead-code", + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCA9873DBA4C4PTQZN4Q", + "id": "01M1X6H38BVNBPKPBDJEFXVC3J", + "kind": "memory", + "score": 0.9999476671218872, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + }, + { + "expansion_handle": "memory:01M1X6FC7XP8VRJ8NHE6MZGS3H", + "id": "01M1X6H38BD5RGM1PK0M04RNPT", + "kind": "memory", + "score": 0.9764312505722046, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 739.3448000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1518, + "mcp_result_bytes": 1625, + "wire_bytes": 1661, + "reported_used_tokens": 1625, + "working_set_bytes": 283713536, + "peak_working_set_bytes": 284626944 + }, + { + "query": "how do I keep a function that is only called on Unix from triggering dead_code warnings on Windows?", + "ranked": [ + "cfg-cross-platform-dead-code" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCA9873DBA4C4PTQZN4Q", + "id": "01M1X6H3ZV9S13TCNS0KJ4DJT8", + "kind": "memory", + "score": 0.9988092184066772, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 896.6256999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 939, + "reported_used_tokens": 903, + "working_set_bytes": 283844608, + "peak_working_set_bytes": 284758016 + }, + { + "query": "deadlocking a Rust mutex in integration tests", + "ranked": [ + "mutex-deadlock-user-brain-disabled", + "testing-serial-vs-parallel", + "kimetsu-query-stemming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBK795VCKKWK7JEKPT4J", + "id": "01M1X6H4V99SV6VXYMXNYHXKH3", + "kind": "memory", + "score": 0.9997490048408508, + "summary": "project:fact - [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure — `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + }, + { + "expansion_handle": "memory:01M1X6FXZM3KNGPDANSTPNKTQR", + "id": "01M1X6H4VA0M70KPARD3EQKYQ5", + "kind": "memory", + "score": 0.9057517647743224, + "summary": "project:fact - [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`)." + }, + { + "expansion_handle": "memory:01M1X6G5KQ8DP47BPMHQSWGBGS", + "id": "01M1X6H4VA0AYRHCAVJXJ15R85", + "kind": "memory", + "score": 0.4889622032642365, + "summary": "project:fact - [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 842.9918, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1930, + "mcp_result_bytes": 2063, + "wire_bytes": 2099, + "reported_used_tokens": 2063, + "working_set_bytes": 283959296, + "peak_working_set_bytes": 284872704 + }, + { + "query": "benchmarking retrieval quality across embedders", + "ranked": [ + "kimetsu-bench-remote-embedder-singleton", + "onnx-quantization-drift", + "cargo-feature-unification-embeddings" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G5NP21NSKR0TBJPHB80K", + "id": "01M1X6H5NNY518CEM55NHAMSXB", + "kind": "memory", + "score": 0.988014280796051, + "summary": "project:fact - [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval." + }, + { + "expansion_handle": "memory:01M1X6FFZW8Z92NY5W7JM4TSR9", + "id": "01M1X6H5NNV5V9ZD9RJZHTD95B", + "kind": "memory", + "score": 0.985597550868988, + "summary": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals — cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + }, + { + "expansion_handle": "memory:01M1X6FBR4K1EDGA6RZKFR1659", + "id": "01M1X6H5NN1SDQ4B8NN25Q9587", + "kind": "memory", + "score": 0.5341982841491699, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 714.5283000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2537, + "mcp_result_bytes": 2658, + "wire_bytes": 2694, + "reported_used_tokens": 2658, + "working_set_bytes": 284327936, + "peak_working_set_bytes": 285237248 + }, + { + "query": "process memory working set RSS peak measurement Windows", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 913.0407, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 284942336, + "peak_working_set_bytes": 285835264 + }, + { + "query": "cloning a git repository server-side into a managed checkout", + "ranked": [ + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBMAHT4JHT1S9T1YAQCN", + "id": "01M1X6H78VAZZNS7W7ZPYW7SG4", + "kind": "memory", + "score": 0.9466677904129028, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 754.2299999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1261, + "mcp_result_bytes": 1342, + "wire_bytes": 1378, + "reported_used_tokens": 1342, + "working_set_bytes": 285155328, + "peak_working_set_bytes": 286060544 + }, + { + "query": "SigV4 signing HTTP requests in Rust", + "ranked": [ + "aws-presigned-urls", + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G25DB3HRGVQYHSTK90X7", + "id": "01M1X6H804WRQ6AB1Z7C4R3MWG", + "kind": "memory", + "score": 0.9992632269859314, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time — clock skew > 15 minutes causes `RequestTimeTooSkewed`." + }, + { + "expansion_handle": "memory:01M1X6FBYZ5JT46Z87176E5AER", + "id": "01M1X6H80476X9H746D7FNQJMJ", + "kind": "memory", + "score": 0.9991399049758912, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1X6FBSFGZG953DWGDC5CQJC", + "id": "01M1X6H804HPHAYJ84PAZDTV4M", + "kind": "memory", + "score": 0.9803794622421264, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 0.5, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 831.2456000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2840, + "mcp_result_bytes": 2985, + "wire_bytes": 3021, + "reported_used_tokens": 2985, + "working_set_bytes": 285597696, + "peak_working_set_bytes": 286494720 + }, + { + "query": "cargo test --workspace feature flag changes broke my unit tests", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-dev-dep-leak", + "ci-flaky-quarantine" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBR4K1EDGA6RZKFR1659", + "id": "01M1X6H8T7GP5NBPPWY50TBV7D", + "kind": "memory", + "score": 0.997899889945984, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X6FCQ2KKER2KYG7B2280XC", + "id": "01M1X6H8T7W48TZ7F3JH4C4241", + "kind": "memory", + "score": 0.9901249408721924, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + }, + { + "expansion_handle": "memory:01M1X6G569HH9Q1FNPCPH6GQV2", + "id": "01M1X6H8T772FJZS77V0AT51A8", + "kind": "memory", + "score": 0.835382342338562, + "summary": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal — a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 745.3519, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2383, + "mcp_result_bytes": 2504, + "wire_bytes": 2540, + "reported_used_tokens": 2504, + "working_set_bytes": 285638656, + "peak_working_set_bytes": 286552064 + }, + { + "query": "how do I make pasta carbonara?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 785.6972, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 285655040, + "peak_working_set_bytes": 286568448 + }, + { + "query": "what is the offside rule in football?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 980.4788, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 285659136, + "peak_working_set_bytes": 286568448 + }, + { + "query": "best way to train for a half marathon", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 993.1735, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 285663232, + "peak_working_set_bytes": 286584832 + }, + { + "query": "my test passes when I run it alone but fails under cargo test --workspace", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBR4K1EDGA6RZKFR1659", + "id": "01M1X6HC7T7YE5M5KC8S6KBCYT", + "kind": "memory", + "score": 0.9907942414283752, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X6FCQ2KKER2KYG7B2280XC", + "id": "01M1X6HC7TRDATY8ZNVNHXEP3Z", + "kind": "memory", + "score": 0.986136794090271, + "summary": "project:fact - [2026-09-07] [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 915.4825, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1863, + "mcp_result_bytes": 1966, + "wire_bytes": 2002, + "reported_used_tokens": 1966, + "working_set_bytes": 286117888, + "peak_working_set_bytes": 287035392 + }, + { + "query": "all the project tests started hanging forever after I added my new test", + "ranked": [ + "cargo-feature-unification-embeddings", + "tokio-runtime-in-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBR4K1EDGA6RZKFR1659", + "id": "01M1X6HD4FGZBY2SJ7F73QJQMF", + "kind": "memory", + "score": 0.774284839630127, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X6FSH3ZJYWBSJKZKZYY4Z4", + "id": "01M1X6HD4FQCJHNCZRCVC3MCTH", + "kind": "memory", + "score": 0.33030807971954346, + "summary": "project:fact - [2026-09-07] [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 866.3612, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1763, + "mcp_result_bytes": 1874, + "wire_bytes": 1910, + "reported_used_tokens": 1874, + "working_set_bytes": 286273536, + "peak_working_set_bytes": 287182848 + }, + { + "query": "my integration test silently wrote memories into my real home brain instead of the temp workspace", + "ranked": [ + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC1PYTV8EEQVV2FCY4FV", + "id": "01M1X6HDZJFCD8J0BX1X61SH8X", + "kind": "memory", + "score": 0.9922831654548644, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 868.7071, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 780, + "mcp_result_bytes": 861, + "wire_bytes": 897, + "reported_used_tokens": 861, + "working_set_bytes": 286330880, + "peak_working_set_bytes": 287248384 + }, + { + "query": "where should the env-var opt-out check live for a cleanup feature triggered from a hot code path", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC0TBZ3RC6PJTY71XESF", + "id": "01M1X6HETSN1YHJCHKNESYX2FP", + "kind": "memory", + "score": 0.9952055215835572, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 931.7883999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 761, + "mcp_result_bytes": 842, + "wire_bytes": 878, + "reported_used_tokens": 842, + "working_set_bytes": 286371840, + "peak_working_set_bytes": 287289344 + }, + { + "query": "the brain database file stays huge on Windows even after deleting most rows", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 975.6314, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 286408704, + "peak_working_set_bytes": 287330304 + }, + { + "query": "re-importing the same exported memories file counts them as new instead of deduplicated", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC5XF447XNVWDTA1KX30", + "id": "01M1X6HGPSD7GEP0672904MTA4", + "kind": "memory", + "score": 0.9878425598144532, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount — both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 914.585, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 965, + "mcp_result_bytes": 1046, + "wire_bytes": 1082, + "reported_used_tokens": 1046, + "working_set_bytes": 286429184, + "peak_working_set_bytes": 287346688 + }, + { + "query": "a helper function only called on Unix at runtime fails the dead-code lint on the Windows build", + "ranked": [ + "cfg-cross-platform-dead-code", + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCA9873DBA4C4PTQZN4Q", + "id": "01M1X6HHK5YM472SMNNVDP00XY", + "kind": "memory", + "score": 0.9971064925193788, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + }, + { + "expansion_handle": "memory:01M1X6FC93AQB8S92QXHHFQGQ9", + "id": "01M1X6HHK56GCNCKN9Q5D9P8EB", + "kind": "memory", + "score": 0.427912950515747, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics — mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 882.0010000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1728, + "mcp_result_bytes": 1827, + "wire_bytes": 1863, + "reported_used_tokens": 1827, + "working_set_bytes": 286470144, + "peak_working_set_bytes": 287391744 + }, + { + "query": "the second Terminal-Bench trial always crashes even though the first one passes", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC3TT3SRMESXFHKJXD7R", + "id": "01M1X6HJER5XAK1037FRV7YF3P", + "kind": "memory", + "score": 0.9963042736053468, + "summary": "project:fact - [2026-09-07] [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 920.6225000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1038, + "mcp_result_bytes": 1119, + "wire_bytes": 1155, + "reported_used_tokens": 1119, + "working_set_bytes": 286498816, + "peak_working_set_bytes": 287412224 + }, + { + "query": "how does doctor tell a running MCP server process is older than the kimetsu binary on disk", + "ranked": [ + "kimetsu-daemon-lifecycle", + "process-start-time-cross-platform", + "mcp-env-propagation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G57BG9P4RZW0F69BHBK0", + "id": "01M1X6HKC6SCB6HE44J2SSM71Z", + "kind": "memory", + "score": 0.9985345602035522, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1X6FC7XP8VRJ8NHE6MZGS3H", + "id": "01M1X6HKC6PWVZN6PDXK0V0KKC", + "kind": "memory", + "score": 0.9438157677650452, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + }, + { + "expansion_handle": "memory:01M1X6G1Y3K75Y1EDB0JHXK3KF", + "id": "01M1X6HKC6Y8BWVN3HMBA8H1VV", + "kind": "memory", + "score": 0.33611738681793213, + "summary": "project:fact - [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment — changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 0.5, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 942.8453999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1936, + "mcp_result_bytes": 2061, + "wire_bytes": 2097, + "reported_used_tokens": 2061, + "working_set_bytes": 286527488, + "peak_working_set_bytes": 287444992 + }, + { + "query": "the self-update preflight needs the list of running kimetsu processes without re-running the OS query", + "ranked": [ + "windows-update-process-locking", + "kimetsu-daemon-lifecycle" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC93AQB8S92QXHHFQGQ9", + "id": "01M1X6HM8XRCT09CQQVTQM6SCJ", + "kind": "memory", + "score": 0.9972410202026368, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics — mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + }, + { + "expansion_handle": "memory:01M1X6G57BG9P4RZW0F69BHBK0", + "id": "01M1X6HM8XVMDGWVDB0HFMH7PV", + "kind": "memory", + "score": 0.8902595043182373, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 877.5347, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1658, + "mcp_result_bytes": 1757, + "wire_bytes": 1793, + "reported_used_tokens": 1757, + "working_set_bytes": 286531584, + "peak_working_set_bytes": 287444992 + }, + { + "query": "parsing the WMI DMTF CreationDate timestamp into epoch seconds without extra crates", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC7XP8VRJ8NHE6MZGS3H", + "id": "01M1X6HN4AE4RFYJ1XG878AB0F", + "kind": "memory", + "score": 0.9258026480674744, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 931.8521, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 924, + "mcp_result_bytes": 1013, + "wire_bytes": 1049, + "reported_used_tokens": 1013, + "working_set_bytes": 286564352, + "peak_working_set_bytes": 287477760 + }, + { + "query": "calling Bedrock InvokeModel from blocking reqwest without the aws sdk", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking", + "aws-region-resolution", + "aws-retry-throttling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBSFGZG953DWGDC5CQJC", + "id": "01M1X6HP1G7PE64KKD8BAA1EWV", + "kind": "memory", + "score": 0.9991798996925354, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X6FBYZ5JT46Z87176E5AER", + "id": "01M1X6HP1GFPF1X4BP9B3BHM9X", + "kind": "memory", + "score": 0.999082326889038, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1X6G23CEN8Y7G8DV4SNBYQB", + "id": "01M1X6HP1GTPB9SNR05J5Z7058", + "kind": "memory", + "score": 0.8391201496124268, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X6G24BKQF5H1P4JPDZJ0GV", + "id": "01M1X6HP1GBK6P69RXER575KAQ", + "kind": "memory", + "score": 0.4906356632709503, + "summary": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with ±25% jitter." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 899.4729, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3330, + "mcp_result_bytes": 3509, + "wire_bytes": 3545, + "reported_used_tokens": 3509, + "working_set_bytes": 286646272, + "peak_working_set_bytes": 287563776 + }, + { + "query": "how do I rotate the encryption key protecting the kimetsu brain database", + "ranked": [ + "kimetsu-eval-fixture-shape" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G5PR2N167V5D1K95D9WP", + "id": "01M1X6HPY54XNEG505QWNTAXGS", + "kind": "memory", + "score": 0.8046634197235107, + "summary": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` — a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases)." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 930.4038, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 817, + "mcp_result_bytes": 942, + "wire_bytes": 978, + "reported_used_tokens": 942, + "working_set_bytes": 286703616, + "peak_working_set_bytes": 287612928 + }, + { + "query": "which tokio runtime worker-thread settings does the kimetsu MCP server use", + "ranked": [ + "tokio-blocking-in-async", + "tokio-runtime-in-tests", + "mcp-stdout-protocol" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FSG1SR2HKPVJWCKTW0XB", + "id": "01M1X6HQTQ58XRTRCAH5A1VZXN", + "kind": "memory", + "score": 0.9973159432411194, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking — never call rusqlite directly from an async fn without spawn_blocking." + }, + { + "expansion_handle": "memory:01M1X6FSH3ZJYWBSJKZKZYY4Z4", + "id": "01M1X6HQTQSKJ9JYRW9KD8MRPQ", + "kind": "memory", + "score": 0.8583173155784607, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + }, + { + "expansion_handle": "memory:01M1X6G1W0ZBD86TBRPQESXKTX", + "id": "01M1X6HQTQ2E9YAPEFKN312MG3", + "kind": "memory", + "score": 0.8141786456108093, + "summary": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 926.1415, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1847, + "mcp_result_bytes": 1972, + "wire_bytes": 2008, + "reported_used_tokens": 1972, + "working_set_bytes": 286994432, + "peak_working_set_bytes": 287907840 + }, + { + "query": "how does kimetsu sync memories between two machines over the network", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 862.3269, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 287379456, + "peak_working_set_bytes": 288292864 + }, + { + "query": "recovering a corrupted usearch ANN index after a power loss", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 820.8115, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 287379456, + "peak_working_set_bytes": 288292864 + }, + { + "query": "what postgres schema should I use to store kimetsu memories", + "ranked": [ + "kimetsu-memory-scopes", + "testing-fixture-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G596BN9AAJMY7GJTXTHF", + "id": "01M1X6HTCF8BTHDDFVA47CJ94A", + "kind": "memory", + "score": 0.9890244603157043, + "summary": "project:fact - [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available — if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope." + }, + { + "expansion_handle": "memory:01M1X6G1V5C0B2FRN6AHK9JQJW", + "id": "01M1X6HTCF1KYZHVTQSD7M9DPT", + "kind": "memory", + "score": 0.8922504782676697, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 906.2177, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1389, + "mcp_result_bytes": 1488, + "wire_bytes": 1524, + "reported_used_tokens": 1488, + "working_set_bytes": 287420416, + "peak_working_set_bytes": 288321536 + }, + { + "query": "the whole CI job just froze forever with no failure output after my latest test PR", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 918.714, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 287436800, + "peak_working_set_bytes": 288354304 + }, + { + "query": "running the test suite left junk state in my home directory", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 919.5084999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 287444992, + "peak_working_set_bytes": 288362496 + }, + { + "query": "I deleted a bunch of old rows but the file on disk is still the same size", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 885.6174, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 287547392, + "peak_working_set_bytes": 288464896 + }, + { + "query": "adding one new crate quietly changed how the whole workspace builds", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-lockfile-drift", + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBR4K1EDGA6RZKFR1659", + "id": "01M1X6HXXSKYJGW8YM7F0G8H2Q", + "kind": "memory", + "score": 0.9941080808639526, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X6FCN11BZ03K13K0EZ96KQ", + "id": "01M1X6HXXSWZ2A955VE052ZYG6", + "kind": "memory", + "score": 0.9717232584953308, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this — it errors on any lockfile diff." + }, + { + "expansion_handle": "memory:01M1X6FCQ2KKER2KYG7B2280XC", + "id": "01M1X6HXXSVAK689FBGG1HNQQ9", + "kind": "memory", + "score": 0.9183088541030884, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 914.9649, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2374, + "mcp_result_bytes": 2495, + "wire_bytes": 2531, + "reported_used_tokens": 2495, + "working_set_bytes": 287559680, + "peak_working_set_bytes": 288481280 + }, + { + "query": "we cannot pull an async runtime into the agent just to talk to AWS", + "ranked": [ + "tokio-blocking-in-async", + "tokio-runtime-in-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FSG1SR2HKPVJWCKTW0XB", + "id": "01M1X6HYTGQZ1X9E4X277WTF6D", + "kind": "memory", + "score": 0.7520647644996643, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking — never call rusqlite directly from an async fn without spawn_blocking." + }, + { + "expansion_handle": "memory:01M1X6FSH3ZJYWBSJKZKZYY4Z4", + "id": "01M1X6HYTG3VR7EJM29VKJDQRA", + "kind": "memory", + "score": 0.7233642935752869, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 929.2574999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1369, + "mcp_result_bytes": 1476, + "wire_bytes": 1512, + "reported_used_tokens": 1476, + "working_set_bytes": 287580160, + "peak_working_set_bytes": 288489472 + }, + { + "query": "users should be able to tell which build variant they installed from the version output", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 958.9667, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 287916032, + "peak_working_set_bytes": 288833536 + }, + { + "query": "what gotchas should I expect writing process-inspection code that works on both Windows and Unix?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 865.5604999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 287985664, + "peak_working_set_bytes": 288907264 + }, + { + "query": "why might tests behave differently on my machine than in the full CI run?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 875.4398, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288100352, + "peak_working_set_bytes": 289017856 + }, + { + "query": "what do I need to know before wiring kimetsu into a brand new host agent?", + "ranked": [ + "bridge-target-enum-seams", + "kimetsu-daemon-lifecycle", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBV74YJ9W6XP288GKM7H", + "id": "01M1X6J2C5NS3C5EANNAQ44AAT", + "kind": "memory", + "score": 0.9741999506950378, + "summary": "project:fact - [2026-09-07] [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + }, + { + "expansion_handle": "memory:01M1X6G57BG9P4RZW0F69BHBK0", + "id": "01M1X6J2C5KMEW2QC3V7BE4F2E", + "kind": "memory", + "score": 0.9637662768363952, + "summary": "project:fact - [2026-09-07] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1X6FBP8RS75FNREKQZ0WD3Q", + "id": "01M1X6J2C5MGTSVNE22WSATRS9", + "kind": "memory", + "score": 0.4149944484233856, + "summary": "project:fact - [2026-09-07] [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 0.6666666666666666, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 934.7820999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2390, + "mcp_result_bytes": 2555, + "wire_bytes": 2591, + "reported_used_tokens": 2555, + "working_set_bytes": 288124928, + "peak_working_set_bytes": 289042432 + }, + { + "query": "tell me everything relevant to running kimetsu against AWS", + "ranked": [ + "kimetsu-mrr-metric", + "aws-credentials-chain", + "cargo-feature-unification-embeddings", + "kimetsu-eval-fixture-shape" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G5QTTFD19FAJ8MZX4ZPC", + "id": "01M1X6J3A2YJS39ZM3XDA74KK2", + "kind": "memory", + "score": 0.984548270702362, + "summary": "project:fact - [tags: kimetsu bench mrr recall metrics evaluation] kimetsu bench reports MRR (Mean Reciprocal Rank) and Recall@K. MRR is 1/rank_of_first_relevant_result, averaged across cases; it penalizes models that rank the correct answer 2nd or 3rd. Recall@K is the fraction of cases where at least one relevant answer appears in the top K." + }, + { + "expansion_handle": "memory:01M1X6G225TRC4EVAFGXT9RHRX", + "id": "01M1X6J3A27V7JB55BC2TCC126", + "kind": "memory", + "score": 0.9737622141838074, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + }, + { + "expansion_handle": "memory:01M1X6FBR4K1EDGA6RZKFR1659", + "id": "01M1X6J3A22EEJ3N0YNEHHNCC9", + "kind": "memory", + "score": 0.9726329445838928, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X6G5PR2N167V5D1K95D9WP", + "id": "01M1X6J3A26YA79ZCZNS854CRP", + "kind": "memory", + "score": 0.9641559720039368, + "summary": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` — a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases)." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 947.1441000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2883, + "mcp_result_bytes": 3066, + "wire_bytes": 3102, + "reported_used_tokens": 3066, + "working_set_bytes": 288325632, + "peak_working_set_bytes": 289234944 + }, + { + "query": "ingesting a cloned repo when the brain lives under a different root", + "ranked": [ + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBMAHT4JHT1S9T1YAQCN", + "id": "01M1X6J46V18VGX2C1N1QYCW76", + "kind": "memory", + "score": 0.9995300769805908, + "summary": "project:fact - [2026-09-07] [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 866.3001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1274, + "mcp_result_bytes": 1355, + "wire_bytes": 1391, + "reported_used_tokens": 1355, + "working_set_bytes": 288415744, + "peak_working_set_bytes": 289325056 + }, + { + "query": "streamable-http transport entry for openclaw.json with a bearer token", + "ranked": [ + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBP8RS75FNREKQZ0WD3Q", + "id": "01M1X6J5299E37P91S64JF8QFK", + "kind": "memory", + "score": 0.9921918511390686, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 904.1683, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 996, + "mcp_result_bytes": 1125, + "wire_bytes": 1161, + "reported_used_tokens": 1125, + "working_set_bytes": 288481280, + "peak_working_set_bytes": 289398784 + }, + { + "query": "serializing ingests with a tokio mutex to avoid checkout races", + "ranked": [ + "remote-ingest-split-roots", + "testing-serial-vs-parallel", + "tokio-select-cancellation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBMAHT4JHT1S9T1YAQCN", + "id": "01M1X6J5YDE3ZBJ4G8K5Q65FT6", + "kind": "memory", + "score": 0.9795480966567992, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1X6FXZM3KNGPDANSTPNKTQR", + "id": "01M1X6J5YDW14K5TJN3X4V4X6F", + "kind": "memory", + "score": 0.9425267577171326, + "summary": "project:fact - [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`)." + }, + { + "expansion_handle": "memory:01M1X6FSJ8K553R8HRDXZWVYCK", + "id": "01M1X6J5YDWPG04X183XDXPVPP", + "kind": "memory", + "score": 0.5619664192199707, + "summary": "project:fact - [tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 929.1194, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2376, + "mcp_result_bytes": 2493, + "wire_bytes": 2529, + "reported_used_tokens": 2493, + "working_set_bytes": 288518144, + "peak_working_set_bytes": 289439744 + }, + { + "query": "percent-encoding the colon in the bedrock model id for the invoke URL", + "ranked": [ + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBSFGZG953DWGDC5CQJC", + "id": "01M1X6J6VBQ34GZ0Q3A23EDNY5", + "kind": "memory", + "score": 0.8341025710105896, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 868.0025, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1204, + "mcp_result_bytes": 1293, + "wire_bytes": 1329, + "reported_used_tokens": 1293, + "working_set_bytes": 288518144, + "peak_working_set_bytes": 289439744 + }, + { + "query": "deduplicating re-imported memories against pre-existing ids", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC5XF447XNVWDTA1KX30", + "id": "01M1X6J7PNJ6T6GP32XYW836W1", + "kind": "memory", + "score": 0.9991393089294434, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount — both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 940.609, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 966, + "mcp_result_bytes": 1047, + "wire_bytes": 1083, + "reported_used_tokens": 1047, + "working_set_bytes": 288567296, + "peak_working_set_bytes": 289464320 + }, + { + "query": "parsing DMTF datetimes", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC7XP8VRJ8NHE6MZGS3H", + "id": "01M1X6J8KWZPQNB848PV2JP8V0", + "kind": "memory", + "score": 0.9934942126274108, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 689.5605, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 924, + "mcp_result_bytes": 1013, + "wire_bytes": 1049, + "reported_used_tokens": 1013, + "working_set_bytes": 288641024, + "peak_working_set_bytes": 289521664 + }, + { + "query": "how should install derive a stable identifier from the git remote URL?", + "ranked": [ + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBP8RS75FNREKQZ0WD3Q", + "id": "01M1X6J99S18EGF93ZNNMH4WXB", + "kind": "memory", + "score": 0.98285174369812, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 901.1454, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 995, + "mcp_result_bytes": 1124, + "wire_bytes": 1160, + "reported_used_tokens": 1124, + "working_set_bytes": 288641024, + "peak_working_set_bytes": 289554432 + }, + { + "query": "the secret token must not end up written into the host config file", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 944.4225, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288673792, + "peak_working_set_bytes": 289591296 + }, + { + "query": "keep the cleanup logic unit-testable without touching environment variables", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 937.7613, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288673792, + "peak_working_set_bytes": 289591296 + }, + { + "query": "how do we stop the server from cloning arbitrary repos clients request?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 876.9009, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288690176, + "peak_working_set_bytes": 289607680 + }, + { + "query": "make sure a wrong guess about a host plugin API never breaks that host", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBWBQK6S520BBWHP1EHF", + "id": "01M1X6JCVVWR9N9F3XPMPTE6KE", + "kind": "memory", + "score": 0.928434193134308, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 783.2337, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 803, + "mcp_result_bytes": 892, + "wire_bytes": 928, + "reported_used_tokens": 892, + "working_set_bytes": 288694272, + "peak_working_set_bytes": 289615872 + }, + { + "query": "which wire-format trick lets us reuse the existing Anthropic request builder for AWS?", + "ranked": [ + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBSFGZG953DWGDC5CQJC", + "id": "01M1X6JDMV5P4YTHYBN2ZCHXFK", + "kind": "memory", + "score": 0.9748817682266236, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 979.3254999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1203, + "mcp_result_bytes": 1292, + "wire_bytes": 1328, + "reported_used_tokens": 1292, + "working_set_bytes": 288808960, + "peak_working_set_bytes": 289726464 + }, + { + "query": "the self-update froze because something was still holding the executable", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 902.888, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288837632, + "peak_working_set_bytes": 289746944 + }, + { + "query": "our notes about the extension API turned out wrong once we read the actual repo", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 904.628, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288862208, + "peak_working_set_bytes": 289771520 + }, + { + "query": "half the benchmark trials die right after the first one finishes", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 863.1566, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288878592, + "peak_working_set_bytes": 289796096 + }, + { + "query": "I need this parser visible to tests on every OS even though only one OS calls it", + "ranked": [ + "cfg-cross-platform-dead-code" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCA9873DBA4C4PTQZN4Q", + "id": "01M1X6JH6XPK0JVG1WK82QXGGS", + "kind": "memory", + "score": 0.36490198969841, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 889.8193, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 939, + "reported_used_tokens": 903, + "working_set_bytes": 288927744, + "peak_working_set_bytes": 289832960 + }, + { + "query": "the config file content refuses to parse even though the TOML looks valid", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC706HRRZF8YZH8DDCVM", + "id": "01M1X6JJ2HYGNDG6G6AMP0QFF9", + "kind": "memory", + "score": 0.6614054441452026, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 934.3593999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 733, + "mcp_result_bytes": 814, + "wire_bytes": 850, + "reported_used_tokens": 814, + "working_set_bytes": 288964608, + "peak_working_set_bytes": 289882112 + }, + { + "query": "the remote server must refresh its checkout before answering file queries", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 948.8135, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288968704, + "peak_working_set_bytes": 289886208 + }, + { + "query": "tests must not climb to a parent git repository when resolving project paths", + "ranked": [ + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FC1PYTV8EEQVV2FCY4FV", + "id": "01M1X6JKXAHQ1CERZ068GJRB0E", + "kind": "memory", + "score": 0.9839988350868224, + "summary": "project:fact - [2026-09-07] [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 922.1287, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 794, + "mcp_result_bytes": 875, + "wire_bytes": 911, + "reported_used_tokens": 875, + "working_set_bytes": 289009664, + "peak_working_set_bytes": 289918976 + }, + { + "query": "how do I test request signing deterministically when timestamps change every run?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 904.8479, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 289091584, + "peak_working_set_bytes": 290004992 + }, + { + "query": "adding a new variant to the host target enum - which places will I forget to update?", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FBV74YJ9W6XP288GKM7H", + "id": "01M1X6JNPEYPJZRYCJJPQWRNPY", + "kind": "memory", + "score": 0.885076105594635, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 944.1534, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1058, + "mcp_result_bytes": 1139, + "wire_bytes": 1175, + "reported_used_tokens": 1139, + "working_set_bytes": 289091584, + "peak_working_set_bytes": 290004992 + }, + { + "query": "how do I enable GPU acceleration for kimetsu embedding inference", + "ranked": [ + "mcp-tool-timeouts", + "kimetsu-proactive-hooks" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G1X1XGE9MM05BANVE5DQ", + "id": "01M1X6JPKYC1HJS7MHVY7DBYXZ", + "kind": "memory", + "score": 0.9826309084892272, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking — in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize — keep it in a process-global `OnceLock`)." + }, + { + "expansion_handle": "memory:01M1X6G5B7FXBAABGSVRFWFCX6", + "id": "01M1X6JPKYWKH4CB1T9JF4BXSJ", + "kind": "memory", + "score": 0.8807981610298157, + "summary": "project:fact - [tags: kimetsu proactive hooks context injection] kimetsu's proactive context injection runs before each agent turn (pre-turn hook) and injects relevant memories into the system prompt prefix. The hook invocation adds latency to the first token: embedding inference + vector search + reranking + context formatting. On a cold start, this can be 1-3 seconds." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 927.5293999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1377, + "mcp_result_bytes": 1476, + "wire_bytes": 1512, + "reported_used_tokens": 1476, + "working_set_bytes": 289161216, + "peak_working_set_bytes": 290054144 + }, + { + "query": "how do I throttle kimetsu API spend per month", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 877.8575, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 289173504, + "peak_working_set_bytes": 290091008 + }, + { + "query": "can the kimetsu brain database be stored in S3 instead of on disk", + "ranked": [ + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G25DB3HRGVQYHSTK90X7", + "id": "01M1X6JRCGWY24H2KBGYMMT6K3", + "kind": "memory", + "score": 0.38596054911613464, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time — clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 828.8815000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 875, + "mcp_result_bytes": 956, + "wire_bytes": 992, + "reported_used_tokens": 956, + "working_set_bytes": 289177600, + "peak_working_set_bytes": 290095104 + }, + { + "query": "how do I plug a custom tokenizer into the FTS index", + "ranked": [ + "sqlite-fts5-tokenizer" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCF4MJWVBEQZXZS69JE5", + "id": "01M1X6JS69MNRAG771GHNP8MQS", + "kind": "memory", + "score": 0.9691632390022278, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 909.2773, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 671, + "mcp_result_bytes": 756, + "wire_bytes": 792, + "reported_used_tokens": 756, + "working_set_bytes": 289206272, + "peak_working_set_bytes": 290111488 + }, + { + "query": "what should I check when kimetsu behaves differently on Windows than on Linux?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 902.5798000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 289452032, + "peak_working_set_bytes": 290365440 + }, + { + "query": "what are the moving parts of the kimetsu remote deployment story?", + "ranked": [ + "kimetsu-write-tools-gate", + "ci-secrets-masking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G5C7NRN6660DTHDS58ZS", + "id": "01M1X6JTZ6A266PDJ0ZKJ9QJQM", + "kind": "memory", + "score": 0.9729357361793518, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level — disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1X6G54DHRYVMZCDFFVTEDFC", + "id": "01M1X6JTZ6PZ11E70J0RCTVBYK", + "kind": "memory", + "score": 0.8412115573883057, + "summary": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output — but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 915.7452000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1410, + "mcp_result_bytes": 1509, + "wire_bytes": 1546, + "reported_used_tokens": 1509, + "working_set_bytes": 289816576, + "peak_working_set_bytes": 290734080 + }, + { + "query": "which lessons cover guarding behavior behind environment variables?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 813.6737, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 289861632, + "peak_working_set_bytes": 290766848 + }, + { + "query": "SQLite BUSY error under concurrent writes", + "ranked": [ + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCB5829A6ZF2C5FB9VZW", + "id": "01M1X6JWN02H6MVWFR8T0SSTYW", + "kind": "memory", + "score": 0.9978362917900084, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 771.9989, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 898, + "mcp_result_bytes": 979, + "wire_bytes": 1016, + "reported_used_tokens": 979, + "working_set_bytes": 289902592, + "peak_working_set_bytes": 290795520 + }, + { + "query": "SQLite WAL mode breaks when the database is on a network share", + "ranked": [ + "sqlite-wal-network-drive", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCDYEQS880XHFGF7HHKN", + "id": "01M1X6JXD48TPYTJXDTF6V0C3R", + "kind": "memory", + "score": 0.999302864074707, + "summary": "project:fact - [2026-09-07] [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + }, + { + "expansion_handle": "memory:01M1X6FCB5829A6ZF2C5FB9VZW", + "id": "01M1X6JXD4C70EYCFDSRPV0SBB", + "kind": "memory", + "score": 0.9966553449630736, + "summary": "project:fact - [2026-09-07] [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 929.3546, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1448, + "mcp_result_bytes": 1547, + "wire_bytes": 1584, + "reported_used_tokens": 1547, + "working_set_bytes": 289923072, + "peak_working_set_bytes": 290836480 + }, + { + "query": "my SQLite WAL database causes SQLITE_IOERR_LOCK on a mapped drive", + "ranked": [ + "sqlite-wal-network-drive" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCDYEQS880XHFGF7HHKN", + "id": "01M1X6JYA9XQPAQZRX0AA359H8", + "kind": "memory", + "score": 0.99892657995224, + "summary": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 951.6091, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 748, + "mcp_result_bytes": 829, + "wire_bytes": 866, + "reported_used_tokens": 829, + "working_set_bytes": 289996800, + "peak_working_set_bytes": 290906112 + }, + { + "query": "FTS5 tokenizer configuration for Rust identifiers with underscores", + "ranked": [ + "sqlite-fts5-tokenizer", + "kimetsu-query-stemming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCF4MJWVBEQZXZS69JE5", + "id": "01M1X6JZ87V6R64T0AVMFQ6CSP", + "kind": "memory", + "score": 0.998104453086853, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + }, + { + "expansion_handle": "memory:01M1X6G5KQ8DP47BPMHQSWGBGS", + "id": "01M1X6JZ873WVD73RAH18B00K0", + "kind": "memory", + "score": 0.7023860812187195, + "summary": "project:fact - [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 895.6216999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1212, + "mcp_result_bytes": 1331, + "wire_bytes": 1368, + "reported_used_tokens": 1331, + "working_set_bytes": 289996800, + "peak_working_set_bytes": 290906112 + }, + { + "query": "I switched the FTS5 tokenizer but search stopped returning results", + "ranked": [ + "sqlite-fts5-tokenizer" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCF4MJWVBEQZXZS69JE5", + "id": "01M1X6K045WH0BE4XB90XC3F5W", + "kind": "memory", + "score": 0.8194089531898499, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 917.5824, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 670, + "mcp_result_bytes": 755, + "wire_bytes": 792, + "reported_used_tokens": 755, + "working_set_bytes": 290025472, + "peak_working_set_bytes": 290934784 + }, + { + "query": "optimal SQLite page size for storing embedding vectors", + "ranked": [ + "sqlite-page-size", + "onnx-dim-mismatch", + "onnx-cosine-vs-dot" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCG8Q282R1BJVYTVPP5K", + "id": "01M1X6K10MQVNTPPSAR8KK1857", + "kind": "memory", + "score": 0.9990121126174928, + "summary": "project:fact - [tags: sqlite page_size performance rusqlite] SQLite's default page_size is 4096 bytes. For a write-heavy brain database with large BLOB payloads (embedding vectors), raising page_size to 16384 reduces fragmentation and improves sequential scan throughput. `PRAGMA page_size = 16384;` must be set BEFORE the first table is created — changing it on an existing database requires a VACUUM afterward to rebuild all pages." + }, + { + "expansion_handle": "memory:01M1X6FG3R3G9YXMC4MA14S64D", + "id": "01M1X6K10NDPPSAQP3P9T7B18J", + "kind": "memory", + "score": 0.9881643056869508, + "summary": "project:fact - [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results — the ANN index shape mismatch isn't always caught at runtime." + }, + { + "expansion_handle": "memory:01M1X6FG2RYQFQGDVARXZ18F3S", + "id": "01M1X6K10N01MM8MHCZ6RQ4Y1W", + "kind": "memory", + "score": 0.9425415992736816, + "summary": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing — double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 846.2722, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1860, + "mcp_result_bytes": 1977, + "wire_bytes": 2014, + "reported_used_tokens": 1977, + "working_set_bytes": 290025472, + "peak_working_set_bytes": 290934784 + }, + { + "query": "ON DELETE CASCADE in SQLite does nothing — foreign keys not enforced", + "ranked": [ + "sqlite-foreign-keys-default-off" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCH5BG9VNPYFKJ7JGXMQ", + "id": "01M1X6K1VAMH16YRSSZDJVZKZ3", + "kind": "memory", + "score": 0.9996858835220336, + "summary": "project:fact - [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting — every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 943.2256, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 736, + "mcp_result_bytes": 817, + "wire_bytes": 854, + "reported_used_tokens": 817, + "working_set_bytes": 290025472, + "peak_working_set_bytes": 290934784 + }, + { + "query": "indexing a JSON metadata column in SQLite without a schema migration", + "ranked": [ + "sqlite-json1-extract", + "testing-fixture-drift", + "onnx-dim-mismatch", + "sqlite-partial-index" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCJ2T9Y1Q3VZYZTX8SGK", + "id": "01M1X6K2RSYA1MK74K00E9D49M", + "kind": "memory", + "score": 0.9955366849899292, + "summary": "project:fact - [tags: sqlite json1 json_extract rusqlite] SQLite's json1 extension (built in since 3.38.0) lets you index and query JSONB columns with `json_extract(col, '$.field')`. To create a partial index over a JSON field: `CREATE INDEX idx ON memories (json_extract(metadata, '$.scope')) WHERE json_extract(metadata, '$.scope') IS NOT NULL;`. Use `json_each` for array fields." + }, + { + "expansion_handle": "memory:01M1X6G1V5C0B2FRN6AHK9JQJW", + "id": "01M1X6K2RSTA7939KFYWF157TQ", + "kind": "memory", + "score": 0.8227390646934509, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + }, + { + "expansion_handle": "memory:01M1X6FG3R3G9YXMC4MA14S64D", + "id": "01M1X6K2RSYRAJ7KBWHDVNDWMH", + "kind": "memory", + "score": 0.38374292850494385, + "summary": "project:fact - [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results — the ANN index shape mismatch isn't always caught at runtime." + }, + { + "expansion_handle": "memory:01M1X6FCM3T8Z791CCFJARVY6N", + "id": "01M1X6K2RTQ9VNZR2RJCX40N91", + "kind": "memory", + "score": 0.3276048004627228, + "summary": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query — the planner uses the partial index only when the WHERE clause matches." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 893.6457, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2381, + "mcp_result_bytes": 2516, + "wire_bytes": 2553, + "reported_used_tokens": 2516, + "working_set_bytes": 290025472, + "peak_working_set_bytes": 290934784 + }, + { + "query": "prepare() vs prepare_cached() in rusqlite hot insert loop", + "ranked": [ + "sqlite-prepared-stmt-cache" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCK6KS0DJ66B33EVXK0C", + "id": "01M1X6K3N1B30BQ5TPMJE46C4P", + "kind": "memory", + "score": 0.9993672966957092, + "summary": "project:fact - [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 888.5539, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 689, + "mcp_result_bytes": 770, + "wire_bytes": 807, + "reported_used_tokens": 770, + "working_set_bytes": 290033664, + "peak_working_set_bytes": 290938880 + }, + { + "query": "speed up bulk memory ingest by caching SQL statements", + "ranked": [ + "sqlite-prepared-stmt-cache" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCK6KS0DJ66B33EVXK0C", + "id": "01M1X6K4GBBZ2W2DY73AH8GDKY", + "kind": "memory", + "score": 0.9823396801948548, + "summary": "project:fact - [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 872.8126000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 688, + "mcp_result_bytes": 769, + "wire_bytes": 806, + "reported_used_tokens": 769, + "working_set_bytes": 290037760, + "peak_working_set_bytes": 290942976 + }, + { + "query": "partial index on deleted_at IS NULL for faster active memory queries", + "ranked": [ + "sqlite-partial-index" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCM3T8Z791CCFJARVY6N", + "id": "01M1X6K5BPZ9AQPF7F6HQFAQJ7", + "kind": "memory", + "score": 0.9988954067230223, + "summary": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query — the planner uses the partial index only when the WHERE clause matches." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 890.4542, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 794, + "mcp_result_bytes": 875, + "wire_bytes": 912, + "reported_used_tokens": 875, + "working_set_bytes": 290037760, + "peak_working_set_bytes": 290942976 + }, + { + "query": "the brain query is slow because it scans all rows including soft-deleted ones", + "ranked": [ + "sqlite-partial-index" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCM3T8Z791CCFJARVY6N", + "id": "01M1X6K67J2358BDB92BY6GVJH", + "kind": "memory", + "score": 0.5760471224784851, + "summary": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query — the planner uses the partial index only when the WHERE clause matches." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 924.0066, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 793, + "mcp_result_bytes": 874, + "wire_bytes": 911, + "reported_used_tokens": 874, + "working_set_bytes": 290037760, + "peak_working_set_bytes": 290955264 + }, + { + "query": "Cargo.lock changed unexpectedly after adding a new workspace crate", + "ranked": [ + "cargo-lockfile-drift", + "cargo-feature-unification-embeddings", + "cargo-target-dir-sharing", + "cargo-patch-section" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCN11BZ03K13K0EZ96KQ", + "id": "01M1X6K74MA1H2KN3KEZYMNJCN", + "kind": "memory", + "score": 0.9991374015808104, + "summary": "project:fact - [2026-09-07] [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this — it errors on any lockfile diff." + }, + { + "expansion_handle": "memory:01M1X6FBR4K1EDGA6RZKFR1659", + "id": "01M1X6K74M4G8XA7037NSNDRDN", + "kind": "memory", + "score": 0.9968542456626892, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X6FCR2A1FS53B5VE4XJM4D", + "id": "01M1X6K74NCQEFE1ZST99QYHK2", + "kind": "memory", + "score": 0.9829630851745604, + "summary": "project:fact - [2026-09-07] [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps — use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + }, + { + "expansion_handle": "memory:01M1X6FCV2P1VH9VAZGCJQXBTY", + "id": "01M1X6K74NXWWRS79MQCE39JWA", + "kind": "memory", + "score": 0.9262890815734864, + "summary": "project:fact - [2026-09-07] [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace — including transitive deps — that depend on `my-crate`. Remove the patch before publishing." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 788.7819000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3010, + "mcp_result_bytes": 3153, + "wire_bytes": 3190, + "reported_used_tokens": 3153, + "working_set_bytes": 290041856, + "peak_working_set_bytes": 290959360 + }, + { + "query": "how do I prevent CI from accepting a modified lockfile silently?", + "ranked": [ + "cargo-lockfile-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCN11BZ03K13K0EZ96KQ", + "id": "01M1X6K7X3VMKA350CNWVV8Z54", + "kind": "memory", + "score": 0.9125379323959352, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this — it errors on any lockfile diff." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 910.0345, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 765, + "mcp_result_bytes": 846, + "wire_bytes": 883, + "reported_used_tokens": 846, + "working_set_bytes": 290045952, + "peak_working_set_bytes": 290963456 + }, + { + "query": "build.rs reruns on every incremental build even when nothing changed", + "ranked": [ + "cargo-build-script-rerun" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCNZ6AVH5M9HH3ZBVGJG", + "id": "01M1X6K8SKP3BXMJT7G4YMBEV9", + "kind": "memory", + "score": 0.9996689558029176, + "summary": "project:fact - [2026-09-07] [tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 939.6328000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 698, + "mcp_result_bytes": 779, + "wire_bytes": 816, + "reported_used_tokens": 779, + "working_set_bytes": 290045952, + "peak_working_set_bytes": 290963456 + }, + { + "query": "incremental cargo build is slow because build script runs every time", + "ranked": [ + "cargo-build-script-rerun" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCNZ6AVH5M9HH3ZBVGJG", + "id": "01M1X6K9PW6SANTR4C6MFGYQ77", + "kind": "memory", + "score": 0.9978280663490297, + "summary": "project:fact - [tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 918.0167, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 685, + "mcp_result_bytes": 766, + "wire_bytes": 803, + "reported_used_tokens": 766, + "working_set_bytes": 290050048, + "peak_working_set_bytes": 290963456 + }, + { + "query": "a dev-dependency is activating an embeddings feature in my production build", + "ranked": [ + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCQ2KKER2KYG7B2280XC", + "id": "01M1X6KAMMB2J3SXZRH2V2B36N", + "kind": "memory", + "score": 0.9944193959236144, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 974.3892, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 931, + "mcp_result_bytes": 1012, + "wire_bytes": 1049, + "reported_used_tokens": 1012, + "working_set_bytes": 290066432, + "peak_working_set_bytes": 290979840 + }, + { + "query": "how do I prevent a test-only feature from bleeding into the non-test compilation?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 895.5907, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 290070528, + "peak_working_set_bytes": 290983936 + }, + { + "query": "linker errors in target/ caused by antivirus holding the exe file", + "ranked": [ + "windows-file-locking-av", + "cargo-target-dir-sharing" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FFR991WAFW34D9V4G69V", + "id": "01M1X6KCEANFEF3TG102B468HH", + "kind": "memory", + "score": 0.9997633099555968, + "summary": "project:fact - [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + }, + { + "expansion_handle": "memory:01M1X6FCR2A1FS53B5VE4XJM4D", + "id": "01M1X6KCEA3ZF5GRSAGT4T8VPD", + "kind": "memory", + "score": 0.7463976740837097, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps — use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 901.1538, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1523, + "mcp_result_bytes": 1622, + "wire_bytes": 1659, + "reported_used_tokens": 1622, + "working_set_bytes": 290082816, + "peak_working_set_bytes": 290992128 + }, + { + "query": "Access is denied (os error 5) when linking on Windows — how do I fix this?", + "ranked": [ + "windows-file-locking-av" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FFR991WAFW34D9V4G69V", + "id": "01M1X6KDADTVWCW3R3600DYK09", + "kind": "memory", + "score": 0.9977193474769592, + "summary": "project:fact - [2026-09-07] [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 902.3153000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 769, + "mcp_result_bytes": 850, + "wire_bytes": 887, + "reported_used_tokens": 850, + "working_set_bytes": 290328576, + "peak_working_set_bytes": 291237888 + }, + { + "query": "incremental build broke with a type mismatch after switching branches", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCS3JS2284QAPZ6KRSQR", + "id": "01M1X6KE6JD4QT1KN133SGBXGR", + "kind": "memory", + "score": 0.7971777319908142, + "summary": "project:fact - [2026-09-07] [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 896.1498, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 890, + "mcp_result_bytes": 971, + "wire_bytes": 1008, + "reported_used_tokens": 971, + "working_set_bytes": 290459648, + "peak_working_set_bytes": 291373056 + }, + { + "query": "cargo reports a type error that references a type not in the codebase", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCS3JS2284QAPZ6KRSQR", + "id": "01M1X6KF2JNYGZAZB58E3CWZA7", + "kind": "memory", + "score": 0.7925198078155518, + "summary": "project:fact - [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 862.7095999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 877, + "mcp_result_bytes": 958, + "wire_bytes": 995, + "reported_used_tokens": 958, + "working_set_bytes": 290467840, + "peak_working_set_bytes": 291389440 + }, + { + "query": "compile fastembed at O2 in debug builds to avoid slow embedding inference", + "ranked": [ + "cargo-profile-override", + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCT0SQT9SH5HPBEB6HH8", + "id": "01M1X6KFXKF92ZK6E1BEPAZA55", + "kind": "memory", + "score": 0.9932281374931335, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1X6G1X1XGE9MM05BANVE5DQ", + "id": "01M1X6KFXKPB5T4X2BV2PP4BBB", + "kind": "memory", + "score": 0.987656831741333, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking — in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize — keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 918.1323, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1322, + "mcp_result_bytes": 1421, + "wire_bytes": 1458, + "reported_used_tokens": 1421, + "working_set_bytes": 290496512, + "peak_working_set_bytes": 291414016 + }, + { + "query": "override compilation profile for a single crate in a Cargo workspace", + "ranked": [ + "cargo-patch-section", + "cargo-profile-override", + "cargo-target-dir-sharing", + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCV2P1VH9VAZGCJQXBTY", + "id": "01M1X6KGTAF1D3HBVECR5E443E", + "kind": "memory", + "score": 0.9984123706817628, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace — including transitive deps — that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1X6FCT0SQT9SH5HPBEB6HH8", + "id": "01M1X6KGTAXXV6JPS1WZ28DYH5", + "kind": "memory", + "score": 0.9979992508888244, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1X6FCR2A1FS53B5VE4XJM4D", + "id": "01M1X6KGTA4T47SC1XCG4CWX4K", + "kind": "memory", + "score": 0.9956549406051636, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps — use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + }, + { + "expansion_handle": "memory:01M1X6FCQ2KKER2KYG7B2280XC", + "id": "01M1X6KGTAX04A4N7TMHDQ08SD", + "kind": "memory", + "score": 0.9820712208747864, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 0.5, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 928.2771, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2682, + "mcp_result_bytes": 2821, + "wire_bytes": 2858, + "reported_used_tokens": 2821, + "working_set_bytes": 290574336, + "peak_working_set_bytes": 291495936 + }, + { + "query": "[patch.crates-io] workspace dependency override", + "ranked": [ + "cargo-patch-section", + "cargo-lockfile-drift", + "cargo-dev-dep-leak", + "cargo-target-dir-sharing" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCV2P1VH9VAZGCJQXBTY", + "id": "01M1X6KHQFF6QETXP02VH59DNS", + "kind": "memory", + "score": 0.9999405145645142, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace — including transitive deps — that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1X6FCN11BZ03K13K0EZ96KQ", + "id": "01M1X6KHQF7X0CM7H48E867YGY", + "kind": "memory", + "score": 0.9975811243057252, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this — it errors on any lockfile diff." + }, + { + "expansion_handle": "memory:01M1X6FCQ2KKER2KYG7B2280XC", + "id": "01M1X6KHQFSZ47XB4BP4TBGNJN", + "kind": "memory", + "score": 0.994149684906006, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + }, + { + "expansion_handle": "memory:01M1X6FCR2A1FS53B5VE4XJM4D", + "id": "01M1X6KHQFR4PFYV55AWPBEKEE", + "kind": "memory", + "score": 0.7471600770950317, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps — use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 707.4231, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2755, + "mcp_result_bytes": 2894, + "wire_bytes": 2931, + "reported_used_tokens": 2894, + "working_set_bytes": 290574336, + "peak_working_set_bytes": 291495936 + }, + { + "query": "pin minimum supported Rust version in Cargo.toml", + "ranked": [ + "cargo-msrv" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCW109CC42VFPE46KZBB", + "id": "01M1X6KJDN572GZM349PXMVA2G", + "kind": "memory", + "score": 0.999652862548828, + "summary": "project:fact - [tags: cargo rust msrv edition compatibility] Set `rust-version` in each `Cargo.toml` to declare the minimum supported Rust version (MSRV). Cargo enforces this with `--check`: `cargo check` fails if the toolchain is older than `rust-version`. Keep MSRV as old as your oldest supported deployment target." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 820.0004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 693, + "mcp_result_bytes": 774, + "wire_bytes": 811, + "reported_used_tokens": 774, + "working_set_bytes": 290582528, + "peak_working_set_bytes": 291500032 + }, + { + "query": "Windows path over 260 characters causes OS error 3 during Cargo build", + "ranked": [ + "windows-long-paths", + "windows-file-locking-av" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FFQ8VSBKZ2BKTFTATS24", + "id": "01M1X6KK80N8F0DYJQK5168RDA", + "kind": "memory", + "score": 0.9964189529418944, + "summary": "project:fact - [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe." + }, + { + "expansion_handle": "memory:01M1X6FFR991WAFW34D9V4G69V", + "id": "01M1X6KK80GJ9JJRYBYCM2HM76", + "kind": "memory", + "score": 0.9571694135665894, + "summary": "project:fact - [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 845.1892, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1297, + "mcp_result_bytes": 1406, + "wire_bytes": 1443, + "reported_used_tokens": 1406, + "working_set_bytes": 290709504, + "peak_working_set_bytes": 291618816 + }, + { + "query": "how do I enable long file paths for Cargo on Windows?", + "ranked": [ + "windows-long-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FFQ8VSBKZ2BKTFTATS24", + "id": "01M1X6KM20ZRWK6KR8TY3G93KZ", + "kind": "memory", + "score": 0.9998334646224976, + "summary": "project:fact - [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 915.3448, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 769, + "mcp_result_bytes": 860, + "wire_bytes": 897, + "reported_used_tokens": 860, + "working_set_bytes": 290951168, + "peak_working_set_bytes": 291868672 + }, + { + "query": "intermittent sharing violation errors when Rust linker writes the exe on Windows", + "ranked": [ + "windows-file-locking-av", + "windows-long-paths", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FFR991WAFW34D9V4G69V", + "id": "01M1X6KMY4E6D9K37NPZ8VR6PV", + "kind": "memory", + "score": 0.999750316143036, + "summary": "project:fact - [2026-09-07] [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + }, + { + "expansion_handle": "memory:01M1X6FFQ8VSBKZ2BKTFTATS24", + "id": "01M1X6KMY5JMQRR6N1FAXMBM38", + "kind": "memory", + "score": 0.4757097661495209, + "summary": "project:fact - [2026-09-07] [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe." + }, + { + "expansion_handle": "memory:01M1X6FCB5829A6ZF2C5FB9VZW", + "id": "01M1X6KMY5EB4GGM5WZEE2G4QJ", + "kind": "memory", + "score": 0.38107830286026, + "summary": "project:fact - [2026-09-07] [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 850.1333, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2006, + "mcp_result_bytes": 2133, + "wire_bytes": 2170, + "reported_used_tokens": 2133, + "working_set_bytes": 291012608, + "peak_working_set_bytes": 291926016 + }, + { + "query": "Rust walkdir follows junctions differently from symlinks on Windows", + "ranked": [ + "windows-junctions-vs-symlinks" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FFVEP9KN0JQRHTND0FM1", + "id": "01M1X6KNRWJ5GM441FQX63AZ96", + "kind": "memory", + "score": 0.9996020197868348, + "summary": "project:fact - [tags: windows junctions symlinks rust std::fs] On Windows, directory junctions (NTFS reparse points) behave like symlinks for directory traversal but `std::fs::symlink_metadata` returns `FileType::is_symlink() = false` for junctions (only true for regular symlinks). Use `std::fs::read_link` — it succeeds for both junction and symlink. `walkdir` crate's `follow_links` follows both, but its `is_symlink()` method correctly reports only actual symlinks." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 868.5502, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 845, + "mcp_result_bytes": 926, + "wire_bytes": 963, + "reported_used_tokens": 926, + "working_set_bytes": 291024896, + "peak_working_set_bytes": 291938304 + }, + { + "query": "UNC path canonicalize returns verbatim prefix — how do I strip it?", + "ranked": [ + "windows-unc-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FFS7DE9DPM08NGY5SVHJ", + "id": "01M1X6KPKZZ02FNY0QDVVWA5VP", + "kind": "memory", + "score": 0.9988629817962646, + "summary": "project:fact - [tags: windows unc-paths rust std::fs] Windows UNC paths (`\\\\server\\share\\...`) are not supported by most Rust `std::fs` operations unless passed through the extended-length prefix `\\\\?\\UNC\\server\\share\\...`. `std::path::Path::new(\"\\\\\\\\server\\\\share\")` works for basic operations but breaks with `canonicalize()` which returns the verbatim prefix form. When walking directory trees that may start on UNC paths, use the `dunce` crate to strip the verbatim prefix before comparing or displaying paths." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 998.3716000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 908, + "mcp_result_bytes": 1025, + "wire_bytes": 1062, + "reported_used_tokens": 1025, + "working_set_bytes": 291024896, + "peak_working_set_bytes": 291950592 + }, + { + "query": "UTF-8 memory text prints as mojibake in the Windows console", + "ranked": [ + "windows-console-encoding" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FFTBPZ8XFF494GQ45PTS", + "id": "01M1X6KQM7YNXTWW4NTC54680J", + "kind": "memory", + "score": 0.9996604919433594, + "summary": "project:fact - [tags: windows console encoding utf8 rust] Windows console code page defaults to the system ANSI code page (usually CP1252 or CP932), not UTF-8. Rust's `println!` writes UTF-8 bytes which display as mojibake in a non-UTF-8 console. Fix at process startup: call `SetConsoleOutputCP(65001)` via `winapi` or `windows-sys`, or set `PYTHONUTF8=1`/`RUST_LOG` before launch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 931.2324, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 757, + "mcp_result_bytes": 838, + "wire_bytes": 875, + "reported_used_tokens": 838, + "working_set_bytes": 291078144, + "peak_working_set_bytes": 291995648 + }, + { + "query": "process exit code is 4294967295 instead of -1 on Windows", + "ranked": [ + "windows-exit-codes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FFWJGHSXG15B8NJBY9ZJ", + "id": "01M1X6KRGDTKXYSGFN8WVHV8KP", + "kind": "memory", + "score": 0.9966622591018676, + "summary": "project:fact - [tags: windows exit-codes rust process child] On Windows, process exit codes are 32-bit unsigned integers (DWORD). Rust's `ExitStatus::code()` returns `Option` — it's `None` if the process was killed by a signal (which Windows doesn't use; instead, TerminateProcess with a code). Conventional codes: 0=success, 1=generic error, 0xC0000005=access violation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 915.4132999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 753, + "mcp_result_bytes": 834, + "wire_bytes": 871, + "reported_used_tokens": 834, + "working_set_bytes": 291135488, + "peak_working_set_bytes": 292048896 + }, + { + "query": "tokenizer.json must match the ONNX model — what breaks if it doesn't?", + "ranked": [ + "onnx-tokenizer-mismatch" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FFYSXQQ6C45AZ59YT4EV", + "id": "01M1X6KSD0M9NHHWZ1HH0G51A2", + "kind": "memory", + "score": 0.9991299510002136, + "summary": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly — specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings — cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 929.2096, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 959, + "mcp_result_bytes": 1040, + "wire_bytes": 1077, + "reported_used_tokens": 1040, + "working_set_bytes": 291233792, + "peak_working_set_bytes": 292147200 + }, + { + "query": "embedding quality degraded after I swapped in the INT8 quantized model", + "ranked": [ + "onnx-quantization-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FFZW8Z92NY5W7JM4TSR9", + "id": "01M1X6KTACKG2ZJ19VQQTB02D3", + "kind": "memory", + "score": 0.997980535030365, + "summary": "project:fact - [2026-09-07] [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals — cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 929.6717, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 990, + "mcp_result_bytes": 1071, + "wire_bytes": 1108, + "reported_used_tokens": 1071, + "working_set_bytes": 291233792, + "peak_working_set_bytes": 292151296 + }, + { + "query": "missing attention mask causes low-norm embeddings in batch inference", + "ranked": [ + "onnx-batch-padding" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FG0RGT5FJC7C49H034FY", + "id": "01M1X6KV7WBHFXETC6W09RDH1A", + "kind": "memory", + "score": 0.9998397827148438, + "summary": "project:fact - [tags: onnx batch padding attention-mask embeddings] When running batch inference with an ONNX model, all inputs in the batch must be padded to the same sequence length. The `attention_mask` tensor marks which tokens are real (1) and which are padding (0). Failing to pass `attention_mask` causes the model to average-pool over padding tokens, producing systematically lower-norm embeddings." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 925.7204, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 781, + "mcp_result_bytes": 862, + "wire_bytes": 899, + "reported_used_tokens": 862, + "working_set_bytes": 291262464, + "peak_working_set_bytes": 292167680 + }, + { + "query": "ONNX model download fails in a Docker container with no home directory", + "ranked": [ + "onnx-model-cache-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FG1TGFPZM415MBZM9G9G", + "id": "01M1X6KW408N341PVZ376S5R73", + "kind": "memory", + "score": 0.9887272119522096, + "summary": "project:fact - [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 946.7518, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 755, + "mcp_result_bytes": 838, + "wire_bytes": 875, + "reported_used_tokens": 838, + "working_set_bytes": 291270656, + "peak_working_set_bytes": 292179968 + }, + { + "query": "fastembed cache path environment variable for CI", + "ranked": [ + "onnx-model-cache-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FG1TGFPZM415MBZM9G9G", + "id": "01M1X6KX1KDNG5ZX8GGMHD6439", + "kind": "memory", + "score": 0.9995118379592896, + "summary": "project:fact - [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 909.1628000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 756, + "mcp_result_bytes": 839, + "wire_bytes": 876, + "reported_used_tokens": 839, + "working_set_bytes": 291270656, + "peak_working_set_bytes": 292179968 + }, + { + "query": "cosine similarity vs dot product for L2-normalized embedding vectors", + "ranked": [ + "onnx-cosine-vs-dot", + "onnx-tokenizer-mismatch", + "onnx-quantization-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FG2RYQFQGDVARXZ18F3S", + "id": "01M1X6KXY1C3W0TKGDX1ZV69GY", + "kind": "memory", + "score": 0.9999407529830932, + "summary": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing — double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + }, + { + "expansion_handle": "memory:01M1X6FFYSXQQ6C45AZ59YT4EV", + "id": "01M1X6KXY1S5C85AH5678YDSWR", + "kind": "memory", + "score": 0.9514977931976318, + "summary": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly — specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings — cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo." + }, + { + "expansion_handle": "memory:01M1X6FFZW8Z92NY5W7JM4TSR9", + "id": "01M1X6KXY1SPE5YGAYTQM3MRRM", + "kind": "memory", + "score": 0.941756010055542, + "summary": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals — cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 869.5242, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2245, + "mcp_result_bytes": 2362, + "wire_bytes": 2399, + "reported_used_tokens": 2362, + "working_set_bytes": 291270656, + "peak_working_set_bytes": 292184064 + }, + { + "query": "stored vectors have wrong dimension after switching embedding models", + "ranked": [ + "onnx-dim-mismatch", + "onnx-cosine-vs-dot", + "onnx-tokenizer-mismatch" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FG3R3G9YXMC4MA14S64D", + "id": "01M1X6KYS9MCRJ1AFGBT0DP22C", + "kind": "memory", + "score": 0.9997621178627014, + "summary": "project:fact - [2026-09-07] [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results — the ANN index shape mismatch isn't always caught at runtime." + }, + { + "expansion_handle": "memory:01M1X6FG2RYQFQGDVARXZ18F3S", + "id": "01M1X6KYS9EGA88M5AM8GW2R7E", + "kind": "memory", + "score": 0.997715711593628, + "summary": "project:fact - [2026-09-07] [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing — double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + }, + { + "expansion_handle": "memory:01M1X6FFYSXQQ6C45AZ59YT4EV", + "id": "01M1X6KYS97XARDXADZQ41HHVF", + "kind": "memory", + "score": 0.9388805031776428, + "summary": "project:fact - [2026-09-07] [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly — specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings — cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 768.2161, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2049, + "mcp_result_bytes": 2166, + "wire_bytes": 2203, + "reported_used_tokens": 2166, + "working_set_bytes": 291270656, + "peak_working_set_bytes": 292184064 + }, + { + "query": "E5 and Instructor models need a query prefix — what happens without it?", + "ranked": [ + "onnx-prefix-instructions", + "onnx-cosine-vs-dot" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FS5P1F6TRPAAV5EB9RV2", + "id": "01M1X6KZHEESF4ACEXFQDBMHK8", + "kind": "memory", + "score": 0.996955633163452, + "summary": "project:fact - [tags: onnx embeddings prefix instruction e5 query passage] E5 and Instructor family models require a text prefix on BOTH query and passage sides to produce meaningful similarities: query prefix `\"query: \"`, passage prefix `\"passage: \"`. Omitting the prefix can drop MRR by 10-15 percentage points on out-of-domain datasets. Check the model's README for the exact prefix string — it varies by model family." + }, + { + "expansion_handle": "memory:01M1X6FG2RYQFQGDVARXZ18F3S", + "id": "01M1X6KZHE81VHH6YC0CSF273A", + "kind": "memory", + "score": 0.9543967247009276, + "summary": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing — double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 914.6564000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1339, + "mcp_result_bytes": 1446, + "wire_bytes": 1483, + "reported_used_tokens": 1446, + "working_set_bytes": 291270656, + "peak_working_set_bytes": 292184064 + }, + { + "query": "ORT thread pool contention when running multiple bench processes in parallel", + "ranked": [ + "onnx-ort-threading" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FS98N5KG1JWR5W9JGH3Q", + "id": "01M1X6M0DWQCWCBGD11RD7CYA3", + "kind": "memory", + "score": 0.9998078942298888, + "summary": "project:fact - [2026-09-07] [tags: onnx ort thread-pool parallelism cpu] ORT (ONNX Runtime) creates its own inter-op and intra-op thread pools. In a multi-process bench setup, each child inherits these pools and they compete for CPU cores. Set `SessionOptionsBuilder::with_intra_threads(1).with_inter_threads(1)` if you're running many parallel bench processes — this sacrifices per-inference throughput for lower contention." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 918.3127, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 802, + "mcp_result_bytes": 883, + "wire_bytes": 920, + "reported_used_tokens": 883, + "working_set_bytes": 291291136, + "peak_working_set_bytes": 292204544 + }, + { + "query": "git worktrees share the .kimetsu brain — how do I isolate test runs?", + "ranked": [ + "git-worktree-brain-isolation", + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FSA7MZHYF9BQ9B8J0E8G", + "id": "01M1X6M1AY0FNJAF3SFKXMJX8A", + "kind": "memory", + "score": 0.9996256828308104, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root — if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + }, + { + "expansion_handle": "memory:01M1X6FC1PYTV8EEQVV2FCY4FV", + "id": "01M1X6M1AY753Q10QP24Y4DW7M", + "kind": "memory", + "score": 0.9904396533966064, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 909.6406999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1435, + "mcp_result_bytes": 1534, + "wire_bytes": 1571, + "reported_used_tokens": 1534, + "working_set_bytes": 291328000, + "peak_working_set_bytes": 292245504 + }, + { + "query": "when is it safe to use --no-verify on git commit?", + "ranked": [ + "git-hooks-bypass", + "git-reflog-rescue" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FSB5ESHBE4FQ0K5CA3VS", + "id": "01M1X6M27BDCAB8HY686PWWCH3", + "kind": "memory", + "score": 0.9956986904144288, + "summary": "project:fact - [2026-09-07] [tags: git hooks bypass pre-commit skip] `git commit --no-verify` skips ALL hooks (pre-commit and commit-msg). Never use this in shared team repos where hooks enforce quality gates (lint, tests, memory harvest). Instead, fix the failing hook." + }, + { + "expansion_handle": "memory:01M1X6FSF4P5WNAZZCJ0H37WSZ", + "id": "01M1X6M27B961TANKRCYN3T0WR", + "kind": "memory", + "score": 0.5084817409515381, + "summary": "project:fact - [2026-09-07] [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone — they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only — remote reflog is not accessible via normal git commands." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 924.5867, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1193, + "mcp_result_bytes": 1292, + "wire_bytes": 1329, + "reported_used_tokens": 1292, + "working_set_bytes": 291332096, + "peak_working_set_bytes": 292245504 + }, + { + "query": "reduce clone size and bandwidth for server-side repo ingest", + "ranked": [ + "git-sparse-checkout", + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FSC5XPNYYWTGYFQSJA47", + "id": "01M1X6M351SX8SPJM75HQFN0QN", + "kind": "memory", + "score": 0.9969936609268188, + "summary": "project:fact - [tags: git sparse-checkout partial-clone bandwidth] `git sparse-checkout init --cone` combined with `git clone --filter=blob:none` (partial clone) fetches only the commit graph and tree objects, not blobs. Individual blobs are fetched on demand when accessed. This cuts clone time for large repos from minutes to seconds." + }, + { + "expansion_handle": "memory:01M1X6FBMAHT4JHT1S9T1YAQCN", + "id": "01M1X6M3512J8BEE29C79SGPAX", + "kind": "memory", + "score": 0.8199672698974609, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1012.184, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1744, + "mcp_result_bytes": 1843, + "wire_bytes": 1880, + "reported_used_tokens": 1843, + "working_set_bytes": 291344384, + "peak_working_set_bytes": 292253696 + }, + { + "query": "spurious diffs from Windows CRLF line ending conversion in git", + "ranked": [ + "git-line-endings-windows" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FSD6AF9T2S6W9B43HM0C", + "id": "01M1X6M43QJEZFBQ9MCYJTC76A", + "kind": "memory", + "score": 0.9993343949317932, + "summary": "project:fact - [tags: git line-endings windows crlf autocrlf] On Windows, `core.autocrlf=true` (git's default for Windows installs) converts LF to CRLF on checkout and CRLF to LF on commit. This causes spurious diffs when files are edited on Windows then committed — the content is identical but the line endings differ in the index vs the working tree. Fix: set `core.autocrlf=false` and `.gitattributes` with `* text=auto eol=lf` for the repo." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 921.7174, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 940, + "reported_used_tokens": 903, + "working_set_bytes": 291459072, + "peak_working_set_bytes": 292364288 + }, + { + "query": "git submodule always gets the wrong commit in CI", + "ranked": [ + "git-submodule-pinning", + "git-hooks-bypass" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FSE7P3FNP04PMXVVBWR0", + "id": "01M1X6M50FTNDZCJT0C68F8V5J", + "kind": "memory", + "score": 0.9992856383323668, + "summary": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip — this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version." + }, + { + "expansion_handle": "memory:01M1X6FSB5ESHBE4FQ0K5CA3VS", + "id": "01M1X6M50FS5TW6W6JF08G3EPS", + "kind": "memory", + "score": 0.6295387744903564, + "summary": "project:fact - [tags: git hooks bypass pre-commit skip] `git commit --no-verify` skips ALL hooks (pre-commit and commit-msg). Never use this in shared team repos where hooks enforce quality gates (lint, tests, memory harvest). Instead, fix the failing hook." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 924.2198999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1157, + "mcp_result_bytes": 1256, + "wire_bytes": 1293, + "reported_used_tokens": 1256, + "working_set_bytes": 291495936, + "peak_working_set_bytes": 292409344 + }, + { + "query": "accidentally ran git reset --hard and lost commits — can I recover?", + "ranked": [ + "git-reflog-rescue" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FSF4P5WNAZZCJ0H37WSZ", + "id": "01M1X6M5XD2J8EFBSF72FC37E9", + "kind": "memory", + "score": 0.9995450377464294, + "summary": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone — they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only — remote reflog is not accessible via normal git commands." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 945.5919, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 762, + "mcp_result_bytes": 843, + "wire_bytes": 880, + "reported_used_tokens": 843, + "working_set_bytes": 291557376, + "peak_working_set_bytes": 292454400 + }, + { + "query": "blocking SQLite call from an async tokio handler causes latency spikes", + "ranked": [ + "tokio-blocking-in-async" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FSG1SR2HKPVJWCKTW0XB", + "id": "01M1X6M6TYVD8PQY00RS71CX4Q", + "kind": "memory", + "score": 0.9996535778045654, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking — never call rusqlite directly from an async fn without spawn_blocking." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 862.9171, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 766, + "mcp_result_bytes": 847, + "wire_bytes": 884, + "reported_used_tokens": 847, + "working_set_bytes": 291561472, + "peak_working_set_bytes": 292470784 + }, + { + "query": "Cannot start a runtime from within a runtime in a tokio test", + "ranked": [ + "tokio-runtime-in-tests", + "tokio-blocking-in-async" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FSH3ZJYWBSJKZKZYY4Z4", + "id": "01M1X6M7QFPEDE24VS6RKEDPY3", + "kind": "memory", + "score": 0.9997126460075378, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + }, + { + "expansion_handle": "memory:01M1X6FSG1SR2HKPVJWCKTW0XB", + "id": "01M1X6M7QFKT0YQ3QJTZAYCXT7", + "kind": "memory", + "score": 0.5779464840888977, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking — never call rusqlite directly from an async fn without spawn_blocking." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 919.6691000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1370, + "mcp_result_bytes": 1477, + "wire_bytes": 1514, + "reported_used_tokens": 1477, + "working_set_bytes": 291565568, + "peak_working_set_bytes": 292478976 + }, + { + "query": "tokio select cancels the other branch and loses the value in the channel", + "ranked": [ + "tokio-select-cancellation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FSJ8K553R8HRDXZWVYCK", + "id": "01M1X6M8K4CDNFFQ8JVHPY0972", + "kind": "memory", + "score": 0.9981033802032472, + "summary": "project:fact - [tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 898.5843, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 751, + "mcp_result_bytes": 832, + "wire_bytes": 869, + "reported_used_tokens": 832, + "working_set_bytes": 291524608, + "peak_working_set_bytes": 292478976 + }, + { + "query": "mpsc channel backpressure causing senders to stall", + "ranked": [ + "tokio-channel-backpressure" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FSKAN9D5R0R7PEPAWVXZ", + "id": "01M1X6M9F0XFK1MC0CPDWJJVYD", + "kind": "memory", + "score": 0.9999104738235474, + "summary": "project:fact - [tags: tokio mpsc channel backpressure async rust] `tokio::sync::mpsc::channel(N)` with a bounded buffer provides backpressure: senders block when the buffer is full. This prevents unbounded memory growth but can cause sender tasks to stall. Choosing N: too small causes frequent backpressure (throughput drops); too large defeats the purpose." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 970.8599, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 733, + "mcp_result_bytes": 814, + "wire_bytes": 851, + "reported_used_tokens": 814, + "working_set_bytes": 291590144, + "peak_working_set_bytes": 292495360 + }, + { + "query": "overhead from calling spawn_blocking on every single query request", + "ranked": [ + "tokio-spawn-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FSMB4HAEPE1NRY0HQGYK", + "id": "01M1X6MADWPJ3RHQY40X6YEHNB", + "kind": "memory", + "score": 0.9961729645729064, + "summary": "project:fact - [tags: tokio spawn_blocking thread-pool rust blocking] `tokio::task::spawn_blocking` places work on a dedicated blocking thread pool (default up to 512 threads, configurable via `Builder::max_blocking_threads`). Each call creates or reuses a thread — there's no true pooling, threads may be created on demand. For many short-duration blocking calls (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 931.0636, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 746, + "mcp_result_bytes": 827, + "wire_bytes": 864, + "reported_used_tokens": 827, + "working_set_bytes": 291631104, + "peak_working_set_bytes": 292540416 + }, + { + "query": "axum server panics during shutdown because the DB pool is already closed", + "ranked": [ + "tokio-shutdown-ordering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FXM81NH999732FH0NQF1", + "id": "01M1X6MBAYGGGM485Z5ND03RKR", + "kind": "memory", + "score": 0.98052579164505, + "summary": "project:fact - [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries — the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 955.8805, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 931, + "mcp_result_bytes": 1012, + "wire_bytes": 1049, + "reported_used_tokens": 1012, + "working_set_bytes": 291647488, + "peak_working_set_bytes": 292564992 + }, + { + "query": "reqwest Client created per-request defeats connection pooling", + "ranked": [ + "http-connection-pooling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FXN8ZABYN3NE7VTA3TA2", + "id": "01M1X6MC84P6EQWE21RDGRX339", + "kind": "memory", + "score": 0.9998082518577576, + "summary": "project:fact - [tags: http reqwest connection-pool keep-alive rust] reqwest's `Client` holds a connection pool; always create ONE `Client` instance and clone it for each handler — cloning is cheap (Arc under the hood). Creating a `Client::new()` per request defeats connection pooling and causes TCP connection exhaustion under load. The default pool settings: max_idle_per_host=usize::MAX (unbounded), idle_timeout=90s." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 859.7458, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 797, + "mcp_result_bytes": 878, + "wire_bytes": 915, + "reported_used_tokens": 878, + "working_set_bytes": 291659776, + "peak_working_set_bytes": 292564992 + }, + { + "query": "LLM request times out during streaming — which timeout setting applies?", + "ranked": [ + "http-timeout-layering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FXPB6K07WRKD3KGXF7EF", + "id": "01M1X6MD3128DSK4QG6RVDQWDD", + "kind": "memory", + "score": 0.9987107515335084, + "summary": "project:fact - [tags: http reqwest timeout connect read total rust] reqwest has three distinct timeout knobs: `connect_timeout`, `read_timeout`, and `timeout` (total). They compose: if all three are set, the request fails at whichever fires first. For LLM API calls with streaming responses, `read_timeout` must be larger than the slowest expected token (often 30-60s) while `connect_timeout` can be tight (3-5s)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 781.1405, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 788, + "mcp_result_bytes": 869, + "wire_bytes": 906, + "reported_used_tokens": 869, + "working_set_bytes": 291663872, + "peak_working_set_bytes": 292577280 + }, + { + "query": "how do I safely retry a POST to the LLM API without creating duplicates?", + "ranked": [ + "http-retry-idempotency" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FXQC2X80NMEXZKQGHQ6F", + "id": "01M1X6MDVFQ39CRNH3HDDEDMYK", + "kind": "memory", + "score": 0.9995805621147156, + "summary": "project:fact - [tags: http retry idempotency post put reqwest] Only retry idempotent requests automatically. GET, HEAD, PUT, DELETE are idempotent. POST is NOT — retrying a POST may create duplicate resources." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 945.2389, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 585, + "mcp_result_bytes": 666, + "wire_bytes": 703, + "reported_used_tokens": 666, + "working_set_bytes": 291676160, + "peak_working_set_bytes": 292593664 + }, + { + "query": "custom enterprise root CA not trusted by rustls on Windows", + "ranked": [ + "http-tls-roots", + "http-proxy-env" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FXREK18YEP6CRP9SWNC4", + "id": "01M1X6MES1J8QSSTGY8VRRSBNM", + "kind": "memory", + "score": 0.9998220801353456, + "summary": "project:fact - [tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle — the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle." + }, + { + "expansion_handle": "memory:01M1X6FXTH25ZXV6TZSV92WFM9", + "id": "01M1X6MES1CPSS3Q5VDWVHZ3SD", + "kind": "memory", + "score": 0.38715291023254395, + "summary": "project:fact - [tags: http proxy environment reqwest rust corporate] reqwest respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` environment variables by default (with `default-tls` or `rustls-tls`). In a corporate network, these may redirect traffic through an intercepting proxy that breaks mTLS or adds latency. To disable proxy usage entirely: `reqwest::ClientBuilder::no_proxy()`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 924.959, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1311, + "mcp_result_bytes": 1410, + "wire_bytes": 1447, + "reported_used_tokens": 1410, + "working_set_bytes": 291676160, + "peak_working_set_bytes": 292593664 + }, + { + "query": "parsing server-sent events when a single TCP chunk contains a partial SSE frame", + "ranked": [ + "http-streaming-bodies" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FXSE15217ZRDGWT76DK4", + "id": "01M1X6MFP30W4XP3RSK1921688", + "kind": "memory", + "score": 0.9667426943778992, + "summary": "project:fact - [2026-09-07] [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding — a chunk may split across frame boundaries." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 849.7919, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 859, + "mcp_result_bytes": 940, + "wire_bytes": 977, + "reported_used_tokens": 940, + "working_set_bytes": 291684352, + "peak_working_set_bytes": 292597760 + }, + { + "query": "reqwest does not use the system proxy settings on Windows", + "ranked": [ + "http-proxy-env", + "http-tls-roots", + "http-connection-pooling", + "http-streaming-bodies" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FXTH25ZXV6TZSV92WFM9", + "id": "01M1X6MGGHJ21NBKD71ACG1AHJ", + "kind": "memory", + "score": 0.9997830986976624, + "summary": "project:fact - [tags: http proxy environment reqwest rust corporate] reqwest respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` environment variables by default (with `default-tls` or `rustls-tls`). In a corporate network, these may redirect traffic through an intercepting proxy that breaks mTLS or adds latency. To disable proxy usage entirely: `reqwest::ClientBuilder::no_proxy()`." + }, + { + "expansion_handle": "memory:01M1X6FXREK18YEP6CRP9SWNC4", + "id": "01M1X6MGGHZ3TWG9FY7M7WXKJ8", + "kind": "memory", + "score": 0.9808586239814758, + "summary": "project:fact - [tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle — the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle." + }, + { + "expansion_handle": "memory:01M1X6FXN8ZABYN3NE7VTA3TA2", + "id": "01M1X6MGGHVEA7MZVMHEGYYMPH", + "kind": "memory", + "score": 0.719273030757904, + "summary": "project:fact - [tags: http reqwest connection-pool keep-alive rust] reqwest's `Client` holds a connection pool; always create ONE `Client` instance and clone it for each handler — cloning is cheap (Arc under the hood). Creating a `Client::new()` per request defeats connection pooling and causes TCP connection exhaustion under load. The default pool settings: max_idle_per_host=usize::MAX (unbounded), idle_timeout=90s." + }, + { + "expansion_handle": "memory:01M1X6FXSE15217ZRDGWT76DK4", + "id": "01M1X6MGGHK2N1P2TK66PR1Z6C", + "kind": "memory", + "score": 0.7009692192077637, + "summary": "project:fact - [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding — a chunk may split across frame boundaries." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 904.0970000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2497, + "mcp_result_bytes": 2632, + "wire_bytes": 2669, + "reported_used_tokens": 2632, + "working_set_bytes": 291696640, + "peak_working_set_bytes": 292605952 + }, + { + "query": "insta snapshot tests fail in CI because output includes a timestamp", + "ranked": [ + "testing-snapshot-churn", + "ci-flaky-quarantine" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FXVJWFJRV5NQKVFGYH08", + "id": "01M1X6MHD3DY28R97EJHE7AJJM", + "kind": "memory", + "score": 0.999855637550354, + "summary": "project:fact - [tags: testing snapshot insta assert churn rust] Snapshot tests (e.g. with the `insta` crate) fail whenever the output changes, even for intended changes. In CI, they fail loudly; locally, `cargo insta review` walks you through accepting or rejecting changes." + }, + { + "expansion_handle": "memory:01M1X6G569HH9Q1FNPCPH6GQV2", + "id": "01M1X6MHD3RJ9CQR3GZSA2VVDH", + "kind": "memory", + "score": 0.5997360348701477, + "summary": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal — a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 834.9459, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1196, + "mcp_result_bytes": 1295, + "wire_bytes": 1332, + "reported_used_tokens": 1295, + "working_set_bytes": 291733504, + "peak_working_set_bytes": 292646912 + }, + { + "query": "two test workers writing to the same temp directory path race each other", + "ranked": [ + "testing-temp-dirs-ci" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FXWMDSNDYDFJ8T7ES5D5", + "id": "01M1X6MJ70BV9J7SFVFZ8MEMP6", + "kind": "memory", + "score": 0.9889234900474548, + "summary": "project:fact - [tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 887.6359, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 755, + "mcp_result_bytes": 836, + "wire_bytes": 873, + "reported_used_tokens": 836, + "working_set_bytes": 291758080, + "peak_working_set_bytes": 292667392 + }, + { + "query": "test passes locally but fails on a slow CI runner due to a 100ms sleep", + "ranked": [ + "testing-time-dependent-flakes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FXXQ3XGY48XD6EHMVND0", + "id": "01M1X6MK2X9E7NQVJ8AWRM492B", + "kind": "memory", + "score": 0.808289110660553, + "summary": "project:fact - [tags: testing time flaky clock mock rust] Tests that depend on wall-clock time are inherently flaky under load (slow CI runners, GC pauses). Abstract time behind a trait (`Clock: Fn() -> SystemTime`) injected at construction, and supply a fake in tests. For tests checking that something happened \"within N seconds\", use a generous multiple of the expected duration (10x is not unreasonable for CI)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 959.1518, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 790, + "mcp_result_bytes": 875, + "wire_bytes": 912, + "reported_used_tokens": 875, + "working_set_bytes": 291758080, + "peak_working_set_bytes": 292679680 + }, + { + "query": "proptest found a hash collision in text normalization that example tests missed", + "ranked": [ + "testing-property-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FXYP8RYKPBJ33PNZH7NJ", + "id": "01M1X6MM128FX20K47YSSQY8PZ", + "kind": "memory", + "score": 0.9994783997535706, + "summary": "project:fact - [tags: testing property-based proptest quickcheck rust] Property-based tests (proptest, quickcheck) find edge cases that example-based tests miss. For kimetsu's memory text normalization, proptest found that zero-width joiner characters and right-to-left marks caused hash collisions. Run proptest with `PROPTEST_CASES=10000` in CI for thorough coverage." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 947.9764, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 744, + "mcp_result_bytes": 825, + "wire_bytes": 862, + "reported_used_tokens": 825, + "working_set_bytes": 291766272, + "peak_working_set_bytes": 292679680 + }, + { + "query": "set_var in tests races when cargo test runs them in parallel", + "ranked": [ + "testing-serial-vs-parallel" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FXZM3KNGPDANSTPNKTQR", + "id": "01M1X6MMYFMGRB9XPFGTCKPN07", + "kind": "memory", + "score": 0.9997344613075256, + "summary": "project:fact - [2026-09-07] [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 925.0024999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 832, + "mcp_result_bytes": 913, + "wire_bytes": 950, + "reported_used_tokens": 913, + "working_set_bytes": 291799040, + "peak_working_set_bytes": 292720640 + }, + { + "query": "hardcoded JSON fixtures broke after a schema migration", + "ranked": [ + "testing-fixture-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G1V5C0B2FRN6AHK9JQJW", + "id": "01M1X6MNV8YP6F0RYMGDYHWPR6", + "kind": "memory", + "score": 0.9998371601104736, + "summary": "project:fact - [2026-09-07] [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 782.6581, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 783, + "mcp_result_bytes": 864, + "wire_bytes": 901, + "reported_used_tokens": 864, + "working_set_bytes": 291811328, + "peak_working_set_bytes": 292720640 + }, + { + "query": "debug print in the MCP handler corrupts the JSON-Lines protocol stream", + "ranked": [ + "mcp-stdout-protocol" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G1W0ZBD86TBRPQESXKTX", + "id": "01M1X6MPMR5PRF1QA1REGZJKRF", + "kind": "memory", + "score": 0.9997472167015076, + "summary": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 971.3231, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 705, + "mcp_result_bytes": 786, + "wire_bytes": 823, + "reported_used_tokens": 786, + "working_set_bytes": 291811328, + "peak_working_set_bytes": 292724736 + }, + { + "query": "kimetsu MCP tool call times out because embedding model is re-initialized every call", + "ranked": [ + "mcp-tool-timeouts", + "mcp-schema-validation", + "kimetsu-bench-remote-embedder-singleton" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G1X1XGE9MM05BANVE5DQ", + "id": "01M1X6MQJNZA08SYFEBY2FTXQR", + "kind": "memory", + "score": 0.9995898604393004, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking — in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize — keep it in a process-global `OnceLock`)." + }, + { + "expansion_handle": "memory:01M1X6G1Z4B34X6RM0V12B7VR5", + "id": "01M1X6MQJNVBYQVE3PXJFW0CWC", + "kind": "memory", + "score": 0.6027993559837341, + "summary": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array — omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error." + }, + { + "expansion_handle": "memory:01M1X6G5NP21NSKR0TBJPHB80K", + "id": "01M1X6MQJN52SARADGD0N8X6WN", + "kind": "memory", + "score": 0.5117799639701843, + "summary": "project:fact - [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 838.5128, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2085, + "mcp_result_bytes": 2202, + "wire_bytes": 2239, + "reported_used_tokens": 2202, + "working_set_bytes": 291811328, + "peak_working_set_bytes": 292724736 + }, + { + "query": "env var set after host launch is not visible to the MCP server process", + "ranked": [ + "mcp-env-propagation", + "kimetsu-daemon-lifecycle" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G1Y3K75Y1EDB0JHXK3KF", + "id": "01M1X6MRCKJJ7W1Q8HWV657ZEF", + "kind": "memory", + "score": 0.9984827637672424, + "summary": "project:fact - [2026-09-07] [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment — changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate." + }, + { + "expansion_handle": "memory:01M1X6G57BG9P4RZW0F69BHBK0", + "id": "01M1X6MRCK3MNYZBV929T4S2TE", + "kind": "memory", + "score": 0.9977922439575196, + "summary": "project:fact - [2026-09-07] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 966.4412, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1267, + "mcp_result_bytes": 1366, + "wire_bytes": 1403, + "reported_used_tokens": 1366, + "working_set_bytes": 291811328, + "peak_working_set_bytes": 292732928 + }, + { + "query": "MCP tool call fails because a required field is missing from the JSON input", + "ranked": [ + "mcp-schema-validation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G1Z4B34X6RM0V12B7VR5", + "id": "01M1X6MSAMD1HH97HMTTKWFBX1", + "kind": "memory", + "score": 0.998538613319397, + "summary": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array — omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 916.9861000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 798, + "mcp_result_bytes": 879, + "wire_bytes": 916, + "reported_used_tokens": 879, + "working_set_bytes": 291811328, + "peak_working_set_bytes": 292732928 + }, + { + "query": "Claude Code rejects the tool name with a hyphen in it", + "ranked": [ + "mcp-tool-naming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G20244N0FWHDXK4YPMG6", + "id": "01M1X6MT78G03JH3AXGB7E51C1", + "kind": "memory", + "score": 0.9982439279556274, + "summary": "project:fact - [tags: mcp tool naming convention kimetsu] MCP tool names must be valid identifiers for all host agents. Claude Code restricts tool names to `[a-zA-Z0-9_-]` and max 64 chars. Use `snake_case` (kimetsu_brain_context, kimetsu_brain_record) — hyphen is technically allowed but some hosts reject it." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 886.049, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 687, + "mcp_result_bytes": 768, + "wire_bytes": 805, + "reported_used_tokens": 768, + "working_set_bytes": 291913728, + "peak_working_set_bytes": 292827136 + }, + { + "query": "MCP response path uses backslashes and the host rejects it", + "ranked": [ + "mcp-transcript-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G212AV49JAYRE5J3NT5J", + "id": "01M1X6MV36REFAJV4K7G1ZS6MW", + "kind": "memory", + "score": 0.9984637498855592, + "summary": "project:fact - [tags: mcp transcript paths kimetsu hooks runs] kimetsu writes run transcripts to `/.kimetsu/runs//`. The post-session hook reads the latest run's transcript to trigger memory harvest. On Windows, the path uses backslashes internally but the MCP JSON must use forward slashes or the host may reject path-type arguments." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 937.4074, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 724, + "mcp_result_bytes": 805, + "wire_bytes": 842, + "reported_used_tokens": 805, + "working_set_bytes": 291913728, + "peak_working_set_bytes": 292827136 + }, + { + "query": "AWS credentials not found — which env var does kimetsu read for Bedrock?", + "ranked": [ + "aws-credentials-chain", + "aws-region-resolution", + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G225TRC4EVAFGXT9RHRX", + "id": "01M1X6MW0PS1JA22Y4HJ1Z70X5", + "kind": "memory", + "score": 0.9990235567092896, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + }, + { + "expansion_handle": "memory:01M1X6G23CEN8Y7G8DV4SNBYQB", + "id": "01M1X6MW0PWGGKJ4EPH21AQ096", + "kind": "memory", + "score": 0.9968422651290894, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X6FBSFGZG953DWGDC5CQJC", + "id": "01M1X6MW0P9SRETETMKHTR361N", + "kind": "memory", + "score": 0.9849756360054016, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X6FBYZ5JT46Z87176E5AER", + "id": "01M1X6MW0PT2RPDA1PZCPSY4V0", + "kind": "memory", + "score": 0.9203452467918396, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 969.0597, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3455, + "mcp_result_bytes": 3618, + "wire_bytes": 3655, + "reported_used_tokens": 3618, + "working_set_bytes": 291917824, + "peak_working_set_bytes": 292831232 + }, + { + "query": "Bedrock InvokeModel fails because the region is not configured", + "ranked": [ + "aws-region-resolution", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G23CEN8Y7G8DV4SNBYQB", + "id": "01M1X6MWYKJNCPBR1K12RPT1AQ", + "kind": "memory", + "score": 0.99688321352005, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X6FBYZ5JT46Z87176E5AER", + "id": "01M1X6MWYK0NFSDM8CV0SQC7M3", + "kind": "memory", + "score": 0.6450709104537964, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 958.3565, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1810, + "mcp_result_bytes": 1929, + "wire_bytes": 1966, + "reported_used_tokens": 1929, + "working_set_bytes": 291979264, + "peak_working_set_bytes": 292896768 + }, + { + "query": "how do I handle ThrottlingException from Bedrock with exponential backoff?", + "ranked": [ + "aws-retry-throttling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G24BKQF5H1P4JPDZJ0GV", + "id": "01M1X6MXWMH5X809J0F1TFFTSK", + "kind": "memory", + "score": 0.9997082352638244, + "summary": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with ±25% jitter." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 982.6761, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 771, + "mcp_result_bytes": 868, + "wire_bytes": 905, + "reported_used_tokens": 868, + "working_set_bytes": 291987456, + "peak_working_set_bytes": 292896768 + }, + { + "query": "generating a presigned S3 URL for brain export without exposing credentials", + "ranked": [ + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G25DB3HRGVQYHSTK90X7", + "id": "01M1X6MYVDVY1J0BKKTRQ3HK7N", + "kind": "memory", + "score": 0.9990487694740297, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time — clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 927.4926, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 875, + "mcp_result_bytes": 956, + "wire_bytes": 993, + "reported_used_tokens": 956, + "working_set_bytes": 292012032, + "peak_working_set_bytes": 292925440 + }, + { + "query": "IMDSv2 token required for instance metadata — PUT before GET", + "ranked": [ + "aws-instance-metadata" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G26FFC1E97N2YGRW0EA1", + "id": "01M1X6MZRGZJ66JT8PYXAQBVVD", + "kind": "memory", + "score": 0.9997182488441468, + "summary": "project:fact - [2026-09-07] [tags: aws imds instance-metadata ec2 token] The AWS Instance Metadata Service v2 (IMDSv2) requires a session token: PUT `http://169.254.169.254/latest/api/token` with `X-aws-ec2-metadata-token-ttl-seconds: 21600` to get a token, then GET metadata with `X-aws-ec2-metadata-token: `. IMDSv1 (no token) is disabled on hardened instances. The metadata endpoint is only reachable from within EC2 — a connection timeout means you're not on EC2." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 922.7968, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 851, + "mcp_result_bytes": 932, + "wire_bytes": 969, + "reported_used_tokens": 932, + "working_set_bytes": 292012032, + "peak_working_set_bytes": 292925440 + }, + { + "query": "Cargo cache key strategy for GitHub Actions to avoid toolchain version collisions", + "ranked": [ + "ci-cache-keys" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G52K557P44BJMTJKMB7J", + "id": "01M1X6N0N7P1HGY345YSW21QG8", + "kind": "memory", + "score": 0.998869240283966, + "summary": "project:fact - [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key — macOS and Windows have incompatible artifact formats." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 938.7366999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 788, + "mcp_result_bytes": 869, + "wire_bytes": 906, + "reported_used_tokens": 869, + "working_set_bytes": 292032512, + "peak_working_set_bytes": 292945920 + }, + { + "query": "CI matrix has 18 jobs and costs too much — how do I reduce it?", + "ranked": [ + "ci-matrix-explosion" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G53FQFM4EWTPS5X1Q0PW", + "id": "01M1X6N1JPVE6CXB148EMGDMT3", + "kind": "memory", + "score": 0.999057948589325, + "summary": "project:fact - [tags: ci github-actions matrix jobs resources] A CI matrix combining OS (3) x Rust toolchain (3) x features (2) = 18 jobs. Each spawns a runner; at $0.008/min for Ubuntu and $0.016/min for Windows, a 10-minute build costs $2.40 per push. Reduce: test the full matrix only on PRs to main; on feature branches, test only Linux+stable." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 965.0097, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 722, + "mcp_result_bytes": 803, + "wire_bytes": 840, + "reported_used_tokens": 803, + "working_set_bytes": 292151296, + "peak_working_set_bytes": 293068800 + }, + { + "query": "GitHub Actions secret accidentally printed in build logs", + "ranked": [ + "ci-secrets-masking", + "ci-cache-keys", + "ci-artifact-retention" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G54DHRYVMZCDFFVTEDFC", + "id": "01M1X6N2GPHAFQ7S700B9VFG1N", + "kind": "memory", + "score": 0.9963951706886292, + "summary": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output — but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable." + }, + { + "expansion_handle": "memory:01M1X6G52K557P44BJMTJKMB7J", + "id": "01M1X6N2GPXCNTBYKNPTE87CF2", + "kind": "memory", + "score": 0.4342843890190125, + "summary": "project:fact - [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key — macOS and Windows have incompatible artifact formats." + }, + { + "expansion_handle": "memory:01M1X6G55DSAXTVCH86QSPHSYF", + "id": "01M1X6N2GPQN6ENVNSNH58H05B", + "kind": "memory", + "score": 0.3422144949436188, + "summary": "project:fact - [tags: ci github-actions artifacts retention benchmark] GitHub Actions artifacts are retained for 90 days (default). For benchmark results, use `actions/upload-artifact` with `retention-days: 365` for long-term tracking. The free tier has 500MB storage — per-combo JSON files from kimetsu bench (each ~60KB) add up fast if you upload them on every push." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 908.0222, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1791, + "mcp_result_bytes": 1908, + "wire_bytes": 1945, + "reported_used_tokens": 1908, + "working_set_bytes": 292179968, + "peak_working_set_bytes": 293093376 + }, + { + "query": "how long do GitHub Actions artifacts persist and what's the storage limit?", + "ranked": [ + "ci-artifact-retention" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G55DSAXTVCH86QSPHSYF", + "id": "01M1X6N3DFME0ZM2ZYKT7CD6XR", + "kind": "memory", + "score": 0.999624252319336, + "summary": "project:fact - [tags: ci github-actions artifacts retention benchmark] GitHub Actions artifacts are retained for 90 days (default). For benchmark results, use `actions/upload-artifact` with `retention-days: 365` for long-term tracking. The free tier has 500MB storage — per-combo JSON files from kimetsu bench (each ~60KB) add up fast if you upload them on every push." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1000.9542, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 744, + "mcp_result_bytes": 825, + "wire_bytes": 862, + "reported_used_tokens": 825, + "working_set_bytes": 292192256, + "peak_working_set_bytes": 293109760 + }, + { + "query": "timing-based test flake in CI — quarantine or fix?", + "ranked": [ + "ci-flaky-quarantine", + "testing-time-dependent-flakes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G569HH9Q1FNPCPH6GQV2", + "id": "01M1X6N4D0VJAPV6CWFC7Q6YHH", + "kind": "memory", + "score": 0.9994743466377258, + "summary": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal — a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output." + }, + { + "expansion_handle": "memory:01M1X6FXXQ3XGY48XD6EHMVND0", + "id": "01M1X6N4D0GZH144QFVFNPANHB", + "kind": "memory", + "score": 0.9849997162818908, + "summary": "project:fact - [tags: testing time flaky clock mock rust] Tests that depend on wall-clock time are inherently flaky under load (slow CI runners, GC pauses). Abstract time behind a trait (`Clock: Fn() -> SystemTime`) injected at construction, and supply a fake in tests. For tests checking that something happened \"within N seconds\", use a generous multiple of the expected duration (10x is not unreasonable for CI)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 970.1291, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1340, + "mcp_result_bytes": 1443, + "wire_bytes": 1480, + "reported_used_tokens": 1443, + "working_set_bytes": 292216832, + "peak_working_set_bytes": 293138432 + }, + { + "query": "kimetsu doctor says the MCP server is running — how do I stop it before an update?", + "ranked": [ + "kimetsu-daemon-lifecycle", + "mcp-env-propagation", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G57BG9P4RZW0F69BHBK0", + "id": "01M1X6N5BJ5DS5AE36N9CXTP55", + "kind": "memory", + "score": 0.9989782571792604, + "summary": "project:fact - [2026-09-07] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1X6G1Y3K75Y1EDB0JHXK3KF", + "id": "01M1X6N5BJYPVFVQQZ2HAKQY39", + "kind": "memory", + "score": 0.9049031734466552, + "summary": "project:fact - [2026-09-07] [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment — changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate." + }, + { + "expansion_handle": "memory:01M1X6FBP8RS75FNREKQZ0WD3Q", + "id": "01M1X6N5BJCXQ0XH9MPGJMDXCB", + "kind": "memory", + "score": 0.4812128245830536, + "summary": "project:fact - [2026-09-07] [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1002.3016, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2046, + "mcp_result_bytes": 2211, + "wire_bytes": 2248, + "reported_used_tokens": 2211, + "working_set_bytes": 292216832, + "peak_working_set_bytes": 293138432 + }, + { + "query": "noise capsules consuming token budget without contributing retrieval signal", + "ranked": [ + "kimetsu-capsule-budgets" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G58AVVH0NJSP58CG862R", + "id": "01M1X6N6A12MX8YE25SAN5BTY9", + "kind": "memory", + "score": 0.9997420907020568, + "summary": "project:fact - [tags: kimetsu capsule tokens budget retrieval] kimetsu retrieval enforces a token budget per capsule type: memory capsules are capped at 6000 tokens total (across all retrieved memories), file capsules at 3000 tokens. When a memory is large and would exceed the budget, it is truncated at a sentence boundary. The budget is enforced AFTER reranking — reranking may reorder results so that a truncated high-ranked memory displaces a full lower-ranked one." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 768.0916, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 847, + "mcp_result_bytes": 928, + "wire_bytes": 965, + "reported_used_tokens": 928, + "working_set_bytes": 292249600, + "peak_working_set_bytes": 293163008 + }, + { + "query": "kimetsu_brain_record writes to the wrong brain location — user vs project scope", + "ranked": [ + "kimetsu-memory-scopes", + "kimetsu-write-tools-gate", + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G596BN9AAJMY7GJTXTHF", + "id": "01M1X6N728ZCVAWP533GZGEFS7", + "kind": "memory", + "score": 0.999030828475952, + "summary": "project:fact - [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available — if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope." + }, + { + "expansion_handle": "memory:01M1X6G5C7NRN6660DTHDS58ZS", + "id": "01M1X6N728YC86EDVPSGZ8CZRJ", + "kind": "memory", + "score": 0.9838979840278624, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level — disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1X6FC1PYTV8EEQVV2FCY4FV", + "id": "01M1X6N728TR5V33S7BGRAFYJ8", + "kind": "memory", + "score": 0.3852712512016296, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 899.9648, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2098, + "mcp_result_bytes": 2215, + "wire_bytes": 2252, + "reported_used_tokens": 2215, + "working_set_bytes": 292286464, + "peak_working_set_bytes": 293195776 + }, + { + "query": "how do I configure kimetsu to use Claude Haiku for harvesting but Opus for the agent?", + "ranked": [ + "kimetsu-distiller-config" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G5A797YY5GFD0S04XJGC", + "id": "01M1X6N7YJP2VS70J65WE3HTJY", + "kind": "memory", + "score": 0.9989088773727416, + "summary": "project:fact - [tags: kimetsu distiller harvest config provider] The kimetsu distiller (auto-harvester) uses a SEPARATE provider configuration from the main agent: `distiller.provider`, `distiller.model`, `distiller.api_key`. This allows running the agent on an expensive model (Claude Opus) while harvesting with a cheap model (Claude Haiku). If `distiller.provider` is not set, it inherits `provider`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 938.3766, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 778, + "mcp_result_bytes": 859, + "wire_bytes": 896, + "reported_used_tokens": 859, + "working_set_bytes": 292290560, + "peak_working_set_bytes": 293203968 + }, + { + "query": "first agent turn is slow because kimetsu proactive hook runs embedding inference", + "ranked": [ + "kimetsu-proactive-hooks", + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G5B7FXBAABGSVRFWFCX6", + "id": "01M1X6N8VN8K1VWT7YV4VCM8Z0", + "kind": "memory", + "score": 0.999568521976471, + "summary": "project:fact - [2026-09-07] [tags: kimetsu proactive hooks context injection] kimetsu's proactive context injection runs before each agent turn (pre-turn hook) and injects relevant memories into the system prompt prefix. The hook invocation adds latency to the first token: embedding inference + vector search + reranking + context formatting. On a cold start, this can be 1-3 seconds." + }, + { + "expansion_handle": "memory:01M1X6G1X1XGE9MM05BANVE5DQ", + "id": "01M1X6N8VNPP40W3XBHBDNREDD", + "kind": "memory", + "score": 0.9405298233032228, + "summary": "project:fact - [2026-09-07] [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking — in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize — keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 962.1216000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1403, + "mcp_result_bytes": 1502, + "wire_bytes": 1539, + "reported_used_tokens": 1502, + "working_set_bytes": 292290560, + "peak_working_set_bytes": 293203968 + }, + { + "query": "make the kimetsu brain read-only for certain repos on a shared remote server", + "ranked": [ + "kimetsu-write-tools-gate", + "remote-ingest-split-roots", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G5C7NRN6660DTHDS58ZS", + "id": "01M1X6N9SRQN7DHX3F87FKW24R", + "kind": "memory", + "score": 0.997682809829712, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level — disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1X6FBMAHT4JHT1S9T1YAQCN", + "id": "01M1X6N9SRM0J2YEEP4JP0FTKD", + "kind": "memory", + "score": 0.9957050681114196, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1X6FBP8RS75FNREKQZ0WD3Q", + "id": "01M1X6N9SRMCQQ376T38EVJM92", + "kind": "memory", + "score": 0.9909282326698304, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 897.4788, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2725, + "mcp_result_bytes": 2890, + "wire_bytes": 2927, + "reported_used_tokens": 2890, + "working_set_bytes": 292290560, + "peak_working_set_bytes": 293203968 + }, + { + "query": "kimetsu FTS search misses 'deadlocking' when memory says 'deadlock'", + "ranked": [ + "kimetsu-query-stemming", + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G5KQ8DP47BPMHQSWGBGS", + "id": "01M1X6NAP3K0GTZJV1RZGFD6CQ", + "kind": "memory", + "score": 0.9904030561447144, + "summary": "project:fact - [2026-09-07] [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression." + }, + { + "expansion_handle": "memory:01M1X6FBK795VCKKWK7JEKPT4J", + "id": "01M1X6NAP3YAYBZR26X53RNWG5", + "kind": "memory", + "score": 0.91664320230484, + "summary": "project:fact - [2026-09-07] [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure — `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 874.9429, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1363, + "mcp_result_bytes": 1478, + "wire_bytes": 1515, + "reported_used_tokens": 1478, + "working_set_bytes": 292290560, + "peak_working_set_bytes": 293203968 + }, + { + "query": "how does pool size affect retrieval recall and latency in the bench?", + "ranked": [ + "kimetsu-rerank-pool" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G5MRFQ9WMBZ3BKM72KNT", + "id": "01M1X6NBHCAGXHKWDX593WQZ80", + "kind": "memory", + "score": 0.9998373985290528, + "summary": "project:fact - [tags: kimetsu reranker pool size ann retrieval] kimetsu's retrieval pipeline: ANN (approximate nearest neighbor) retrieves a pool of candidates, then the reranker reorders them, then the top-K are returned. The pool size (default 6 for production, 12 in bench) controls the recall-latency tradeoff: larger pool = higher recall = more reranker calls = more latency. For the jina-tiny reranker, pool 12 adds ~80ms vs pool 6." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 919.0719, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 813, + "mcp_result_bytes": 894, + "wire_bytes": 931, + "reported_used_tokens": 894, + "working_set_bytes": 292290560, + "peak_working_set_bytes": 293208064 + }, + { + "query": "second embedder in a remote bench run gets worse results than the first", + "ranked": [ + "kimetsu-bench-remote-embedder-singleton" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G5NP21NSKR0TBJPHB80K", + "id": "01M1X6NCEQR22YGMNYFKJ61163", + "kind": "memory", + "score": 0.9939629435539246, + "summary": "project:fact - [2026-09-07] [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 932.421, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 895, + "mcp_result_bytes": 976, + "wire_bytes": 1013, + "reported_used_tokens": 976, + "working_set_bytes": 292290560, + "peak_working_set_bytes": 293208064 + }, + { + "query": "what is the expected JSON schema for kimetsu brain bench dataset files?", + "ranked": [ + "kimetsu-eval-fixture-shape", + "testing-fixture-drift", + "kimetsu-mrr-metric", + "mcp-schema-validation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G5PR2N167V5D1K95D9WP", + "id": "01M1X6NDB3TKFYA33H81T9DM5E", + "kind": "memory", + "score": 0.9996767044067384, + "summary": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` — a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases)." + }, + { + "expansion_handle": "memory:01M1X6G1V5C0B2FRN6AHK9JQJW", + "id": "01M1X6NDB31SDDGJHEJXHTKVGH", + "kind": "memory", + "score": 0.9682880640029908, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + }, + { + "expansion_handle": "memory:01M1X6G5QTTFD19FAJ8MZX4ZPC", + "id": "01M1X6NDB3RYPAVGQ9GEAC0YEE", + "kind": "memory", + "score": 0.8818408250808716, + "summary": "project:fact - [tags: kimetsu bench mrr recall metrics evaluation] kimetsu bench reports MRR (Mean Reciprocal Rank) and Recall@K. MRR is 1/rank_of_first_relevant_result, averaged across cases; it penalizes models that rank the correct answer 2nd or 3rd. Recall@K is the fraction of cases where at least one relevant answer appears in the top K." + }, + { + "expansion_handle": "memory:01M1X6G1Z4B34X6RM0V12B7VR5", + "id": "01M1X6NDB3XSCJJ80X7MM3BXBE", + "kind": "memory", + "score": 0.6527947187423706, + "summary": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array — omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 932.5379, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2424, + "mcp_result_bytes": 2603, + "wire_bytes": 2640, + "reported_used_tokens": 2603, + "working_set_bytes": 292290560, + "peak_working_set_bytes": 293208064 + }, + { + "query": "what does MRR mean and how do I interpret a 0.01 difference between combos?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 952.2467, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 292286464, + "peak_working_set_bytes": 293208064 + }, + { + "query": "SQLITE_BUSY keeps appearing even with WAL mode enabled", + "ranked": [ + "sqlite-busy-timeout-wal", + "sqlite-wal-network-drive" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCB5829A6ZF2C5FB9VZW", + "id": "01M1X6NF62K57YX3W4N6YV1MT0", + "kind": "memory", + "score": 0.9982662796974182, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + }, + { + "expansion_handle": "memory:01M1X6FCDYEQS880XHFGF7HHKN", + "id": "01M1X6NF62WK8QN5TYMCAT4DA7", + "kind": "memory", + "score": 0.7844027280807495, + "summary": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 962.1621, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1423, + "mcp_result_bytes": 1522, + "wire_bytes": 1559, + "reported_used_tokens": 1522, + "working_set_bytes": 292286464, + "peak_working_set_bytes": 293208064 + }, + { + "query": "my brain file got huge again right after I compacted it", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 900.6366999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 292286464, + "peak_working_set_bytes": 293208064 + }, + { + "query": "all my FTS queries stopped returning results after I changed the tokenizer config", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 929.5967, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 292286464, + "peak_working_set_bytes": 293208064 + }, + { + "query": "something is preventing the kimetsu binary from being replaced during update", + "ranked": [ + "kimetsu-daemon-lifecycle", + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G57BG9P4RZW0F69BHBK0", + "id": "01M1X6NHXEY6JMQ8HWSQ4Z8PCB", + "kind": "memory", + "score": 0.9678457975387572, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1X6FC93AQB8S92QXHHFQGQ9", + "id": "01M1X6NHXE6Y9A3KVRSSZ0YWTM", + "kind": "memory", + "score": 0.9395453929901124, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics — mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 0.6666666666666666, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 854.7651000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1657, + "mcp_result_bytes": 1756, + "wire_bytes": 1793, + "reported_used_tokens": 1756, + "working_set_bytes": 292286464, + "peak_working_set_bytes": 293208064 + }, + { + "query": "tool call results not appearing in the context — is the semantic floor too high?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 954.7316, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 292286464, + "peak_working_set_bytes": 293208064 + }, + { + "query": "CARGO_INCREMENTAL=0 in CI prevents a class of spurious compilation errors", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCS3JS2284QAPZ6KRSQR", + "id": "01M1X6NKP5HT39BKBFJJV0Q2ZG", + "kind": "memory", + "score": 0.7995238304138184, + "summary": "project:fact - [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 923.0652, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 877, + "mcp_result_bytes": 958, + "wire_bytes": 995, + "reported_used_tokens": 958, + "working_set_bytes": 292290560, + "peak_working_set_bytes": 293208064 + }, + { + "query": "how do I check whether my Cargo workspace respects the MSRV constraint?", + "ranked": [ + "cargo-msrv", + "cargo-dev-dep-leak", + "cargo-patch-section", + "cargo-target-dir-sharing" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCW109CC42VFPE46KZBB", + "id": "01M1X6NMKBQBBYJ1VC00NDWWHB", + "kind": "memory", + "score": 0.9921064376831056, + "summary": "project:fact - [tags: cargo rust msrv edition compatibility] Set `rust-version` in each `Cargo.toml` to declare the minimum supported Rust version (MSRV). Cargo enforces this with `--check`: `cargo check` fails if the toolchain is older than `rust-version`. Keep MSRV as old as your oldest supported deployment target." + }, + { + "expansion_handle": "memory:01M1X6FCQ2KKER2KYG7B2280XC", + "id": "01M1X6NMKBYHVNQYQSRZS1J8GW", + "kind": "memory", + "score": 0.887407660484314, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + }, + { + "expansion_handle": "memory:01M1X6FCV2P1VH9VAZGCJQXBTY", + "id": "01M1X6NMKBKN7F0AJ0G7V1X94K", + "kind": "memory", + "score": 0.7220955491065979, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace — including transitive deps — that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1X6FCR2A1FS53B5VE4XJM4D", + "id": "01M1X6NMKCTD7R7VR657F4H2F3", + "kind": "memory", + "score": 0.4095200598239898, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps — use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 967.4589, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2682, + "mcp_result_bytes": 2821, + "wire_bytes": 2858, + "reported_used_tokens": 2821, + "working_set_bytes": 292290560, + "peak_working_set_bytes": 293212160 + }, + { + "query": "rusqlite connection opened but ON DELETE CASCADE cascade never fires", + "ranked": [ + "sqlite-foreign-keys-default-off" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FCH5BG9VNPYFKJ7JGXMQ", + "id": "01M1X6NNH31S35MAT93V29QEP4", + "kind": "memory", + "score": 0.9922945499420166, + "summary": "project:fact - [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting — every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 900.8687, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 735, + "mcp_result_bytes": 816, + "wire_bytes": 853, + "reported_used_tokens": 816, + "working_set_bytes": 292290560, + "peak_working_set_bytes": 293212160 + }, + { + "query": "I cannot connect to kimetsu-remote — something about TLS cert validation failed", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 894.8147, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 292290560, + "peak_working_set_bytes": 293212160 + }, + { + "query": "graceful shutdown fails because in-flight SQLite queries are still running when pool closes", + "ranked": [ + "tokio-shutdown-ordering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FXM81NH999732FH0NQF1", + "id": "01M1X6NQ9AMBA92W0QPKB43H50", + "kind": "memory", + "score": 0.9996342658996582, + "summary": "project:fact - [2026-09-07] [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries — the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 896.467, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 947, + "mcp_result_bytes": 1028, + "wire_bytes": 1065, + "reported_used_tokens": 1028, + "working_set_bytes": 292290560, + "peak_working_set_bytes": 293212160 + }, + { + "query": "kimetsu-remote response takes 8 seconds — which stage is slow?", + "ranked": [ + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G1X1XGE9MM05BANVE5DQ", + "id": "01M1X6NR58JGYGDMZQCZ1AM841", + "kind": "memory", + "score": 0.9876242876052856, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking — in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize — keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 948.9598, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 858, + "mcp_result_bytes": 939, + "wire_bytes": 976, + "reported_used_tokens": 939, + "working_set_bytes": 292294656, + "peak_working_set_bytes": 293212160 + }, + { + "query": "git reflog to rescue accidentally deleted branch", + "ranked": [ + "git-reflog-rescue" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FSF4P5WNAZZCJ0H37WSZ", + "id": "01M1X6NS32EJYCNKSEECV7H59J", + "kind": "memory", + "score": 0.998464822769165, + "summary": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone — they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only — remote reflog is not accessible via normal git commands." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 981.6856, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 761, + "mcp_result_bytes": 842, + "wire_bytes": 879, + "reported_used_tokens": 842, + "working_set_bytes": 292294656, + "peak_working_set_bytes": 293212160 + }, + { + "query": "git submodule --remote advances the pinned SHA unexpectedly", + "ranked": [ + "git-submodule-pinning", + "git-reflog-rescue", + "ci-secrets-masking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FSE7P3FNP04PMXVVBWR0", + "id": "01M1X6NT1MW9VGPJJAEJTYNMH8", + "kind": "memory", + "score": 0.9998551607131958, + "summary": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip — this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version." + }, + { + "expansion_handle": "memory:01M1X6FSF4P5WNAZZCJ0H37WSZ", + "id": "01M1X6NT1MY89XJJ73Z4AR33CJ", + "kind": "memory", + "score": 0.8857361078262329, + "summary": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone — they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only — remote reflog is not accessible via normal git commands." + }, + { + "expansion_handle": "memory:01M1X6G54DHRYVMZCDFFVTEDFC", + "id": "01M1X6NT1M9X5HK93XWEXCQPE6", + "kind": "memory", + "score": 0.8434544205665588, + "summary": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output — but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 880.4419999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1771, + "mcp_result_bytes": 1888, + "wire_bytes": 1925, + "reported_used_tokens": 1888, + "working_set_bytes": 292306944, + "peak_working_set_bytes": 293212160 + }, + { + "query": "axum SSE streaming drops the last event when client disconnects", + "ranked": [ + "http-streaming-bodies" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FXSE15217ZRDGWT76DK4", + "id": "01M1X6NTXDS95XNTKQG1G2YMNK", + "kind": "memory", + "score": 0.9926375150680542, + "summary": "project:fact - [2026-09-07] [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding — a chunk may split across frame boundaries." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 958.0794000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 859, + "mcp_result_bytes": 940, + "wire_bytes": 977, + "reported_used_tokens": 940, + "working_set_bytes": 292306944, + "peak_working_set_bytes": 293220352 + }, + { + "query": "how do I detect that I am running inside a git worktree vs the main checkout?", + "ranked": [ + "git-worktree-brain-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FSA7MZHYF9BQ9B8J0E8G", + "id": "01M1X6NVV5TCGM56MG7S7F94AC", + "kind": "memory", + "score": 0.9857924580574036, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root — if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 933.8018999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 881, + "mcp_result_bytes": 962, + "wire_bytes": 999, + "reported_used_tokens": 962, + "working_set_bytes": 292306944, + "peak_working_set_bytes": 293224448 + }, + { + "query": "ONNX Runtime intra-op threads causing CPU contention during parallel bench", + "ranked": [ + "onnx-ort-threading", + "tokio-blocking-in-async" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6FS98N5KG1JWR5W9JGH3Q", + "id": "01M1X6NWRQ7MFTBS6JK3AAZH5W", + "kind": "memory", + "score": 0.9999210834503174, + "summary": "project:fact - [tags: onnx ort thread-pool parallelism cpu] ORT (ONNX Runtime) creates its own inter-op and intra-op thread pools. In a multi-process bench setup, each child inherits these pools and they compete for CPU cores. Set `SessionOptionsBuilder::with_intra_threads(1).with_inter_threads(1)` if you're running many parallel bench processes — this sacrifices per-inference throughput for lower contention." + }, + { + "expansion_handle": "memory:01M1X6FSG1SR2HKPVJWCKTW0XB", + "id": "01M1X6NWRQ6B4R9HVTN0WZYNVG", + "kind": "memory", + "score": 0.5390238761901855, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking — never call rusqlite directly from an async fn without spawn_blocking." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 875.7675, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1328, + "mcp_result_bytes": 1427, + "wire_bytes": 1464, + "reported_used_tokens": 1427, + "working_set_bytes": 292311040, + "peak_working_set_bytes": 293224448 + }, + { + "query": "what is the right way to supply AWS session token alongside access key and secret?", + "ranked": [ + "aws-credentials-chain" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6G225TRC4EVAFGXT9RHRX", + "id": "01M1X6NXKQMJY9V3RTQSRNXBCE", + "kind": "memory", + "score": 0.9493365287780762, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 910.3599, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 895, + "mcp_result_bytes": 976, + "wire_bytes": 1013, + "reported_used_tokens": 976, + "working_set_bytes": 292311040, + "peak_working_set_bytes": 293224448 + } + ], + "id": "existing-development-100", + "dimension": "retrieval", + "tier": "hard", + "score": 0.8182539682539681, + "skipped": false, + "detail": "positive-recall@4=0.84 mrr=0.85 stale-hit=n/a resolution=n/a false-injection=0.538 (n=13) positive-n=197 negative-n=13 (210 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 0.8182539682539681, + 1 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 0.8182539682539681, + "n": 1, + "ci95": null + } + }, + "overall_index": 0.8182539682539681, + "scenario_weighted_index": 0.8182539682539681 +} diff --git a/docs/audits/2026-09-07-answerability/results/development/1-candidate.stderr.log b/docs/audits/2026-09-07-answerability/results/development/1-candidate.stderr.log new file mode 100644 index 0000000..23a61c2 --- /dev/null +++ b/docs/audits/2026-09-07-answerability/results/development/1-candidate.stderr.log @@ -0,0 +1,4 @@ +brainbench: 1 scenario(s) to run + [1/1] existing-development-100 | dim=retrieval tier=hard ... + -> score=0.82 | positive-recall@4=0.84 mrr=0.85 stale-hit=n/a resolution=n/a false-injection=0.538 (n=13) positive-n=197 negative-n=13 (210 queries) +kbench brainbench: report saved -> E:\tmp\kimetsu-brain-hardening\bench\local\runs\brainbench\2026-09-07T05-53-41.7227119Z.json diff --git a/docs/audits/2026-09-07-answerability/results/development/1-candidate.stdout.log b/docs/audits/2026-09-07-answerability/results/development/1-candidate.stdout.log new file mode 100644 index 0000000..ce016b7 --- /dev/null +++ b/docs/audits/2026-09-07-answerability/results/development/1-candidate.stdout.log @@ -0,0 +1,6811 @@ +{ + "generated_at": "2026-09-07T05:53:41.7216833Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-retrieval\\development-100.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "test_env_lock inside with_user_brain_disabled deadlock", + "ranked": [ + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZHYGSE0FR8XFDPNJP6E", + "id": "01M1X6P4NJ41E6VYRRK2RW85WR", + "kind": "memory", + "score": 0.9999488592147828, + "summary": "project:fact - [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure — `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1041.7926, + "first_query": true, + "server_startup_ms": 77.6363, + "model_text_bytes": 796, + "mcp_result_bytes": 877, + "wire_bytes": 912, + "reported_used_tokens": 877, + "working_set_bytes": 227131392, + "peak_working_set_bytes": 248270848 + }, + { + "query": "why does my test hang after calling with_user_brain_disabled when I also lock test_env_lock?", + "ranked": [ + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZHYGSE0FR8XFDPNJP6E", + "id": "01M1X6P5BYS7DENZ8MWME9E285", + "kind": "memory", + "score": 0.9990190267562866, + "summary": "project:fact - [2026-09-07] [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure — `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 831.1445, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 808, + "mcp_result_bytes": 889, + "wire_bytes": 924, + "reported_used_tokens": 889, + "working_set_bytes": 229261312, + "peak_working_set_bytes": 248270848 + }, + { + "query": "ingest_repo_at_root brain_root files_root kimetsu remote", + "ranked": [ + "remote-ingest-split-roots", + "kimetsu-write-tools-gate", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZK0NCCC1P19NW0KM4XV", + "id": "01M1X6P663YP37CJB97YPSW5XX", + "kind": "memory", + "score": 0.999886393547058, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1X6P3Q18YAX03ZC3P1KS0CF", + "id": "01M1X6P663BKS4BNEZP7ABJ97P", + "kind": "memory", + "score": 0.8439717888832092, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level — disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1X6NZN0FVTV36T1B6KBFMKZ", + "id": "01M1X6P663HYJJGDC14VRNM2KA", + "kind": "memory", + "score": 0.8363722562789917, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 953.6565, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2726, + "mcp_result_bytes": 2891, + "wire_bytes": 2926, + "reported_used_tokens": 2891, + "working_set_bytes": 251510784, + "peak_working_set_bytes": 252420096 + }, + { + "query": "why does the remote server index the wrong directory when I run kimetsu brain ingest?", + "ranked": [ + "remote-ingest-split-roots", + "onnx-dim-mismatch" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZK0NCCC1P19NW0KM4XV", + "id": "01M1X6P73ZJ1Y1MY20QZRG6A3H", + "kind": "memory", + "score": 0.9836117625236512, + "summary": "project:fact - [2026-09-07] [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1X6P1TBDB4YP32AX87Z885E", + "id": "01M1X6P740BPHXQXY24YF6EJXK", + "kind": "memory", + "score": 0.3657674789428711, + "summary": "project:fact - [2026-09-07] [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results — the ANN index shape mismatch isn't always caught at runtime." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 932.8673, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1800, + "mcp_result_bytes": 1899, + "wire_bytes": 1934, + "reported_used_tokens": 1899, + "working_set_bytes": 257769472, + "peak_working_set_bytes": 258691072 + }, + { + "query": "kimetsu plugin install --remote mcp.json authorization bearer token", + "ranked": [ + "remote-mcp-host-wiring", + "mcp-stdout-protocol" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZN0FVTV36T1B6KBFMKZ", + "id": "01M1X6P81VFV1MEZKYP7BT4998", + "kind": "memory", + "score": 0.999605119228363, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + }, + { + "expansion_handle": "memory:01M1X6P2XXR71TXJXVQBRVTSF6", + "id": "01M1X6P81VFCGB7WP3B4DHDXTP", + "kind": "memory", + "score": 0.3375842869281769, + "summary": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 873.6983, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1472, + "mcp_result_bytes": 1619, + "wire_bytes": 1654, + "reported_used_tokens": 1619, + "working_set_bytes": 258248704, + "peak_working_set_bytes": 259166208 + }, + { + "query": "how do I wire a remote kimetsu brain into Claude Code without storing the token in the config file?", + "ranked": [ + "remote-mcp-host-wiring", + "mcp-tool-naming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZN0FVTV36T1B6KBFMKZ", + "id": "01M1X6P8WAJD6DB9664FQC1CFD", + "kind": "memory", + "score": 0.9963359832763672, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + }, + { + "expansion_handle": "memory:01M1X6P32468TZD6PXNN0J84CQ", + "id": "01M1X6P8WB246CNP8HFYCZMM9X", + "kind": "memory", + "score": 0.831425666809082, + "summary": "project:fact - [tags: mcp tool naming convention kimetsu] MCP tool names must be valid identifiers for all host agents. Claude Code restricts tool names to `[a-zA-Z0-9_-]` and max 64 chars. Use `snake_case` (kimetsu_brain_context, kimetsu_brain_record) — hyphen is technically allowed but some hosts reject it." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 850.6759000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1454, + "mcp_result_bytes": 1601, + "wire_bytes": 1636, + "reported_used_tokens": 1601, + "working_set_bytes": 258703360, + "peak_working_set_bytes": 259637248 + }, + { + "query": "cargo feature unification kimetsu-brain embeddings fastembed test failure", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-profile-override", + "clap-version-build-flavor" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZPZTKQEJANHCBJAH229", + "id": "01M1X6P9PYMCS2SDFRXR1MRX35", + "kind": "memory", + "score": 0.9996790885925292, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X6P0RPWS98KK91A3P03BXJ", + "id": "01M1X6P9PYDAQ2DDV6H9DHR1X6", + "kind": "memory", + "score": 0.9923595786094666, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1X6P01HC3V39QH0WT3B898R", + "id": "01M1X6P9PYN6WH69E4MHYSRZX8", + "kind": "memory", + "score": 0.585203230381012, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 832.843, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2387, + "mcp_result_bytes": 2524, + "wire_bytes": 2559, + "reported_used_tokens": 2524, + "working_set_bytes": 260403200, + "peak_working_set_bytes": 261324800 + }, + { + "query": "my integration tests pass in isolation but break when I run cargo test --workspace — embedder changed?", + "ranked": [ + "cargo-feature-unification-embeddings", + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZPZTKQEJANHCBJAH229", + "id": "01M1X6PAH1NXNPMYRMG5C3SPXY", + "kind": "memory", + "score": 0.9943140745162964, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X6P00PJD8FDG3QR2RS7QAK", + "id": "01M1X6PAH1NSABEJ3HGGVBBMHS", + "kind": "memory", + "score": 0.31398114562034607, + "summary": "project:fact - [2026-09-07] [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 895.2949, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1714, + "mcp_result_bytes": 1817, + "wire_bytes": 1852, + "reported_used_tokens": 1817, + "working_set_bytes": 261046272, + "peak_working_set_bytes": 261967872 + }, + { + "query": "build_anthropic_body bedrock-2023-05-31 InvokeModel blocking reqwest", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZRCKJJR7B15FGFKTS4W", + "id": "01M1X6PBCZHH954PAN0VFHQR4Y", + "kind": "memory", + "score": 0.9973788261413574, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X6NZXZMGW8CD9YMNHQ3ZTX", + "id": "01M1X6PBCZ8402R55XBF3DH6G7", + "kind": "memory", + "score": 0.6916899085044861, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 685.8140999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2193, + "mcp_result_bytes": 2320, + "wire_bytes": 2356, + "reported_used_tokens": 2320, + "working_set_bytes": 261664768, + "peak_working_set_bytes": 262578176 + }, + { + "query": "how do I add AWS Bedrock as a model provider in Kimetsu without pulling in the aws-sdk?", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-region-resolution", + "aws-credentials-chain", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZRCKJJR7B15FGFKTS4W", + "id": "01M1X6PC2KY9J0TA3YEF7K0N6T", + "kind": "memory", + "score": 0.9998898506164552, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X6P35QVP8D7M962ZGK168S", + "id": "01M1X6PC2KBKNCCJGFH06EGMXA", + "kind": "memory", + "score": 0.995676338672638, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X6P34CEH6KSKA057ZQRBCP", + "id": "01M1X6PC2K46WRJA4ZW1GCF4CR", + "kind": "memory", + "score": 0.987064242362976, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + }, + { + "expansion_handle": "memory:01M1X6NZXZMGW8CD9YMNHQ3ZTX", + "id": "01M1X6PC2KRQAXHW7D2RDWQW0C", + "kind": "memory", + "score": 0.9493880867958068, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 851.8489999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3455, + "mcp_result_bytes": 3618, + "wire_bytes": 3654, + "reported_used_tokens": 3618, + "working_set_bytes": 269824000, + "peak_working_set_bytes": 270741504 + }, + { + "query": "BridgeTarget enum seams plugin_install_inner plugin_status_inner resolve_setup_hosts", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZT95Z1V2RHNFPXWT53G", + "id": "01M1X6PCX3E4Y77C5S3M1CAXBG", + "kind": "memory", + "score": 0.9997583031654358, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 715.3876, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1060, + "mcp_result_bytes": 1141, + "wire_bytes": 1177, + "reported_used_tokens": 1141, + "working_set_bytes": 279863296, + "peak_working_set_bytes": 280776704 + }, + { + "query": "I added a new host to the bridge enum but cargo gives me compile errors in five different match arms — what did I miss?", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZT95Z1V2RHNFPXWT53G", + "id": "01M1X6PDKPTATYHR6BW747F46H", + "kind": "memory", + "score": 0.9977060556411744, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 927.0911, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1059, + "mcp_result_bytes": 1140, + "wire_bytes": 1176, + "reported_used_tokens": 1140, + "working_set_bytes": 280354816, + "peak_working_set_bytes": 281268224 + }, + { + "query": "Pi extension factory defineExtension agent_end session_shutdown kimetsu.ts", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZVC2YA8Q7SYT91Q3N9B", + "id": "01M1X6PEGQ700Z60T77S97PH83", + "kind": "memory", + "score": 0.9990354776382446, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 925.2431, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 804, + "mcp_result_bytes": 893, + "wire_bytes": 929, + "reported_used_tokens": 893, + "working_set_bytes": 280813568, + "peak_working_set_bytes": 281726976 + }, + { + "query": "how does Pi (earendil-works/pi) load plugins and what lifecycle hooks does it expose?", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZVC2YA8Q7SYT91Q3N9B", + "id": "01M1X6PFEEKASQ7ENMJHGC8M0J", + "kind": "memory", + "score": 0.9934834837913512, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 960.7774000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 803, + "mcp_result_bytes": 892, + "wire_bytes": 928, + "reported_used_tokens": 892, + "working_set_bytes": 280924160, + "peak_working_set_bytes": 281833472 + }, + { + "query": "aws-sigv4 SigningParams apply_to_request_http1x reqwest sign-http", + "ranked": [ + "aws-sigv4-bedrock-blocking", + "aws-presigned-urls", + "bedrock-kimetsu-provider", + "aws-credentials-chain" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZXZMGW8CD9YMNHQ3ZTX", + "id": "01M1X6PGBG3TD80KCXZEPFBDQ1", + "kind": "memory", + "score": 0.9995608925819396, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1X6P37Y0AZY1Q54BEN2E91E", + "id": "01M1X6PGBGYJZ134XRA9CNVVMD", + "kind": "memory", + "score": 0.984916627407074, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time — clock skew > 15 minutes causes `RequestTimeTooSkewed`." + }, + { + "expansion_handle": "memory:01M1X6NZRCKJJR7B15FGFKTS4W", + "id": "01M1X6PGBGP78PHX8GJ26EBMRF", + "kind": "memory", + "score": 0.983895778656006, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X6P34CEH6KSKA057ZQRBCP", + "id": "01M1X6PGBGD0QC0NEX87YY0AQW", + "kind": "memory", + "score": 0.8592692017555237, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 702.0019, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3507, + "mcp_result_bytes": 3670, + "wire_bytes": 3706, + "reported_used_tokens": 3670, + "working_set_bytes": 280940544, + "peak_working_set_bytes": 281841664 + }, + { + "query": "how do I sign a Bedrock InvokeModel request with aws-sigv4 in blocking Rust?", + "ranked": [ + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider", + "aws-region-resolution", + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZXZMGW8CD9YMNHQ3ZTX", + "id": "01M1X6PH1DCHNW5SP2ZDKPX2P1", + "kind": "memory", + "score": 0.9998323917388916, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1X6NZRCKJJR7B15FGFKTS4W", + "id": "01M1X6PH1DVAK3FRMVQT1D2XF3", + "kind": "memory", + "score": 0.9970844388008118, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X6P35QVP8D7M962ZGK168S", + "id": "01M1X6PH1D6MPMBJXVB85CDWMZ", + "kind": "memory", + "score": 0.9468621611595154, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X6P37Y0AZY1Q54BEN2E91E", + "id": "01M1X6PH1D2CWRZD80QWDWS9MW", + "kind": "memory", + "score": 0.9210098385810852, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time — clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 861.1256999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3434, + "mcp_result_bytes": 3597, + "wire_bytes": 3633, + "reported_used_tokens": 3597, + "working_set_bytes": 280940544, + "peak_working_set_bytes": 281858048 + }, + { + "query": "KIMETSU_RUNS_GC env opt-out TraceWriter create gc_old_runs caller", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZZW45G4D5CAC09350DM", + "id": "01M1X6PHWA60016YRDMPSK99BZ", + "kind": "memory", + "score": 0.999936580657959, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 812.7954, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 761, + "mcp_result_bytes": 842, + "wire_bytes": 878, + "reported_used_tokens": 842, + "working_set_bytes": 280948736, + "peak_working_set_bytes": 281862144 + }, + { + "query": "where should I put the KIMETSU_RUNS_GC=0 guard — inside the GC function or at the call site?", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZZW45G4D5CAC09350DM", + "id": "01M1X6PJNVNEGWY46DXD8KCNEK", + "kind": "memory", + "score": 0.9971211552619934, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 918.0858, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 762, + "mcp_result_bytes": 843, + "wire_bytes": 879, + "reported_used_tokens": 843, + "working_set_bytes": 281305088, + "peak_working_set_bytes": 282226688 + }, + { + "query": "git_init_boundary ProjectPaths::discover temp dir user brain isolation", + "ranked": [ + "init-project-git-boundary", + "git-worktree-brain-isolation", + "testing-temp-dirs-ci", + "kimetsu-memory-scopes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P00PJD8FDG3QR2RS7QAK", + "id": "01M1X6PKJQPJRK6MV35ARP6BG6", + "kind": "memory", + "score": 0.9997712969779968, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + }, + { + "expansion_handle": "memory:01M1X6P1YWTX8Y6NQRBF82KS3J", + "id": "01M1X6PKJQ5V5B9V7MNXWWAG08", + "kind": "memory", + "score": 0.9962491393089294, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root — if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + }, + { + "expansion_handle": "memory:01M1X6P2P2TSKGVDHTVF5Y34FZ", + "id": "01M1X6PKJQB4YC3BJCQNRV5MGV", + "kind": "memory", + "score": 0.9682154655456544, + "summary": "project:fact - [tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure." + }, + { + "expansion_handle": "memory:01M1X6P3KY5MF7R4RJB34EHA6R", + "id": "01M1X6PKJQV5ZXA9WHD931Z4HA", + "kind": "memory", + "score": 0.3057229816913605, + "summary": "project:fact - [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available — if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 792.6714999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2580, + "mcp_result_bytes": 2715, + "wire_bytes": 2751, + "reported_used_tokens": 2715, + "working_set_bytes": 281321472, + "peak_working_set_bytes": 282234880 + }, + { + "query": "my test calls init_project but it writes to the real ~/.kimetsu instead of the temp folder — why?", + "ranked": [ + "init-project-git-boundary", + "cargo-feature-unification-embeddings", + "testing-fixture-drift", + "tokio-runtime-in-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P00PJD8FDG3QR2RS7QAK", + "id": "01M1X6PMBC0K6CN0R7H1QFZ8D4", + "kind": "memory", + "score": 0.9995088577270508, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + }, + { + "expansion_handle": "memory:01M1X6NZPZTKQEJANHCBJAH229", + "id": "01M1X6PMBC7EFE0FD71088WGXW", + "kind": "memory", + "score": 0.7287850975990295, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X6P2X1Q84DSXWGJTFEX3G2", + "id": "01M1X6PMBCXSXPYGNDYZASRYDB", + "kind": "memory", + "score": 0.6596062183380127, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + }, + { + "expansion_handle": "memory:01M1X6P25SW9R97848FW6SCJ4H", + "id": "01M1X6PMBC2923TGG91H67FSTV", + "kind": "memory", + "score": 0.3297702968120575, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 916.1302000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2833, + "mcp_result_bytes": 2980, + "wire_bytes": 3016, + "reported_used_tokens": 2980, + "working_set_bytes": 281718784, + "peak_working_set_bytes": 282632192 + }, + { + "query": "clap command version KIMETSU_VERSION_DISPLAY cfg feature embeddings", + "ranked": [ + "clap-version-build-flavor", + "cargo-feature-unification-embeddings" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P01HC3V39QH0WT3B898R", + "id": "01M1X6PN8672Y3N6J01XHX8NW8", + "kind": "memory", + "score": 0.9996613264083862, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + }, + { + "expansion_handle": "memory:01M1X6NZPZTKQEJANHCBJAH229", + "id": "01M1X6PN86DBXNEKV9WWV5Y30Z", + "kind": "memory", + "score": 0.3973360061645508, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 710.3815, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1922, + "mcp_result_bytes": 2041, + "wire_bytes": 2077, + "reported_used_tokens": 2041, + "working_set_bytes": 282136576, + "peak_working_set_bytes": 283041792 + }, + { + "query": "how do I show the build flavor (lean vs embeddings) in the kimetsu --version output?", + "ranked": [ + "clap-version-build-flavor", + "cargo-feature-unification-embeddings", + "onnx-quantization-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P01HC3V39QH0WT3B898R", + "id": "01M1X6PNYDCD59RJTG9AW0VH47", + "kind": "memory", + "score": 0.9978312849998474, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + }, + { + "expansion_handle": "memory:01M1X6NZPZTKQEJANHCBJAH229", + "id": "01M1X6PNYD44EW9SX4JWCVG2QK", + "kind": "memory", + "score": 0.8926984667778015, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X6P1PMS5XEE1J3MS9A2WGM", + "id": "01M1X6PNYDC1WCG4Y0YBKP2QSA", + "kind": "memory", + "score": 0.8877003192901611, + "summary": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals — cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 928.7767, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2672, + "mcp_result_bytes": 2809, + "wire_bytes": 2845, + "reported_used_tokens": 2809, + "working_set_bytes": 282488832, + "peak_working_set_bytes": 283406336 + }, + { + "query": "Harbor pyiceberg os.getcwd stale WSL2 DrvFs worker-result subprocess re-exec", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P02RMXB3NSW1B6TJT5GQ", + "id": "01M1X6PPVYZ163D4H54254V5QH", + "kind": "memory", + "score": 0.9998155236244202, + "summary": "project:fact - [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 969.961, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1026, + "mcp_result_bytes": 1107, + "wire_bytes": 1143, + "reported_used_tokens": 1107, + "working_set_bytes": 283000832, + "peak_working_set_bytes": 283910144 + }, + { + "query": "why does my kbench sweep crash after the first trial with 'result.json missing' on WSL2?", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P02RMXB3NSW1B6TJT5GQ", + "id": "01M1X6PQSXJK2ASMX3MBSKAEAS", + "kind": "memory", + "score": 0.998451828956604, + "summary": "project:fact - [2026-09-07] [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 953.1028, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1038, + "mcp_result_bytes": 1119, + "wire_bytes": 1155, + "reported_used_tokens": 1119, + "working_set_bytes": 283144192, + "peak_working_set_bytes": 284065792 + }, + { + "query": "rusqlite VACUUM transaction WAL checkpoint wal_checkpoint TRUNCATE", + "ranked": [ + "sqlite-vacuum-wal-checkpoint", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P040MT6RZK6WACMMJ8MN", + "id": "01M1X6PRQCQTGANBN9D663EQXB", + "kind": "memory", + "score": 0.9996871948242188, + "summary": "project:fact - [tags: rust sqlite vacuum rusqlite windows] When implementing SQLite VACUUM in rusqlite: VACUUM cannot run inside a transaction. rusqlite's Connection does not hold an implicit transaction, so `conn.execute_batch(\"VACUUM;\")` works directly. After VACUUM, run `PRAGMA wal_checkpoint(TRUNCATE);` before measuring file size — on Windows the WAL file can hold significant space that isn't reflected in the main db file until the checkpoint runs." + }, + { + "expansion_handle": "memory:01M1X6P0A6JDJBPYZTYEXJX1EZ", + "id": "01M1X6PRQCGJC08GE24HB1P9Z0", + "kind": "memory", + "score": 0.5274003744125366, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 712.1927999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1507, + "mcp_result_bytes": 1610, + "wire_bytes": 1646, + "reported_used_tokens": 1610, + "working_set_bytes": 283160576, + "peak_working_set_bytes": 284065792 + }, + { + "query": "my SQLite VACUUM reports the file shrank but the disk usage stayed the same — Windows WAL?", + "ranked": [ + "sqlite-vacuum-wal-checkpoint", + "sqlite-wal-network-drive" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P040MT6RZK6WACMMJ8MN", + "id": "01M1X6PSGFDE0CVC4NQ8YP23QG", + "kind": "memory", + "score": 0.9155893921852112, + "summary": "project:fact - [tags: rust sqlite vacuum rusqlite windows] When implementing SQLite VACUUM in rusqlite: VACUUM cannot run inside a transaction. rusqlite's Connection does not hold an implicit transaction, so `conn.execute_batch(\"VACUUM;\")` works directly. After VACUUM, run `PRAGMA wal_checkpoint(TRUNCATE);` before measuring file size — on Windows the WAL file can hold significant space that isn't reflected in the main db file until the checkpoint runs." + }, + { + "expansion_handle": "memory:01M1X6P0CJBPZG72ZW2NCZ8ST6", + "id": "01M1X6PSGGE3363HPD1G3C8EEZ", + "kind": "memory", + "score": 0.902395486831665, + "summary": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1076.2671, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1357, + "mcp_result_bytes": 1460, + "wire_bytes": 1496, + "reported_used_tokens": 1460, + "working_set_bytes": 283283456, + "peak_working_set_bytes": 284200960 + }, + { + "query": "add_memory import dedup seen_ids snapshot pre-existing active memory IDs", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P04SMZJAVHMFMNBRSWBG", + "id": "01M1X6PTFMGJ7X2B75Z8RDRQMA", + "kind": "memory", + "score": 0.9999133348464966, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount — both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 815.9419999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 966, + "mcp_result_bytes": 1047, + "wire_bytes": 1083, + "reported_used_tokens": 1047, + "working_set_bytes": 283312128, + "peak_working_set_bytes": 284221440 + }, + { + "query": "brain import re-imports the same JSON file but the deduplication counter is wrong — why?", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P04SMZJAVHMFMNBRSWBG", + "id": "01M1X6PV98CM60CJFQ561PATXQ", + "kind": "memory", + "score": 0.9254016876220704, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount — both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 875.0482000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 965, + "mcp_result_bytes": 1046, + "wire_bytes": 1082, + "reported_used_tokens": 1046, + "working_set_bytes": 283320320, + "peak_working_set_bytes": 284237824 + }, + { + "query": "toml::from_str Value parse document unexpected content str.parse", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P05X9753C2E7K2XGW40Y", + "id": "01M1X6PW48CA2HAWB0F0W2M0RH", + "kind": "memory", + "score": 0.9991866946220398, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 751.6471, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 734, + "mcp_result_bytes": 815, + "wire_bytes": 851, + "reported_used_tokens": 815, + "working_set_bytes": 283340800, + "peak_working_set_bytes": 284254208 + }, + { + "query": "how do I parse a TOML configuration file into a toml::Value in toml 0.9?", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P05X9753C2E7K2XGW40Y", + "id": "01M1X6PWW1614ZFEWA9AN3YM3F", + "kind": "memory", + "score": 0.9992641806602478, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 892.8201, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 733, + "mcp_result_bytes": 814, + "wire_bytes": 850, + "reported_used_tokens": 814, + "working_set_bytes": 283348992, + "peak_working_set_bytes": 284262400 + }, + { + "query": "CIM CreationDate DMTF WMI ps etimes started_at assess_mcp_skew", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P06VVC3AVKTJXX8VPZMF", + "id": "01M1X6PXQZ1Q88PMQPF3HMQW3B", + "kind": "memory", + "score": 0.9957948923110962, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 697.8135, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 924, + "mcp_result_bytes": 1013, + "wire_bytes": 1049, + "reported_used_tokens": 1013, + "working_set_bytes": 283353088, + "peak_working_set_bytes": 284262400 + }, + { + "query": "how do I read a process start time on both Windows and Linux in pure Rust?", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P06VVC3AVKTJXX8VPZMF", + "id": "01M1X6PYEEYKVC2Q5QVZ5VH9FS", + "kind": "memory", + "score": 0.99687659740448, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 924.393, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 921, + "mcp_result_bytes": 1010, + "wire_bytes": 1046, + "reported_used_tokens": 1010, + "working_set_bytes": 283389952, + "peak_working_set_bytes": 284315648 + }, + { + "query": "processes_locking_target decide_preflight_action BufRead Write update.rs", + "ranked": [ + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P08364QKMJ37XXNKFWJS", + "id": "01M1X6PZACDB0W4NF6WWR917XD", + "kind": "memory", + "score": 0.9995336532592772, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics — mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 767.0137, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1133, + "mcp_result_bytes": 1214, + "wire_bytes": 1250, + "reported_used_tokens": 1214, + "working_set_bytes": 283435008, + "peak_working_set_bytes": 284352512 + }, + { + "query": "how should I reuse the existing process enumerator in the update preflight check to avoid a second PowerShell query?", + "ranked": [ + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P08364QKMJ37XXNKFWJS", + "id": "01M1X6Q0334Z3H809NJBJWMXZ3", + "kind": "memory", + "score": 0.9973384737968444, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics — mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 972.6356000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1132, + "mcp_result_bytes": 1213, + "wire_bytes": 1249, + "reported_used_tokens": 1213, + "working_set_bytes": 283439104, + "peak_working_set_bytes": 284356608 + }, + { + "query": "cfg_attr windows allow dead_code parse_unix_ps cross-platform tests", + "ranked": [ + "cfg-cross-platform-dead-code", + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P098A9WT8YP7XVFD6RQZ", + "id": "01M1X6Q10SWSVJH9W65RS6C4BX", + "kind": "memory", + "score": 0.9999476671218872, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + }, + { + "expansion_handle": "memory:01M1X6P06VVC3AVKTJXX8VPZMF", + "id": "01M1X6Q10S5S2JA7Z9SV34NKSR", + "kind": "memory", + "score": 0.9764312505722046, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 720.8574, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1518, + "mcp_result_bytes": 1625, + "wire_bytes": 1661, + "reported_used_tokens": 1625, + "working_set_bytes": 283607040, + "peak_working_set_bytes": 284520448 + }, + { + "query": "how do I keep a function that is only called on Unix from triggering dead_code warnings on Windows?", + "ranked": [ + "cfg-cross-platform-dead-code" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P098A9WT8YP7XVFD6RQZ", + "id": "01M1X6Q1QHQJ5S37FQ1Y9PTM70", + "kind": "memory", + "score": 0.9988092184066772, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 883.4047, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 939, + "reported_used_tokens": 903, + "working_set_bytes": 284106752, + "peak_working_set_bytes": 285020160 + }, + { + "query": "deadlocking a Rust mutex in integration tests", + "ranked": [ + "mutex-deadlock-user-brain-disabled", + "testing-serial-vs-parallel", + "kimetsu-query-stemming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZHYGSE0FR8XFDPNJP6E", + "id": "01M1X6Q2K5AY47N9S8WKXA5P63", + "kind": "memory", + "score": 0.9997490048408508, + "summary": "project:fact - [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure — `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + }, + { + "expansion_handle": "memory:01M1X6P2SBPNK9PEDW9QRXPYZJ", + "id": "01M1X6Q2K6GKQREJY2DA28C050", + "kind": "memory", + "score": 0.9057517647743224, + "summary": "project:fact - [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`)." + }, + { + "expansion_handle": "memory:01M1X6P3TM4S5HK5WSJCSCQQHF", + "id": "01M1X6Q2K6W5QM0V04TZVP0VS3", + "kind": "memory", + "score": 0.4889622032642365, + "summary": "project:fact - [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 851.7991000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1930, + "mcp_result_bytes": 2063, + "wire_bytes": 2099, + "reported_used_tokens": 2063, + "working_set_bytes": 284176384, + "peak_working_set_bytes": 285089792 + }, + { + "query": "benchmarking retrieval quality across embedders", + "ranked": [ + "kimetsu-bench-remote-embedder-singleton", + "onnx-quantization-drift", + "cargo-feature-unification-embeddings" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3WKNTTE5DK6PSVCEB7R", + "id": "01M1X6Q3DHRES1F0E1N1ET36JG", + "kind": "memory", + "score": 0.988014280796051, + "summary": "project:fact - [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval." + }, + { + "expansion_handle": "memory:01M1X6P1PMS5XEE1J3MS9A2WGM", + "id": "01M1X6Q3DH04DDZYNGN2DWKYSG", + "kind": "memory", + "score": 0.985597550868988, + "summary": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals — cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + }, + { + "expansion_handle": "memory:01M1X6NZPZTKQEJANHCBJAH229", + "id": "01M1X6Q3DJN048Q5ZAESG44W60", + "kind": "memory", + "score": 0.5341982841491699, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 714.6012000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2537, + "mcp_result_bytes": 2658, + "wire_bytes": 2694, + "reported_used_tokens": 2658, + "working_set_bytes": 284479488, + "peak_working_set_bytes": 285388800 + }, + { + "query": "process memory working set RSS peak measurement Windows", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 897.4518, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 285048832, + "peak_working_set_bytes": 285941760 + }, + { + "query": "cloning a git repository server-side into a managed checkout", + "ranked": [ + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZK0NCCC1P19NW0KM4XV", + "id": "01M1X6Q5084AVSEZT7WWZR1Z65", + "kind": "memory", + "score": 0.9466677904129028, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 753.7226, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1261, + "mcp_result_bytes": 1342, + "wire_bytes": 1378, + "reported_used_tokens": 1342, + "working_set_bytes": 285335552, + "peak_working_set_bytes": 286240768 + }, + { + "query": "SigV4 signing HTTP requests in Rust", + "ranked": [ + "aws-presigned-urls", + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P37Y0AZY1Q54BEN2E91E", + "id": "01M1X6Q5R5HX43J2NRDB9ES28H", + "kind": "memory", + "score": 0.9992632269859314, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time — clock skew > 15 minutes causes `RequestTimeTooSkewed`." + }, + { + "expansion_handle": "memory:01M1X6NZXZMGW8CD9YMNHQ3ZTX", + "id": "01M1X6Q5R5DJ3V3ANBSJA6HR5S", + "kind": "memory", + "score": 0.9991399049758912, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1X6NZRCKJJR7B15FGFKTS4W", + "id": "01M1X6Q5R5H3BPT0PSCE8WDMAH", + "kind": "memory", + "score": 0.9803794622421264, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 0.5, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 888.06, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2840, + "mcp_result_bytes": 2985, + "wire_bytes": 3021, + "reported_used_tokens": 2985, + "working_set_bytes": 285712384, + "peak_working_set_bytes": 286609408 + }, + { + "query": "cargo test --workspace feature flag changes broke my unit tests", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-dev-dep-leak", + "ci-flaky-quarantine" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZPZTKQEJANHCBJAH229", + "id": "01M1X6Q6MG35D9MDVVHPMMQ0WZ", + "kind": "memory", + "score": 0.997899889945984, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X6P0NMHGDXYV47S0SSW2XA", + "id": "01M1X6Q6MGQ0XAMJEBH0E3CJ65", + "kind": "memory", + "score": 0.9901249408721924, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + }, + { + "expansion_handle": "memory:01M1X6P3H0TD9NJ1PFEC45G42A", + "id": "01M1X6Q6MGTGCZDXKYM25RGF4H", + "kind": "memory", + "score": 0.835382342338562, + "summary": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal — a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 778.2959000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2383, + "mcp_result_bytes": 2504, + "wire_bytes": 2540, + "reported_used_tokens": 2504, + "working_set_bytes": 285741056, + "peak_working_set_bytes": 286654464 + }, + { + "query": "how do I make pasta carbonara?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 770.7376, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 285802496, + "peak_working_set_bytes": 286715904 + }, + { + "query": "what is the offside rule in football?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 955.6245, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 285822976, + "peak_working_set_bytes": 286732288 + }, + { + "query": "best way to train for a half marathon", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 970.3988, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 286015488, + "peak_working_set_bytes": 286937088 + }, + { + "query": "my test passes when I run it alone but fails under cargo test --workspace", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZPZTKQEJANHCBJAH229", + "id": "01M1X6QA0DP5QJJNW1WW166JZQ", + "kind": "memory", + "score": 0.9907942414283752, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X6P0NMHGDXYV47S0SSW2XA", + "id": "01M1X6QA0DCCX41CZ6JHSZ211Z", + "kind": "memory", + "score": 0.986136794090271, + "summary": "project:fact - [2026-09-07] [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 921.6551, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1863, + "mcp_result_bytes": 1966, + "wire_bytes": 2002, + "reported_used_tokens": 1966, + "working_set_bytes": 286593024, + "peak_working_set_bytes": 287510528 + }, + { + "query": "all the project tests started hanging forever after I added my new test", + "ranked": [ + "cargo-feature-unification-embeddings", + "tokio-runtime-in-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZPZTKQEJANHCBJAH229", + "id": "01M1X6QAXCJBYJGEDARG6W3GXE", + "kind": "memory", + "score": 0.774284839630127, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X6P25SW9R97848FW6SCJ4H", + "id": "01M1X6QAXCR30T5MA5A2MQ31YC", + "kind": "memory", + "score": 0.33030807971954346, + "summary": "project:fact - [2026-09-07] [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 964.7365, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1763, + "mcp_result_bytes": 1874, + "wire_bytes": 1910, + "reported_used_tokens": 1874, + "working_set_bytes": 286629888, + "peak_working_set_bytes": 287539200 + }, + { + "query": "my integration test silently wrote memories into my real home brain instead of the temp workspace", + "ranked": [ + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P00PJD8FDG3QR2RS7QAK", + "id": "01M1X6QBVCPPRECMCGWFWGEK73", + "kind": "memory", + "score": 0.9922831654548644, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 876.4055, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 780, + "mcp_result_bytes": 861, + "wire_bytes": 897, + "reported_used_tokens": 861, + "working_set_bytes": 286683136, + "peak_working_set_bytes": 287600640 + }, + { + "query": "where should the env-var opt-out check live for a cleanup feature triggered from a hot code path", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZZW45G4D5CAC09350DM", + "id": "01M1X6QCPH2FGN3B2NB5X6E45M", + "kind": "memory", + "score": 0.9952055215835572, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 931.1973, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 761, + "mcp_result_bytes": 842, + "wire_bytes": 878, + "reported_used_tokens": 842, + "working_set_bytes": 286699520, + "peak_working_set_bytes": 287617024 + }, + { + "query": "the brain database file stays huge on Windows even after deleting most rows", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 937.8179, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 286703616, + "peak_working_set_bytes": 287625216 + }, + { + "query": "re-importing the same exported memories file counts them as new instead of deduplicated", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P04SMZJAVHMFMNBRSWBG", + "id": "01M1X6QEH2A1JHRNSCBZRS5V8H", + "kind": "memory", + "score": 0.9878425598144532, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount — both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 899.7212, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 965, + "mcp_result_bytes": 1046, + "wire_bytes": 1082, + "reported_used_tokens": 1046, + "working_set_bytes": 286711808, + "peak_working_set_bytes": 287629312 + }, + { + "query": "a helper function only called on Unix at runtime fails the dead-code lint on the Windows build", + "ranked": [ + "cfg-cross-platform-dead-code", + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P098A9WT8YP7XVFD6RQZ", + "id": "01M1X6QFD685VJJJ0DCHTREY0Y", + "kind": "memory", + "score": 0.9971064925193788, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + }, + { + "expansion_handle": "memory:01M1X6P08364QKMJ37XXNKFWJS", + "id": "01M1X6QFD6EJ9E0YQMCAPWSJQY", + "kind": "memory", + "score": 0.427912950515747, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics — mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 874.7296, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1728, + "mcp_result_bytes": 1827, + "wire_bytes": 1863, + "reported_used_tokens": 1827, + "working_set_bytes": 286851072, + "peak_working_set_bytes": 287772672 + }, + { + "query": "the second Terminal-Bench trial always crashes even though the first one passes", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P02RMXB3NSW1B6TJT5GQ", + "id": "01M1X6QG8H7Y1WF3M7TTXSKQYK", + "kind": "memory", + "score": 0.9963042736053468, + "summary": "project:fact - [2026-09-07] [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 912.2396, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1038, + "mcp_result_bytes": 1119, + "wire_bytes": 1155, + "reported_used_tokens": 1119, + "working_set_bytes": 286859264, + "peak_working_set_bytes": 287772672 + }, + { + "query": "how does doctor tell a running MCP server process is older than the kimetsu binary on disk", + "ranked": [ + "kimetsu-daemon-lifecycle", + "process-start-time-cross-platform", + "mcp-env-propagation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3J1H7C1NPH0T2G7FWCC", + "id": "01M1X6QH54YGPZ9RKASYBJDWKW", + "kind": "memory", + "score": 0.9985345602035522, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1X6P06VVC3AVKTJXX8VPZMF", + "id": "01M1X6QH54MEX6W80GW2WGCJ4B", + "kind": "memory", + "score": 0.9438157677650452, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + }, + { + "expansion_handle": "memory:01M1X6P302K2RV969H5CEVQWE9", + "id": "01M1X6QH54YTF26WDGC444GAGF", + "kind": "memory", + "score": 0.33611738681793213, + "summary": "project:fact - [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment — changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 0.5, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 913.6339999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1936, + "mcp_result_bytes": 2061, + "wire_bytes": 2097, + "reported_used_tokens": 2061, + "working_set_bytes": 286867456, + "peak_working_set_bytes": 287784960 + }, + { + "query": "the self-update preflight needs the list of running kimetsu processes without re-running the OS query", + "ranked": [ + "windows-update-process-locking", + "kimetsu-daemon-lifecycle" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P08364QKMJ37XXNKFWJS", + "id": "01M1X6QJ1Z85QNYBJ9FARJ7ZQ1", + "kind": "memory", + "score": 0.9972410202026368, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics — mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + }, + { + "expansion_handle": "memory:01M1X6P3J1H7C1NPH0T2G7FWCC", + "id": "01M1X6QJ1ZM3XF98QRDY10B1MK", + "kind": "memory", + "score": 0.8902595043182373, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 886.4286, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1658, + "mcp_result_bytes": 1757, + "wire_bytes": 1793, + "reported_used_tokens": 1757, + "working_set_bytes": 286998528, + "peak_working_set_bytes": 287899648 + }, + { + "query": "parsing the WMI DMTF CreationDate timestamp into epoch seconds without extra crates", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P06VVC3AVKTJXX8VPZMF", + "id": "01M1X6QJXE2ZAJSV08FEVJJZK9", + "kind": "memory", + "score": 0.9258026480674744, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 978.8485999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 924, + "mcp_result_bytes": 1013, + "wire_bytes": 1049, + "reported_used_tokens": 1013, + "working_set_bytes": 286998528, + "peak_working_set_bytes": 287911936 + }, + { + "query": "calling Bedrock InvokeModel from blocking reqwest without the aws sdk", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking", + "aws-region-resolution", + "aws-retry-throttling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZRCKJJR7B15FGFKTS4W", + "id": "01M1X6QKW1QXGDFAQQKM7MSZT0", + "kind": "memory", + "score": 0.9991798996925354, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X6NZXZMGW8CD9YMNHQ3ZTX", + "id": "01M1X6QKW1CNRKN4MJ7ETVTS4K", + "kind": "memory", + "score": 0.999082326889038, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1X6P35QVP8D7M962ZGK168S", + "id": "01M1X6QKW1RNPKHD09ZBJGF8D0", + "kind": "memory", + "score": 0.8391201496124268, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X6P36TQNTE0Z7YTV5BM0YV", + "id": "01M1X6QKW18W0XNXE7V5S1H7PX", + "kind": "memory", + "score": 0.4906356632709503, + "summary": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with ±25% jitter." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 939.9555, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3330, + "mcp_result_bytes": 3509, + "wire_bytes": 3545, + "reported_used_tokens": 3509, + "working_set_bytes": 287002624, + "peak_working_set_bytes": 287920128 + }, + { + "query": "how do I rotate the encryption key protecting the kimetsu brain database", + "ranked": [ + "kimetsu-eval-fixture-shape" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3XM0BN6GY22JB0THAHB", + "id": "01M1X6QMSC4C8S8712N6YJZGKB", + "kind": "memory", + "score": 0.8046634197235107, + "summary": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` — a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases)." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 909.9994, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 817, + "mcp_result_bytes": 942, + "wire_bytes": 978, + "reported_used_tokens": 942, + "working_set_bytes": 287260672, + "peak_working_set_bytes": 288169984 + }, + { + "query": "which tokio runtime worker-thread settings does the kimetsu MCP server use", + "ranked": [ + "tokio-blocking-in-async", + "tokio-runtime-in-tests", + "mcp-stdout-protocol" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P24PG6KXJZQB4K5G8MKX", + "id": "01M1X6QNP6B0HTFG3HM08PF75S", + "kind": "memory", + "score": 0.9973159432411194, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking — never call rusqlite directly from an async fn without spawn_blocking." + }, + { + "expansion_handle": "memory:01M1X6P25SW9R97848FW6SCJ4H", + "id": "01M1X6QNP684HZGWS62F1BW59M", + "kind": "memory", + "score": 0.8583173155784607, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + }, + { + "expansion_handle": "memory:01M1X6P2XXR71TXJXVQBRVTSF6", + "id": "01M1X6QNP68PA77TP0AFB9QKRT", + "kind": "memory", + "score": 0.8141786456108093, + "summary": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 951.3683, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1847, + "mcp_result_bytes": 1972, + "wire_bytes": 2008, + "reported_used_tokens": 1972, + "working_set_bytes": 287969280, + "peak_working_set_bytes": 288882688 + }, + { + "query": "how does kimetsu sync memories between two machines over the network", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 874.6456000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288296960, + "peak_working_set_bytes": 289210368 + }, + { + "query": "recovering a corrupted usearch ANN index after a power loss", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 816.6948000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288579584, + "peak_working_set_bytes": 289492992 + }, + { + "query": "what postgres schema should I use to store kimetsu memories", + "ranked": [ + "kimetsu-memory-scopes", + "testing-fixture-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3KY5MF7R4RJB34EHA6R", + "id": "01M1X6QR8KC84KS2DMV0K2JAS3", + "kind": "memory", + "score": 0.9890244603157043, + "summary": "project:fact - [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available — if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope." + }, + { + "expansion_handle": "memory:01M1X6P2X1Q84DSXWGJTFEX3G2", + "id": "01M1X6QR8K60PGMKRGKAN6N1JC", + "kind": "memory", + "score": 0.8922504782676697, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 897.8376999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1389, + "mcp_result_bytes": 1488, + "wire_bytes": 1524, + "reported_used_tokens": 1488, + "working_set_bytes": 288731136, + "peak_working_set_bytes": 289632256 + }, + { + "query": "the whole CI job just froze forever with no failure output after my latest test PR", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 893.2662, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288804864, + "peak_working_set_bytes": 289722368 + }, + { + "query": "running the test suite left junk state in my home directory", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 936.0711, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 289206272, + "peak_working_set_bytes": 290123776 + }, + { + "query": "I deleted a bunch of old rows but the file on disk is still the same size", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 893.7782, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 289599488, + "peak_working_set_bytes": 290516992 + }, + { + "query": "adding one new crate quietly changed how the whole workspace builds", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-lockfile-drift", + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZPZTKQEJANHCBJAH229", + "id": "01M1X6QVSPVW4P30PY0T569ZH2", + "kind": "memory", + "score": 0.9941080808639526, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X6P0KJTZ6A9CZ4Q0SNH3G8", + "id": "01M1X6QVSPC1B6W5HYKME18V7R", + "kind": "memory", + "score": 0.9717232584953308, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this — it errors on any lockfile diff." + }, + { + "expansion_handle": "memory:01M1X6P0NMHGDXYV47S0SSW2XA", + "id": "01M1X6QVSPEWH5ZS5PT8ETQQFT", + "kind": "memory", + "score": 0.9183088541030884, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 913.8888, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2374, + "mcp_result_bytes": 2495, + "wire_bytes": 2531, + "reported_used_tokens": 2495, + "working_set_bytes": 289632256, + "peak_working_set_bytes": 290553856 + }, + { + "query": "we cannot pull an async runtime into the agent just to talk to AWS", + "ranked": [ + "tokio-blocking-in-async", + "tokio-runtime-in-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P24PG6KXJZQB4K5G8MKX", + "id": "01M1X6QWPM9P3ZKMBQDPJ0Y411", + "kind": "memory", + "score": 0.7520647644996643, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking — never call rusqlite directly from an async fn without spawn_blocking." + }, + { + "expansion_handle": "memory:01M1X6P25SW9R97848FW6SCJ4H", + "id": "01M1X6QWPMJ9TGA01SZW8Q738Q", + "kind": "memory", + "score": 0.7233642935752869, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 968.2756999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1369, + "mcp_result_bytes": 1476, + "wire_bytes": 1512, + "reported_used_tokens": 1476, + "working_set_bytes": 289648640, + "peak_working_set_bytes": 290557952 + }, + { + "query": "users should be able to tell which build variant they installed from the version output", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 955.7077, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 289669120, + "peak_working_set_bytes": 290586624 + }, + { + "query": "what gotchas should I expect writing process-inspection code that works on both Windows and Unix?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 882.8116, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 289714176, + "peak_working_set_bytes": 290635776 + }, + { + "query": "why might tests behave differently on my machine than in the full CI run?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 871.5808000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 289759232, + "peak_working_set_bytes": 290676736 + }, + { + "query": "what do I need to know before wiring kimetsu into a brand new host agent?", + "ranked": [ + "bridge-target-enum-seams", + "kimetsu-daemon-lifecycle", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZT95Z1V2RHNFPXWT53G", + "id": "01M1X6R09EZCHYMRT28N89PGG9", + "kind": "memory", + "score": 0.9741999506950378, + "summary": "project:fact - [2026-09-07] [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + }, + { + "expansion_handle": "memory:01M1X6P3J1H7C1NPH0T2G7FWCC", + "id": "01M1X6R09E4GSGTZQ1BTMPCMK2", + "kind": "memory", + "score": 0.9637662768363952, + "summary": "project:fact - [2026-09-07] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1X6NZN0FVTV36T1B6KBFMKZ", + "id": "01M1X6R09E57Q5RXKBBMHNHMVE", + "kind": "memory", + "score": 0.4149944484233856, + "summary": "project:fact - [2026-09-07] [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 0.6666666666666666, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 936.9214000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2390, + "mcp_result_bytes": 2555, + "wire_bytes": 2591, + "reported_used_tokens": 2555, + "working_set_bytes": 289759232, + "peak_working_set_bytes": 290676736 + }, + { + "query": "tell me everything relevant to running kimetsu against AWS", + "ranked": [ + "kimetsu-mrr-metric", + "aws-credentials-chain", + "cargo-feature-unification-embeddings", + "kimetsu-eval-fixture-shape" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3YTF6X09YDCSHS6P0JQ", + "id": "01M1X6R16KQ5G5DB8R6NSHZ8SB", + "kind": "memory", + "score": 0.984548270702362, + "summary": "project:fact - [tags: kimetsu bench mrr recall metrics evaluation] kimetsu bench reports MRR (Mean Reciprocal Rank) and Recall@K. MRR is 1/rank_of_first_relevant_result, averaged across cases; it penalizes models that rank the correct answer 2nd or 3rd. Recall@K is the fraction of cases where at least one relevant answer appears in the top K." + }, + { + "expansion_handle": "memory:01M1X6P34CEH6KSKA057ZQRBCP", + "id": "01M1X6R16KDZT7CJCW02J07ZV5", + "kind": "memory", + "score": 0.9737622141838074, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + }, + { + "expansion_handle": "memory:01M1X6NZPZTKQEJANHCBJAH229", + "id": "01M1X6R16KWBJYRMKN4XW7DVRM", + "kind": "memory", + "score": 0.9726329445838928, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X6P3XM0BN6GY22JB0THAHB", + "id": "01M1X6R16KKJBJ0EF02YFF3AKW", + "kind": "memory", + "score": 0.9641559720039368, + "summary": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` — a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases)." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 917.4006, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2883, + "mcp_result_bytes": 3066, + "wire_bytes": 3102, + "reported_used_tokens": 3066, + "working_set_bytes": 289787904, + "peak_working_set_bytes": 290697216 + }, + { + "query": "ingesting a cloned repo when the brain lives under a different root", + "ranked": [ + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZK0NCCC1P19NW0KM4XV", + "id": "01M1X6R23CA82V7TEESB3YNRGD", + "kind": "memory", + "score": 0.9995300769805908, + "summary": "project:fact - [2026-09-07] [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 876.21, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1274, + "mcp_result_bytes": 1355, + "wire_bytes": 1391, + "reported_used_tokens": 1355, + "working_set_bytes": 289792000, + "peak_working_set_bytes": 290701312 + }, + { + "query": "streamable-http transport entry for openclaw.json with a bearer token", + "ranked": [ + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZN0FVTV36T1B6KBFMKZ", + "id": "01M1X6R2YTFRK2H9B5YCFV3ME2", + "kind": "memory", + "score": 0.9921918511390686, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 890.6237, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 996, + "mcp_result_bytes": 1125, + "wire_bytes": 1161, + "reported_used_tokens": 1125, + "working_set_bytes": 290045952, + "peak_working_set_bytes": 290963456 + }, + { + "query": "serializing ingests with a tokio mutex to avoid checkout races", + "ranked": [ + "remote-ingest-split-roots", + "testing-serial-vs-parallel", + "tokio-select-cancellation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZK0NCCC1P19NW0KM4XV", + "id": "01M1X6R3TMVVTCK2G60AKREWGR", + "kind": "memory", + "score": 0.9795480966567992, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1X6P2SBPNK9PEDW9QRXPYZJ", + "id": "01M1X6R3TM5VWNMHX7JM7J9DSR", + "kind": "memory", + "score": 0.9425267577171326, + "summary": "project:fact - [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`)." + }, + { + "expansion_handle": "memory:01M1X6P270RXS50A66Q2YBWW4H", + "id": "01M1X6R3TMCGY67R1DW2RNWWB1", + "kind": "memory", + "score": 0.5619664192199707, + "summary": "project:fact - [tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 924.7050999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2376, + "mcp_result_bytes": 2493, + "wire_bytes": 2529, + "reported_used_tokens": 2493, + "working_set_bytes": 290058240, + "peak_working_set_bytes": 290979840 + }, + { + "query": "percent-encoding the colon in the bedrock model id for the invoke URL", + "ranked": [ + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZRCKJJR7B15FGFKTS4W", + "id": "01M1X6R4QQQAY0B8NRFCY395YE", + "kind": "memory", + "score": 0.8341025710105896, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 875.3322999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1204, + "mcp_result_bytes": 1293, + "wire_bytes": 1329, + "reported_used_tokens": 1293, + "working_set_bytes": 290062336, + "peak_working_set_bytes": 290979840 + }, + { + "query": "deduplicating re-imported memories against pre-existing ids", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P04SMZJAVHMFMNBRSWBG", + "id": "01M1X6R5K5S3JP5NTEV4Y59C7E", + "kind": "memory", + "score": 0.9991393089294434, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount — both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 947.8259, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 966, + "mcp_result_bytes": 1047, + "wire_bytes": 1083, + "reported_used_tokens": 1047, + "working_set_bytes": 290062336, + "peak_working_set_bytes": 290979840 + }, + { + "query": "parsing DMTF datetimes", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P06VVC3AVKTJXX8VPZMF", + "id": "01M1X6R6H9D420GXTDXZ4ASYJ7", + "kind": "memory", + "score": 0.9934942126274108, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 709.198, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 924, + "mcp_result_bytes": 1013, + "wire_bytes": 1049, + "reported_used_tokens": 1013, + "working_set_bytes": 290082816, + "peak_working_set_bytes": 290979840 + }, + { + "query": "how should install derive a stable identifier from the git remote URL?", + "ranked": [ + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZN0FVTV36T1B6KBFMKZ", + "id": "01M1X6R76P46Q5D5GQRKM86MG3", + "kind": "memory", + "score": 0.98285174369812, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 888.1916, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 995, + "mcp_result_bytes": 1124, + "wire_bytes": 1160, + "reported_used_tokens": 1124, + "working_set_bytes": 290082816, + "peak_working_set_bytes": 290996224 + }, + { + "query": "the secret token must not end up written into the host config file", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 940.5013, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 290086912, + "peak_working_set_bytes": 291004416 + }, + { + "query": "keep the cleanup logic unit-testable without touching environment variables", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 905.3257, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 290086912, + "peak_working_set_bytes": 291004416 + }, + { + "query": "how do we stop the server from cloning arbitrary repos clients request?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 876.8886, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 290086912, + "peak_working_set_bytes": 291004416 + }, + { + "query": "make sure a wrong guess about a host plugin API never breaks that host", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZVC2YA8Q7SYT91Q3N9B", + "id": "01M1X6RAR2RFYWVNWWE4VJQKFZ", + "kind": "memory", + "score": 0.928434193134308, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 774.3385000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 803, + "mcp_result_bytes": 892, + "wire_bytes": 928, + "reported_used_tokens": 892, + "working_set_bytes": 290140160, + "peak_working_set_bytes": 291061760 + }, + { + "query": "which wire-format trick lets us reuse the existing Anthropic request builder for AWS?", + "ranked": [ + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZRCKJJR7B15FGFKTS4W", + "id": "01M1X6RBFWVY4MRM1V10T2A5MV", + "kind": "memory", + "score": 0.9748817682266236, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 946.9912, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1203, + "mcp_result_bytes": 1292, + "wire_bytes": 1328, + "reported_used_tokens": 1292, + "working_set_bytes": 290242560, + "peak_working_set_bytes": 291160064 + }, + { + "query": "the self-update froze because something was still holding the executable", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1191.3511999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 290361344, + "peak_working_set_bytes": 291270656 + }, + { + "query": "our notes about the extension API turned out wrong once we read the actual repo", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 990.2658, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 290414592, + "peak_working_set_bytes": 291323904 + }, + { + "query": "half the benchmark trials die right after the first one finishes", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 898.4513, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 290414592, + "peak_working_set_bytes": 291332096 + }, + { + "query": "I need this parser visible to tests on every OS even though only one OS calls it", + "ranked": [ + "cfg-cross-platform-dead-code" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P098A9WT8YP7XVFD6RQZ", + "id": "01M1X6RFDT459ZETAP0KPQ5PDY", + "kind": "memory", + "score": 0.36490198969841, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 886.3231000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 939, + "reported_used_tokens": 903, + "working_set_bytes": 290463744, + "peak_working_set_bytes": 291368960 + }, + { + "query": "the config file content refuses to parse even though the TOML looks valid", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P05X9753C2E7K2XGW40Y", + "id": "01M1X6RG9G6RWKDW994V43ERXA", + "kind": "memory", + "score": 0.6614054441452026, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 918.6942, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 733, + "mcp_result_bytes": 814, + "wire_bytes": 850, + "reported_used_tokens": 814, + "working_set_bytes": 290496512, + "peak_working_set_bytes": 291414016 + }, + { + "query": "the remote server must refresh its checkout before answering file queries", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 927.0274, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 290504704, + "peak_working_set_bytes": 291422208 + }, + { + "query": "tests must not climb to a parent git repository when resolving project paths", + "ranked": [ + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P00PJD8FDG3QR2RS7QAK", + "id": "01M1X6RJ36TMCZHZXJVNK3NHST", + "kind": "memory", + "score": 0.9839988350868224, + "summary": "project:fact - [2026-09-07] [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 922.201, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 794, + "mcp_result_bytes": 875, + "wire_bytes": 911, + "reported_used_tokens": 875, + "working_set_bytes": 290508800, + "peak_working_set_bytes": 291422208 + }, + { + "query": "how do I test request signing deterministically when timestamps change every run?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 916.098, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 290652160, + "peak_working_set_bytes": 291565568 + }, + { + "query": "adding a new variant to the host target enum - which places will I forget to update?", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6NZT95Z1V2RHNFPXWT53G", + "id": "01M1X6RKWSSN92D4QCNNPV6A82", + "kind": "memory", + "score": 0.885076105594635, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 947.0763, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1058, + "mcp_result_bytes": 1139, + "wire_bytes": 1175, + "reported_used_tokens": 1139, + "working_set_bytes": 290689024, + "peak_working_set_bytes": 291602432 + }, + { + "query": "how do I enable GPU acceleration for kimetsu embedding inference", + "ranked": [ + "mcp-tool-timeouts", + "kimetsu-proactive-hooks" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P2YZ4YRDGDVJV5XMKCP1", + "id": "01M1X6RMTG3K4PTK1A2E035A77", + "kind": "memory", + "score": 0.9826309084892272, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking — in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize — keep it in a process-global `OnceLock`)." + }, + { + "expansion_handle": "memory:01M1X6P3P27FMRCY89DFW7MFKS", + "id": "01M1X6RMTGQHA4QRZ5NYCF4JPB", + "kind": "memory", + "score": 0.8807981610298157, + "summary": "project:fact - [tags: kimetsu proactive hooks context injection] kimetsu's proactive context injection runs before each agent turn (pre-turn hook) and injects relevant memories into the system prompt prefix. The hook invocation adds latency to the first token: embedding inference + vector search + reranking + context formatting. On a cold start, this can be 1-3 seconds." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 919.2233, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1377, + "mcp_result_bytes": 1476, + "wire_bytes": 1512, + "reported_used_tokens": 1476, + "working_set_bytes": 290693120, + "peak_working_set_bytes": 291602432 + }, + { + "query": "how do I throttle kimetsu API spend per month", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 876.0017, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 290717696, + "peak_working_set_bytes": 291635200 + }, + { + "query": "can the kimetsu brain database be stored in S3 instead of on disk", + "ranked": [ + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P37Y0AZY1Q54BEN2E91E", + "id": "01M1X6RPK32C71E7KF2QNWHPX9", + "kind": "memory", + "score": 0.38596054911613464, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time — clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 859.2295, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 875, + "mcp_result_bytes": 956, + "wire_bytes": 992, + "reported_used_tokens": 956, + "working_set_bytes": 290766848, + "peak_working_set_bytes": 291684352 + }, + { + "query": "how do I plug a custom tokenizer into the FTS index", + "ranked": [ + "sqlite-fts5-tokenizer" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0DKH32KYKD57N5WWCRX", + "id": "01M1X6RQDBQAH13FD1AT5GBSW0", + "kind": "memory", + "score": 0.9691632390022278, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 902.3303000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 671, + "mcp_result_bytes": 756, + "wire_bytes": 792, + "reported_used_tokens": 756, + "working_set_bytes": 290770944, + "peak_working_set_bytes": 291684352 + }, + { + "query": "what should I check when kimetsu behaves differently on Windows than on Linux?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 896.6828999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 290914304, + "peak_working_set_bytes": 291827712 + }, + { + "query": "what are the moving parts of the kimetsu remote deployment story?", + "ranked": [ + "kimetsu-write-tools-gate", + "ci-secrets-masking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3Q18YAX03ZC3P1KS0CF", + "id": "01M1X6RS5JA94A08523CDXEA76", + "kind": "memory", + "score": 0.9729357361793518, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level — disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1X6P3F12CWS8FE5S50H7TYH", + "id": "01M1X6RS5KMA7A7FAXAAX7TSHC", + "kind": "memory", + "score": 0.8412115573883057, + "summary": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output — but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 884.3661000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1410, + "mcp_result_bytes": 1509, + "wire_bytes": 1546, + "reported_used_tokens": 1509, + "working_set_bytes": 290996224, + "peak_working_set_bytes": 291913728 + }, + { + "query": "which lessons cover guarding behavior behind environment variables?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1033.2767000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 291028992, + "peak_working_set_bytes": 291934208 + }, + { + "query": "SQLite BUSY error under concurrent writes", + "ranked": [ + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0A6JDJBPYZTYEXJX1EZ", + "id": "01M1X6RV26BQJ1MYTQMV28B0S7", + "kind": "memory", + "score": 0.9978362917900084, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 799.0188, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 898, + "mcp_result_bytes": 979, + "wire_bytes": 1016, + "reported_used_tokens": 979, + "working_set_bytes": 291110912, + "peak_working_set_bytes": 292003840 + }, + { + "query": "SQLite WAL mode breaks when the database is on a network share", + "ranked": [ + "sqlite-wal-network-drive", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0CJBPZG72ZW2NCZ8ST6", + "id": "01M1X6RVTJJ04CSBSWAPMTV4NM", + "kind": "memory", + "score": 0.999302864074707, + "summary": "project:fact - [2026-09-07] [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + }, + { + "expansion_handle": "memory:01M1X6P0A6JDJBPYZTYEXJX1EZ", + "id": "01M1X6RVTJ2S9C5VHRAGNSQ14E", + "kind": "memory", + "score": 0.9966553449630736, + "summary": "project:fact - [2026-09-07] [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 926.5964, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1448, + "mcp_result_bytes": 1547, + "wire_bytes": 1584, + "reported_used_tokens": 1547, + "working_set_bytes": 291115008, + "peak_working_set_bytes": 292028416 + }, + { + "query": "my SQLite WAL database causes SQLITE_IOERR_LOCK on a mapped drive", + "ranked": [ + "sqlite-wal-network-drive" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0CJBPZG72ZW2NCZ8ST6", + "id": "01M1X6RWQSAEM9PHMBKHY906KK", + "kind": "memory", + "score": 0.99892657995224, + "summary": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 961.2746, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 748, + "mcp_result_bytes": 829, + "wire_bytes": 866, + "reported_used_tokens": 829, + "working_set_bytes": 291188736, + "peak_working_set_bytes": 292098048 + }, + { + "query": "FTS5 tokenizer configuration for Rust identifiers with underscores", + "ranked": [ + "sqlite-fts5-tokenizer", + "kimetsu-query-stemming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0DKH32KYKD57N5WWCRX", + "id": "01M1X6RXNM47BJ4ZA8VE513MXQ", + "kind": "memory", + "score": 0.998104453086853, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + }, + { + "expansion_handle": "memory:01M1X6P3TM4S5HK5WSJCSCQQHF", + "id": "01M1X6RXNNMQG9ZSJ22RG6HR2H", + "kind": "memory", + "score": 0.7023860812187195, + "summary": "project:fact - [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 892.2713, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1212, + "mcp_result_bytes": 1331, + "wire_bytes": 1368, + "reported_used_tokens": 1331, + "working_set_bytes": 291196928, + "peak_working_set_bytes": 292106240 + }, + { + "query": "I switched the FTS5 tokenizer but search stopped returning results", + "ranked": [ + "sqlite-fts5-tokenizer" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0DKH32KYKD57N5WWCRX", + "id": "01M1X6RYHXYF8ZA3YBKA5XA4V3", + "kind": "memory", + "score": 0.8194089531898499, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 962.6889, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 670, + "mcp_result_bytes": 755, + "wire_bytes": 792, + "reported_used_tokens": 755, + "working_set_bytes": 291217408, + "peak_working_set_bytes": 292126720 + }, + { + "query": "optimal SQLite page size for storing embedding vectors", + "ranked": [ + "sqlite-page-size", + "onnx-dim-mismatch", + "onnx-cosine-vs-dot" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0EN1F2M5R0H76S72S7T", + "id": "01M1X6RZFJAD704NXT455EBR43", + "kind": "memory", + "score": 0.9990121126174928, + "summary": "project:fact - [tags: sqlite page_size performance rusqlite] SQLite's default page_size is 4096 bytes. For a write-heavy brain database with large BLOB payloads (embedding vectors), raising page_size to 16384 reduces fragmentation and improves sequential scan throughput. `PRAGMA page_size = 16384;` must be set BEFORE the first table is created — changing it on an existing database requires a VACUUM afterward to rebuild all pages." + }, + { + "expansion_handle": "memory:01M1X6P1TBDB4YP32AX87Z885E", + "id": "01M1X6RZFJRA85DJ0WXG90V6CA", + "kind": "memory", + "score": 0.9881643056869508, + "summary": "project:fact - [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results — the ANN index shape mismatch isn't always caught at runtime." + }, + { + "expansion_handle": "memory:01M1X6P1SDTFE6G9SQ1JWY55C5", + "id": "01M1X6RZFJGAWSQJ1J0KKEFPNQ", + "kind": "memory", + "score": 0.9425415992736816, + "summary": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing — double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 847.7025, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1860, + "mcp_result_bytes": 1977, + "wire_bytes": 2014, + "reported_used_tokens": 1977, + "working_set_bytes": 291250176, + "peak_working_set_bytes": 292155392 + }, + { + "query": "ON DELETE CASCADE in SQLite does nothing — foreign keys not enforced", + "ranked": [ + "sqlite-foreign-keys-default-off" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0FMEPPEMPPJJ176K5CE", + "id": "01M1X6S0A5Z079XXSQWCJDQR8P", + "kind": "memory", + "score": 0.9996858835220336, + "summary": "project:fact - [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting — every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 934.907, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 736, + "mcp_result_bytes": 817, + "wire_bytes": 854, + "reported_used_tokens": 817, + "working_set_bytes": 291258368, + "peak_working_set_bytes": 292167680 + }, + { + "query": "indexing a JSON metadata column in SQLite without a schema migration", + "ranked": [ + "sqlite-json1-extract", + "testing-fixture-drift", + "onnx-dim-mismatch", + "sqlite-partial-index" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0GGPA6E0F0TZYGKXDT9", + "id": "01M1X6S17B0WRPHZJP2H8JPNV1", + "kind": "memory", + "score": 0.9955366849899292, + "summary": "project:fact - [tags: sqlite json1 json_extract rusqlite] SQLite's json1 extension (built in since 3.38.0) lets you index and query JSONB columns with `json_extract(col, '$.field')`. To create a partial index over a JSON field: `CREATE INDEX idx ON memories (json_extract(metadata, '$.scope')) WHERE json_extract(metadata, '$.scope') IS NOT NULL;`. Use `json_each` for array fields." + }, + { + "expansion_handle": "memory:01M1X6P2X1Q84DSXWGJTFEX3G2", + "id": "01M1X6S17BG6KTDNQ00FN6K67Y", + "kind": "memory", + "score": 0.8227390646934509, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + }, + { + "expansion_handle": "memory:01M1X6P1TBDB4YP32AX87Z885E", + "id": "01M1X6S17BPS5649MT8R5PXT2V", + "kind": "memory", + "score": 0.38374292850494385, + "summary": "project:fact - [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results — the ANN index shape mismatch isn't always caught at runtime." + }, + { + "expansion_handle": "memory:01M1X6P0JKGMF00QG34TX11P9N", + "id": "01M1X6S17BJSYFX5KEGEG1YRE1", + "kind": "memory", + "score": 0.3276048004627228, + "summary": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query — the planner uses the partial index only when the WHERE clause matches." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 879.3746, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2381, + "mcp_result_bytes": 2516, + "wire_bytes": 2553, + "reported_used_tokens": 2516, + "working_set_bytes": 291332096, + "peak_working_set_bytes": 292241408 + }, + { + "query": "prepare() vs prepare_cached() in rusqlite hot insert loop", + "ranked": [ + "sqlite-prepared-stmt-cache" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0HK639ECPAWR156DDPZ", + "id": "01M1X6S22XGCKDAYRM3WCPCRY0", + "kind": "memory", + "score": 0.9993672966957092, + "summary": "project:fact - [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 882.1865, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 689, + "mcp_result_bytes": 770, + "wire_bytes": 807, + "reported_used_tokens": 770, + "working_set_bytes": 291356672, + "peak_working_set_bytes": 292261888 + }, + { + "query": "speed up bulk memory ingest by caching SQL statements", + "ranked": [ + "sqlite-prepared-stmt-cache" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0HK639ECPAWR156DDPZ", + "id": "01M1X6S2YNGS3HSJQTS6HBW89Q", + "kind": "memory", + "score": 0.9823396801948548, + "summary": "project:fact - [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 896.6949, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 688, + "mcp_result_bytes": 769, + "wire_bytes": 806, + "reported_used_tokens": 769, + "working_set_bytes": 291409920, + "peak_working_set_bytes": 292315136 + }, + { + "query": "partial index on deleted_at IS NULL for faster active memory queries", + "ranked": [ + "sqlite-partial-index" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0JKGMF00QG34TX11P9N", + "id": "01M1X6S3TGK6GV321CNK17YPWK", + "kind": "memory", + "score": 0.9988954067230223, + "summary": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query — the planner uses the partial index only when the WHERE clause matches." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 900.2224, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 794, + "mcp_result_bytes": 875, + "wire_bytes": 912, + "reported_used_tokens": 875, + "working_set_bytes": 291786752, + "peak_working_set_bytes": 292687872 + }, + { + "query": "the brain query is slow because it scans all rows including soft-deleted ones", + "ranked": [ + "sqlite-partial-index" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0JKGMF00QG34TX11P9N", + "id": "01M1X6S4PNTHK9Q8C8HWGM8H4A", + "kind": "memory", + "score": 0.5760471224784851, + "summary": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query — the planner uses the partial index only when the WHERE clause matches." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 943.4644, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 793, + "mcp_result_bytes": 874, + "wire_bytes": 911, + "reported_used_tokens": 874, + "working_set_bytes": 291803136, + "peak_working_set_bytes": 292720640 + }, + { + "query": "Cargo.lock changed unexpectedly after adding a new workspace crate", + "ranked": [ + "cargo-lockfile-drift", + "cargo-feature-unification-embeddings", + "cargo-target-dir-sharing", + "cargo-patch-section" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0KJTZ6A9CZ4Q0SNH3G8", + "id": "01M1X6S5M3QFMWHG68CMCBXPD8", + "kind": "memory", + "score": 0.9991374015808104, + "summary": "project:fact - [2026-09-07] [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this — it errors on any lockfile diff." + }, + { + "expansion_handle": "memory:01M1X6NZPZTKQEJANHCBJAH229", + "id": "01M1X6S5M38W5G9YPGECNV1Q36", + "kind": "memory", + "score": 0.9968542456626892, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1X6P0PQ9PDB9P815QJTY464", + "id": "01M1X6S5M30H983CAXM3RZVVA5", + "kind": "memory", + "score": 0.9829630851745604, + "summary": "project:fact - [2026-09-07] [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps — use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + }, + { + "expansion_handle": "memory:01M1X6P0SRM17TYNHXSXEWSP31", + "id": "01M1X6S5M3GEQ45GR3M17K65EW", + "kind": "memory", + "score": 0.9262890815734864, + "summary": "project:fact - [2026-09-07] [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace — including transitive deps — that depend on `my-crate`. Remove the patch before publishing." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 800.9239, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3010, + "mcp_result_bytes": 3153, + "wire_bytes": 3190, + "reported_used_tokens": 3153, + "working_set_bytes": 291868672, + "peak_working_set_bytes": 292786176 + }, + { + "query": "how do I prevent CI from accepting a modified lockfile silently?", + "ranked": [ + "cargo-lockfile-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0KJTZ6A9CZ4Q0SNH3G8", + "id": "01M1X6S6DE4VMWCZHM5DJRAFT2", + "kind": "memory", + "score": 0.9125379323959352, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this — it errors on any lockfile diff." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 951.2239999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 765, + "mcp_result_bytes": 846, + "wire_bytes": 883, + "reported_used_tokens": 846, + "working_set_bytes": 292192256, + "peak_working_set_bytes": 293109760 + }, + { + "query": "build.rs reruns on every incremental build even when nothing changed", + "ranked": [ + "cargo-build-script-rerun" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0MFFWR9VFS6MSBGG2HS", + "id": "01M1X6S7BDKKSTWFV4PW7WEQYW", + "kind": "memory", + "score": 0.9996689558029176, + "summary": "project:fact - [2026-09-07] [tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 950.8377, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 698, + "mcp_result_bytes": 779, + "wire_bytes": 816, + "reported_used_tokens": 779, + "working_set_bytes": 292401152, + "peak_working_set_bytes": 293318656 + }, + { + "query": "incremental cargo build is slow because build script runs every time", + "ranked": [ + "cargo-build-script-rerun" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0MFFWR9VFS6MSBGG2HS", + "id": "01M1X6S88QK3GYJN4GN4EBZVXA", + "kind": "memory", + "score": 0.9978280663490297, + "summary": "project:fact - [tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 889.9504000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 685, + "mcp_result_bytes": 766, + "wire_bytes": 803, + "reported_used_tokens": 766, + "working_set_bytes": 292438016, + "peak_working_set_bytes": 293351424 + }, + { + "query": "a dev-dependency is activating an embeddings feature in my production build", + "ranked": [ + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0NMHGDXYV47S0SSW2XA", + "id": "01M1X6S94P0G091PW51J4GXE2C", + "kind": "memory", + "score": 0.9944193959236144, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 908.3688, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 931, + "mcp_result_bytes": 1012, + "wire_bytes": 1049, + "reported_used_tokens": 1012, + "working_set_bytes": 292507648, + "peak_working_set_bytes": 293421056 + }, + { + "query": "how do I prevent a test-only feature from bleeding into the non-test compilation?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 878.5384, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 292536320, + "peak_working_set_bytes": 293449728 + }, + { + "query": "linker errors in target/ caused by antivirus holding the exe file", + "ranked": [ + "windows-file-locking-av", + "cargo-target-dir-sharing" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1FEZNYXN0DQN55611JR", + "id": "01M1X6SAWKR4KYQXKT1AFD7DX6", + "kind": "memory", + "score": 0.9997633099555968, + "summary": "project:fact - [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + }, + { + "expansion_handle": "memory:01M1X6P0PQ9PDB9P815QJTY464", + "id": "01M1X6SAWK78JSP78BZ7TSBA5G", + "kind": "memory", + "score": 0.7463976740837097, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps — use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 903.6118, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1523, + "mcp_result_bytes": 1622, + "wire_bytes": 1659, + "reported_used_tokens": 1622, + "working_set_bytes": 292642816, + "peak_working_set_bytes": 293552128 + }, + { + "query": "Access is denied (os error 5) when linking on Windows — how do I fix this?", + "ranked": [ + "windows-file-locking-av" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1FEZNYXN0DQN55611JR", + "id": "01M1X6SBRPZ0H97FMKMXGYWRGG", + "kind": "memory", + "score": 0.9977193474769592, + "summary": "project:fact - [2026-09-07] [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 916.1673000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 769, + "mcp_result_bytes": 850, + "wire_bytes": 887, + "reported_used_tokens": 850, + "working_set_bytes": 292700160, + "peak_working_set_bytes": 293609472 + }, + { + "query": "incremental build broke with a type mismatch after switching branches", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0QQQS1JZYGKXH9ZX9RW", + "id": "01M1X6SCNDPP15MN74NQH7QR15", + "kind": "memory", + "score": 0.7971777319908142, + "summary": "project:fact - [2026-09-07] [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 914.3299000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 890, + "mcp_result_bytes": 971, + "wire_bytes": 1008, + "reported_used_tokens": 971, + "working_set_bytes": 292745216, + "peak_working_set_bytes": 293658624 + }, + { + "query": "cargo reports a type error that references a type not in the codebase", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0QQQS1JZYGKXH9ZX9RW", + "id": "01M1X6SDJ0MV9AN968QKR4RH5S", + "kind": "memory", + "score": 0.7925198078155518, + "summary": "project:fact - [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 855.4866, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 877, + "mcp_result_bytes": 958, + "wire_bytes": 995, + "reported_used_tokens": 958, + "working_set_bytes": 292835328, + "peak_working_set_bytes": 293756928 + }, + { + "query": "compile fastembed at O2 in debug builds to avoid slow embedding inference", + "ranked": [ + "cargo-profile-override", + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0RPWS98KK91A3P03BXJ", + "id": "01M1X6SED3A9KY44N7AZWH1CXC", + "kind": "memory", + "score": 0.9932281374931335, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1X6P2YZ4YRDGDVJV5XMKCP1", + "id": "01M1X6SED3493V0PMG5A9P8TM7", + "kind": "memory", + "score": 0.987656831741333, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking — in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize — keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 933.6553, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1322, + "mcp_result_bytes": 1421, + "wire_bytes": 1458, + "reported_used_tokens": 1421, + "working_set_bytes": 292950016, + "peak_working_set_bytes": 293867520 + }, + { + "query": "override compilation profile for a single crate in a Cargo workspace", + "ranked": [ + "cargo-patch-section", + "cargo-profile-override", + "cargo-target-dir-sharing", + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0SRM17TYNHXSXEWSP31", + "id": "01M1X6SFAG6DPHKJXAJ03TYVFX", + "kind": "memory", + "score": 0.9984123706817628, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace — including transitive deps — that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1X6P0RPWS98KK91A3P03BXJ", + "id": "01M1X6SFAG69KA5H9MT7Z389ST", + "kind": "memory", + "score": 0.9979992508888244, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1X6P0PQ9PDB9P815QJTY464", + "id": "01M1X6SFAGV7ACW2MCDA9FVV3V", + "kind": "memory", + "score": 0.9956549406051636, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps — use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + }, + { + "expansion_handle": "memory:01M1X6P0NMHGDXYV47S0SSW2XA", + "id": "01M1X6SFAG9VMZGEBR2E90BPHW", + "kind": "memory", + "score": 0.9820712208747864, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 0.5, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 974.1467, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2682, + "mcp_result_bytes": 2821, + "wire_bytes": 2858, + "reported_used_tokens": 2821, + "working_set_bytes": 293011456, + "peak_working_set_bytes": 293933056 + }, + { + "query": "[patch.crates-io] workspace dependency override", + "ranked": [ + "cargo-patch-section", + "cargo-lockfile-drift", + "cargo-dev-dep-leak", + "cargo-target-dir-sharing" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0SRM17TYNHXSXEWSP31", + "id": "01M1X6SGDRT4S592AZAW14SBFW", + "kind": "memory", + "score": 0.9999405145645142, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace — including transitive deps — that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1X6P0KJTZ6A9CZ4Q0SNH3G8", + "id": "01M1X6SGDRMH0HEGAPZEQT0RKG", + "kind": "memory", + "score": 0.9975811243057252, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this — it errors on any lockfile diff." + }, + { + "expansion_handle": "memory:01M1X6P0NMHGDXYV47S0SSW2XA", + "id": "01M1X6SGDRQAC75SSE6KH0TW0G", + "kind": "memory", + "score": 0.994149684906006, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + }, + { + "expansion_handle": "memory:01M1X6P0PQ9PDB9P815QJTY464", + "id": "01M1X6SGDRF36W37VV4FJ4AYC8", + "kind": "memory", + "score": 0.7471600770950317, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps — use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 888.5977, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2755, + "mcp_result_bytes": 2894, + "wire_bytes": 2931, + "reported_used_tokens": 2894, + "working_set_bytes": 293036032, + "peak_working_set_bytes": 293941248 + }, + { + "query": "pin minimum supported Rust version in Cargo.toml", + "ranked": [ + "cargo-msrv" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0TQV281HN337V782S05", + "id": "01M1X6SH48BC5T890CFYZFRTQT", + "kind": "memory", + "score": 0.999652862548828, + "summary": "project:fact - [tags: cargo rust msrv edition compatibility] Set `rust-version` in each `Cargo.toml` to declare the minimum supported Rust version (MSRV). Cargo enforces this with `--check`: `cargo check` fails if the toolchain is older than `rust-version`. Keep MSRV as old as your oldest supported deployment target." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 815.8598999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 693, + "mcp_result_bytes": 774, + "wire_bytes": 811, + "reported_used_tokens": 774, + "working_set_bytes": 293048320, + "peak_working_set_bytes": 293965824 + }, + { + "query": "Windows path over 260 characters causes OS error 3 during Cargo build", + "ranked": [ + "windows-long-paths", + "windows-file-locking-av" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1EF5D0C56F8HZZCV7WT", + "id": "01M1X6SHXQA3NG56BMV9DJDPB2", + "kind": "memory", + "score": 0.9964189529418944, + "summary": "project:fact - [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe." + }, + { + "expansion_handle": "memory:01M1X6P1FEZNYXN0DQN55611JR", + "id": "01M1X6SHXQBKXZKPD7DXZ5C8YR", + "kind": "memory", + "score": 0.9571694135665894, + "summary": "project:fact - [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 824.7103999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1297, + "mcp_result_bytes": 1406, + "wire_bytes": 1443, + "reported_used_tokens": 1406, + "working_set_bytes": 293048320, + "peak_working_set_bytes": 293965824 + }, + { + "query": "how do I enable long file paths for Cargo on Windows?", + "ranked": [ + "windows-long-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1EF5D0C56F8HZZCV7WT", + "id": "01M1X6SJQH1E9HBJSM4S6Q65RN", + "kind": "memory", + "score": 0.9998334646224976, + "summary": "project:fact - [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 911.0258, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 769, + "mcp_result_bytes": 860, + "wire_bytes": 897, + "reported_used_tokens": 860, + "working_set_bytes": 293150720, + "peak_working_set_bytes": 294068224 + }, + { + "query": "intermittent sharing violation errors when Rust linker writes the exe on Windows", + "ranked": [ + "windows-file-locking-av", + "windows-long-paths", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1FEZNYXN0DQN55611JR", + "id": "01M1X6SKKYT9FK6WQWJAX3ZE8H", + "kind": "memory", + "score": 0.999750316143036, + "summary": "project:fact - [2026-09-07] [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + }, + { + "expansion_handle": "memory:01M1X6P1EF5D0C56F8HZZCV7WT", + "id": "01M1X6SKKYF4QB7WWJHZHVYXWJ", + "kind": "memory", + "score": 0.4757097661495209, + "summary": "project:fact - [2026-09-07] [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe." + }, + { + "expansion_handle": "memory:01M1X6P0A6JDJBPYZTYEXJX1EZ", + "id": "01M1X6SKKYA2BPHJC2RXA5BX3K", + "kind": "memory", + "score": 0.38107830286026, + "summary": "project:fact - [2026-09-07] [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 847.3796, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2006, + "mcp_result_bytes": 2133, + "wire_bytes": 2170, + "reported_used_tokens": 2133, + "working_set_bytes": 293220352, + "peak_working_set_bytes": 294133760 + }, + { + "query": "Rust walkdir follows junctions differently from symlinks on Windows", + "ranked": [ + "windows-junctions-vs-symlinks" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1JGK45HS6ZMEP0VSX0P", + "id": "01M1X6SMEFVC8AYQY8GRACHAJD", + "kind": "memory", + "score": 0.9996020197868348, + "summary": "project:fact - [tags: windows junctions symlinks rust std::fs] On Windows, directory junctions (NTFS reparse points) behave like symlinks for directory traversal but `std::fs::symlink_metadata` returns `FileType::is_symlink() = false` for junctions (only true for regular symlinks). Use `std::fs::read_link` — it succeeds for both junction and symlink. `walkdir` crate's `follow_links` follows both, but its `is_symlink()` method correctly reports only actual symlinks." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 873.4731, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 845, + "mcp_result_bytes": 926, + "wire_bytes": 963, + "reported_used_tokens": 926, + "working_set_bytes": 293261312, + "peak_working_set_bytes": 294174720 + }, + { + "query": "UNC path canonicalize returns verbatim prefix — how do I strip it?", + "ranked": [ + "windows-unc-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1GBZ2HQSEPCWPZA4V7M", + "id": "01M1X6SNA0RXY36FA1HWQYX8AE", + "kind": "memory", + "score": 0.9988629817962646, + "summary": "project:fact - [tags: windows unc-paths rust std::fs] Windows UNC paths (`\\\\server\\share\\...`) are not supported by most Rust `std::fs` operations unless passed through the extended-length prefix `\\\\?\\UNC\\server\\share\\...`. `std::path::Path::new(\"\\\\\\\\server\\\\share\")` works for basic operations but breaks with `canonicalize()` which returns the verbatim prefix form. When walking directory trees that may start on UNC paths, use the `dunce` crate to strip the verbatim prefix before comparing or displaying paths." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 916.99, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 908, + "mcp_result_bytes": 1025, + "wire_bytes": 1062, + "reported_used_tokens": 1025, + "working_set_bytes": 293384192, + "peak_working_set_bytes": 294309888 + }, + { + "query": "UTF-8 memory text prints as mojibake in the Windows console", + "ranked": [ + "windows-console-encoding" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1HE2E8TZ61WBXD24YKY", + "id": "01M1X6SP6P8QFC4AM309S29B8D", + "kind": "memory", + "score": 0.9996604919433594, + "summary": "project:fact - [tags: windows console encoding utf8 rust] Windows console code page defaults to the system ANSI code page (usually CP1252 or CP932), not UTF-8. Rust's `println!` writes UTF-8 bytes which display as mojibake in a non-UTF-8 console. Fix at process startup: call `SetConsoleOutputCP(65001)` via `winapi` or `windows-sys`, or set `PYTHONUTF8=1`/`RUST_LOG` before launch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 883.0991, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 757, + "mcp_result_bytes": 838, + "wire_bytes": 875, + "reported_used_tokens": 838, + "working_set_bytes": 293392384, + "peak_working_set_bytes": 294309888 + }, + { + "query": "process exit code is 4294967295 instead of -1 on Windows", + "ranked": [ + "windows-exit-codes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1KG7ET6ANHTAZM767GZ", + "id": "01M1X6SQ2WQ8AZ25WV9AX3T964", + "kind": "memory", + "score": 0.9966622591018676, + "summary": "project:fact - [tags: windows exit-codes rust process child] On Windows, process exit codes are 32-bit unsigned integers (DWORD). Rust's `ExitStatus::code()` returns `Option` — it's `None` if the process was killed by a signal (which Windows doesn't use; instead, TerminateProcess with a code). Conventional codes: 0=success, 1=generic error, 0xC0000005=access violation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 927.7739, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 753, + "mcp_result_bytes": 834, + "wire_bytes": 871, + "reported_used_tokens": 834, + "working_set_bytes": 293408768, + "peak_working_set_bytes": 294322176 + }, + { + "query": "tokenizer.json must match the ONNX model — what breaks if it doesn't?", + "ranked": [ + "onnx-tokenizer-mismatch" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1NPTMQW0G2RKJ2C2QZ6", + "id": "01M1X6SQZSA9CVT5VSV8WGR8YB", + "kind": "memory", + "score": 0.9991299510002136, + "summary": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly — specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings — cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 970.6116999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 959, + "mcp_result_bytes": 1040, + "wire_bytes": 1077, + "reported_used_tokens": 1040, + "working_set_bytes": 293412864, + "peak_working_set_bytes": 294326272 + }, + { + "query": "embedding quality degraded after I swapped in the INT8 quantized model", + "ranked": [ + "onnx-quantization-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1PMS5XEE1J3MS9A2WGM", + "id": "01M1X6SRXKJX75F6837KC7FHHG", + "kind": "memory", + "score": 0.997980535030365, + "summary": "project:fact - [2026-09-07] [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals — cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 917.0119, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 990, + "mcp_result_bytes": 1071, + "wire_bytes": 1108, + "reported_used_tokens": 1071, + "working_set_bytes": 293421056, + "peak_working_set_bytes": 294338560 + }, + { + "query": "missing attention mask causes low-norm embeddings in batch inference", + "ranked": [ + "onnx-batch-padding" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1QJRKJ1W5Q7GCPK136E", + "id": "01M1X6SST5ZXBYTRV42212FCSV", + "kind": "memory", + "score": 0.9998397827148438, + "summary": "project:fact - [tags: onnx batch padding attention-mask embeddings] When running batch inference with an ONNX model, all inputs in the batch must be padded to the same sequence length. The `attention_mask` tensor marks which tokens are real (1) and which are padding (0). Failing to pass `attention_mask` causes the model to average-pool over padding tokens, producing systematically lower-norm embeddings." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 904.6568000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 781, + "mcp_result_bytes": 862, + "wire_bytes": 899, + "reported_used_tokens": 862, + "working_set_bytes": 293494784, + "peak_working_set_bytes": 294400000 + }, + { + "query": "ONNX model download fails in a Docker container with no home directory", + "ranked": [ + "onnx-model-cache-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1RMJD8P4HWH6SGXTMZ9", + "id": "01M1X6STPFVB79VRZV1GPWVFC9", + "kind": "memory", + "score": 0.9887272119522096, + "summary": "project:fact - [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 956.6773, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 755, + "mcp_result_bytes": 838, + "wire_bytes": 875, + "reported_used_tokens": 838, + "working_set_bytes": 293548032, + "peak_working_set_bytes": 294457344 + }, + { + "query": "fastembed cache path environment variable for CI", + "ranked": [ + "onnx-model-cache-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1RMJD8P4HWH6SGXTMZ9", + "id": "01M1X6SVMC4RPDZNPEZYT0JF3T", + "kind": "memory", + "score": 0.9995118379592896, + "summary": "project:fact - [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 908.3086000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 756, + "mcp_result_bytes": 839, + "wire_bytes": 876, + "reported_used_tokens": 839, + "working_set_bytes": 293634048, + "peak_working_set_bytes": 294539264 + }, + { + "query": "cosine similarity vs dot product for L2-normalized embedding vectors", + "ranked": [ + "onnx-cosine-vs-dot", + "onnx-tokenizer-mismatch", + "onnx-quantization-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1SDTFE6G9SQ1JWY55C5", + "id": "01M1X6SWGZBACND3NDJQ3AK2VG", + "kind": "memory", + "score": 0.9999407529830932, + "summary": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing — double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + }, + { + "expansion_handle": "memory:01M1X6P1NPTMQW0G2RKJ2C2QZ6", + "id": "01M1X6SWGZ8JAN62AEXJGBAK66", + "kind": "memory", + "score": 0.9514977931976318, + "summary": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly — specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings — cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo." + }, + { + "expansion_handle": "memory:01M1X6P1PMS5XEE1J3MS9A2WGM", + "id": "01M1X6SWGZV9GCACYBXBSWB1VW", + "kind": "memory", + "score": 0.941756010055542, + "summary": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals — cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 877.8926, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2245, + "mcp_result_bytes": 2362, + "wire_bytes": 2399, + "reported_used_tokens": 2362, + "working_set_bytes": 293957632, + "peak_working_set_bytes": 294871040 + }, + { + "query": "stored vectors have wrong dimension after switching embedding models", + "ranked": [ + "onnx-dim-mismatch", + "onnx-cosine-vs-dot", + "onnx-tokenizer-mismatch" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1TBDB4YP32AX87Z885E", + "id": "01M1X6SXCCAZGK5F98X62WNVR5", + "kind": "memory", + "score": 0.9997621178627014, + "summary": "project:fact - [2026-09-07] [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results — the ANN index shape mismatch isn't always caught at runtime." + }, + { + "expansion_handle": "memory:01M1X6P1SDTFE6G9SQ1JWY55C5", + "id": "01M1X6SXCCHDNZAGVBDN5XNHNT", + "kind": "memory", + "score": 0.997715711593628, + "summary": "project:fact - [2026-09-07] [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing — double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + }, + { + "expansion_handle": "memory:01M1X6P1NPTMQW0G2RKJ2C2QZ6", + "id": "01M1X6SXCCS9KSP9WC80HJ29CE", + "kind": "memory", + "score": 0.9388805031776428, + "summary": "project:fact - [2026-09-07] [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly — specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings — cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 770.2015, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2049, + "mcp_result_bytes": 2166, + "wire_bytes": 2203, + "reported_used_tokens": 2166, + "working_set_bytes": 294035456, + "peak_working_set_bytes": 294944768 + }, + { + "query": "E5 and Instructor models need a query prefix — what happens without it?", + "ranked": [ + "onnx-prefix-instructions", + "onnx-cosine-vs-dot" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1X0E7WV7YJ8EMERDVM1", + "id": "01M1X6SY4K842JMHEAMHZAJAE8", + "kind": "memory", + "score": 0.996955633163452, + "summary": "project:fact - [tags: onnx embeddings prefix instruction e5 query passage] E5 and Instructor family models require a text prefix on BOTH query and passage sides to produce meaningful similarities: query prefix `\"query: \"`, passage prefix `\"passage: \"`. Omitting the prefix can drop MRR by 10-15 percentage points on out-of-domain datasets. Check the model's README for the exact prefix string — it varies by model family." + }, + { + "expansion_handle": "memory:01M1X6P1SDTFE6G9SQ1JWY55C5", + "id": "01M1X6SY4K7KF9MFF7K405RT9V", + "kind": "memory", + "score": 0.9543967247009276, + "summary": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing — double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 927.7011, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1339, + "mcp_result_bytes": 1446, + "wire_bytes": 1483, + "reported_used_tokens": 1446, + "working_set_bytes": 294100992, + "peak_working_set_bytes": 295014400 + }, + { + "query": "ORT thread pool contention when running multiple bench processes in parallel", + "ranked": [ + "onnx-ort-threading" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1XX3WCG6HCFJES7B7FT", + "id": "01M1X6SZ26GXB5B1S1BVD3F8F0", + "kind": "memory", + "score": 0.9998078942298888, + "summary": "project:fact - [2026-09-07] [tags: onnx ort thread-pool parallelism cpu] ORT (ONNX Runtime) creates its own inter-op and intra-op thread pools. In a multi-process bench setup, each child inherits these pools and they compete for CPU cores. Set `SessionOptionsBuilder::with_intra_threads(1).with_inter_threads(1)` if you're running many parallel bench processes — this sacrifices per-inference throughput for lower contention." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 946.5814, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 802, + "mcp_result_bytes": 883, + "wire_bytes": 920, + "reported_used_tokens": 883, + "working_set_bytes": 294113280, + "peak_working_set_bytes": 295026688 + }, + { + "query": "git worktrees share the .kimetsu brain — how do I isolate test runs?", + "ranked": [ + "git-worktree-brain-isolation", + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1YWTX8Y6NQRBF82KS3J", + "id": "01M1X6SZZ3M5MW5K03HJD603A5", + "kind": "memory", + "score": 0.9996256828308104, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root — if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + }, + { + "expansion_handle": "memory:01M1X6P00PJD8FDG3QR2RS7QAK", + "id": "01M1X6SZZ3E5FZ9CXNCRRMS7CC", + "kind": "memory", + "score": 0.9904396533966064, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 910.5159000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1435, + "mcp_result_bytes": 1534, + "wire_bytes": 1571, + "reported_used_tokens": 1534, + "working_set_bytes": 294117376, + "peak_working_set_bytes": 295034880 + }, + { + "query": "when is it safe to use --no-verify on git commit?", + "ranked": [ + "git-hooks-bypass", + "git-reflog-rescue" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1ZTA6M3QRJEDZXT7GPD", + "id": "01M1X6T0VE2TD5BHNMGMF9TEJ3", + "kind": "memory", + "score": 0.9956986904144288, + "summary": "project:fact - [2026-09-07] [tags: git hooks bypass pre-commit skip] `git commit --no-verify` skips ALL hooks (pre-commit and commit-msg). Never use this in shared team repos where hooks enforce quality gates (lint, tests, memory harvest). Instead, fix the failing hook." + }, + { + "expansion_handle": "memory:01M1X6P23PDPP71F97F0Z839MX", + "id": "01M1X6T0VENFVB9M1DCJRK75AX", + "kind": "memory", + "score": 0.5084817409515381, + "summary": "project:fact - [2026-09-07] [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone — they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only — remote reflog is not accessible via normal git commands." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 920.865, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1193, + "mcp_result_bytes": 1292, + "wire_bytes": 1329, + "reported_used_tokens": 1292, + "working_set_bytes": 294117376, + "peak_working_set_bytes": 295034880 + }, + { + "query": "reduce clone size and bandwidth for server-side repo ingest", + "ranked": [ + "git-sparse-checkout", + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P20RSG8Q3WVX1DR7PCMJ", + "id": "01M1X6T1RBPM9HZ3HMMCH5QQ09", + "kind": "memory", + "score": 0.9969936609268188, + "summary": "project:fact - [tags: git sparse-checkout partial-clone bandwidth] `git sparse-checkout init --cone` combined with `git clone --filter=blob:none` (partial clone) fetches only the commit graph and tree objects, not blobs. Individual blobs are fetched on demand when accessed. This cuts clone time for large repos from minutes to seconds." + }, + { + "expansion_handle": "memory:01M1X6NZK0NCCC1P19NW0KM4XV", + "id": "01M1X6T1RBV9K1YTPAASQ2GN3P", + "kind": "memory", + "score": 0.8199672698974609, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 983.3775, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1744, + "mcp_result_bytes": 1843, + "wire_bytes": 1880, + "reported_used_tokens": 1843, + "working_set_bytes": 294129664, + "peak_working_set_bytes": 295038976 + }, + { + "query": "spurious diffs from Windows CRLF line ending conversion in git", + "ranked": [ + "git-line-endings-windows" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P21RP2J0P311XGVMM8MY", + "id": "01M1X6T2Q04X0EEBRPJ79ZB3Q5", + "kind": "memory", + "score": 0.9993343949317932, + "summary": "project:fact - [tags: git line-endings windows crlf autocrlf] On Windows, `core.autocrlf=true` (git's default for Windows installs) converts LF to CRLF on checkout and CRLF to LF on commit. This causes spurious diffs when files are edited on Windows then committed — the content is identical but the line endings differ in the index vs the working tree. Fix: set `core.autocrlf=false` and `.gitattributes` with `* text=auto eol=lf` for the repo." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 908.5777, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 940, + "reported_used_tokens": 903, + "working_set_bytes": 294154240, + "peak_working_set_bytes": 295059456 + }, + { + "query": "git submodule always gets the wrong commit in CI", + "ranked": [ + "git-submodule-pinning", + "git-hooks-bypass" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P22PHYS4T5P5F3PE7N1M", + "id": "01M1X6T3KKRDFGF8RZQ3A21FK1", + "kind": "memory", + "score": 0.9992856383323668, + "summary": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip — this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version." + }, + { + "expansion_handle": "memory:01M1X6P1ZTA6M3QRJEDZXT7GPD", + "id": "01M1X6T3KKC72DJF7P63RMM14X", + "kind": "memory", + "score": 0.6295387744903564, + "summary": "project:fact - [tags: git hooks bypass pre-commit skip] `git commit --no-verify` skips ALL hooks (pre-commit and commit-msg). Never use this in shared team repos where hooks enforce quality gates (lint, tests, memory harvest). Instead, fix the failing hook." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 933.53, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1157, + "mcp_result_bytes": 1256, + "wire_bytes": 1293, + "reported_used_tokens": 1256, + "working_set_bytes": 294174720, + "peak_working_set_bytes": 295088128 + }, + { + "query": "accidentally ran git reset --hard and lost commits — can I recover?", + "ranked": [ + "git-reflog-rescue" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P23PDPP71F97F0Z839MX", + "id": "01M1X6T4GMGS2SDP932S6YRAJX", + "kind": "memory", + "score": 0.9995450377464294, + "summary": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone — they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only — remote reflog is not accessible via normal git commands." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 947.8262, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 762, + "mcp_result_bytes": 843, + "wire_bytes": 880, + "reported_used_tokens": 843, + "working_set_bytes": 294187008, + "peak_working_set_bytes": 295088128 + }, + { + "query": "blocking SQLite call from an async tokio handler causes latency spikes", + "ranked": [ + "tokio-blocking-in-async" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P24PG6KXJZQB4K5G8MKX", + "id": "01M1X6T5ENWESC500QTEVKN0GD", + "kind": "memory", + "score": 0.9996535778045654, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking — never call rusqlite directly from an async fn without spawn_blocking." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 874.721, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 766, + "mcp_result_bytes": 847, + "wire_bytes": 884, + "reported_used_tokens": 847, + "working_set_bytes": 294207488, + "peak_working_set_bytes": 295116800 + }, + { + "query": "Cannot start a runtime from within a runtime in a tokio test", + "ranked": [ + "tokio-runtime-in-tests", + "tokio-blocking-in-async" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P25SW9R97848FW6SCJ4H", + "id": "01M1X6T69T4XKPHT3XF5PK3ZMC", + "kind": "memory", + "score": 0.9997126460075378, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + }, + { + "expansion_handle": "memory:01M1X6P24PG6KXJZQB4K5G8MKX", + "id": "01M1X6T69VV8JYWSKCC9AHYDTF", + "kind": "memory", + "score": 0.5779464840888977, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking — never call rusqlite directly from an async fn without spawn_blocking." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 862.5042000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1370, + "mcp_result_bytes": 1477, + "wire_bytes": 1514, + "reported_used_tokens": 1477, + "working_set_bytes": 294207488, + "peak_working_set_bytes": 295120896 + }, + { + "query": "tokio select cancels the other branch and loses the value in the channel", + "ranked": [ + "tokio-select-cancellation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P270RXS50A66Q2YBWW4H", + "id": "01M1X6T75MD8MFKE9VRDY77VC3", + "kind": "memory", + "score": 0.9981033802032472, + "summary": "project:fact - [tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 920.0183, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 751, + "mcp_result_bytes": 832, + "wire_bytes": 869, + "reported_used_tokens": 832, + "working_set_bytes": 294178816, + "peak_working_set_bytes": 295120896 + }, + { + "query": "mpsc channel backpressure causing senders to stall", + "ranked": [ + "tokio-channel-backpressure" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P284KFX2CP4805G4SDCD", + "id": "01M1X6T81C59QG0YR8N9HJ5R1V", + "kind": "memory", + "score": 0.9999104738235474, + "summary": "project:fact - [tags: tokio mpsc channel backpressure async rust] `tokio::sync::mpsc::channel(N)` with a bounded buffer provides backpressure: senders block when the buffer is full. This prevents unbounded memory growth but can cause sender tasks to stall. Choosing N: too small causes frequent backpressure (throughput drops); too large defeats the purpose." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 970.7314, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 733, + "mcp_result_bytes": 814, + "wire_bytes": 851, + "reported_used_tokens": 814, + "working_set_bytes": 294182912, + "peak_working_set_bytes": 295120896 + }, + { + "query": "overhead from calling spawn_blocking on every single query request", + "ranked": [ + "tokio-spawn-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P297FDMXCDWTFDYT22QV", + "id": "01M1X6T8ZQYJNHSGC87Z70HQ0E", + "kind": "memory", + "score": 0.9961729645729064, + "summary": "project:fact - [tags: tokio spawn_blocking thread-pool rust blocking] `tokio::task::spawn_blocking` places work on a dedicated blocking thread pool (default up to 512 threads, configurable via `Builder::max_blocking_threads`). Each call creates or reuses a thread — there's no true pooling, threads may be created on demand. For many short-duration blocking calls (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 912.1324, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 746, + "mcp_result_bytes": 827, + "wire_bytes": 864, + "reported_used_tokens": 827, + "working_set_bytes": 294309888, + "peak_working_set_bytes": 295219200 + }, + { + "query": "axum server panics during shutdown because the DB pool is already closed", + "ranked": [ + "tokio-shutdown-ordering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P2CZ739FX2TTZAGZGTRW", + "id": "01M1X6T9W7WH90QDNAG8RCJZS1", + "kind": "memory", + "score": 0.98052579164505, + "summary": "project:fact - [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries — the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 920.5963, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 931, + "mcp_result_bytes": 1012, + "wire_bytes": 1049, + "reported_used_tokens": 1012, + "working_set_bytes": 294412288, + "peak_working_set_bytes": 295329792 + }, + { + "query": "reqwest Client created per-request defeats connection pooling", + "ranked": [ + "http-connection-pooling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P2E2AZRTGV4X4V6ND9WP", + "id": "01M1X6TAS5D36434XZB5H58Z3Q", + "kind": "memory", + "score": 0.9998082518577576, + "summary": "project:fact - [tags: http reqwest connection-pool keep-alive rust] reqwest's `Client` holds a connection pool; always create ONE `Client` instance and clone it for each handler — cloning is cheap (Arc under the hood). Creating a `Client::new()` per request defeats connection pooling and causes TCP connection exhaustion under load. The default pool settings: max_idle_per_host=usize::MAX (unbounded), idle_timeout=90s." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 862.3707, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 797, + "mcp_result_bytes": 878, + "wire_bytes": 915, + "reported_used_tokens": 878, + "working_set_bytes": 294449152, + "peak_working_set_bytes": 295342080 + }, + { + "query": "LLM request times out during streaming — which timeout setting applies?", + "ranked": [ + "http-timeout-layering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P2F6VFRXARZ6TPWVGB9S", + "id": "01M1X6TBM71HB0Q1WWHWZWGBQW", + "kind": "memory", + "score": 0.9987107515335084, + "summary": "project:fact - [tags: http reqwest timeout connect read total rust] reqwest has three distinct timeout knobs: `connect_timeout`, `read_timeout`, and `timeout` (total). They compose: if all three are set, the request fails at whichever fires first. For LLM API calls with streaming responses, `read_timeout` must be larger than the slowest expected token (often 30-60s) while `connect_timeout` can be tight (3-5s)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 786.327, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 788, + "mcp_result_bytes": 869, + "wire_bytes": 906, + "reported_used_tokens": 869, + "working_set_bytes": 294481920, + "peak_working_set_bytes": 295395328 + }, + { + "query": "how do I safely retry a POST to the LLM API without creating duplicates?", + "ranked": [ + "http-retry-idempotency" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P2GA8QFB8KPZ4G6QRSD5", + "id": "01M1X6TCCM6WM0E7MXHTD0XW2R", + "kind": "memory", + "score": 0.9995805621147156, + "summary": "project:fact - [tags: http retry idempotency post put reqwest] Only retry idempotent requests automatically. GET, HEAD, PUT, DELETE are idempotent. POST is NOT — retrying a POST may create duplicate resources." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 954.5133, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 585, + "mcp_result_bytes": 666, + "wire_bytes": 703, + "reported_used_tokens": 666, + "working_set_bytes": 294481920, + "peak_working_set_bytes": 295399424 + }, + { + "query": "custom enterprise root CA not trusted by rustls on Windows", + "ranked": [ + "http-tls-roots", + "http-proxy-env" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P2HH2CGAQBG5W2V177J6", + "id": "01M1X6TDAM3J3813F2B31E9PD0", + "kind": "memory", + "score": 0.9998220801353456, + "summary": "project:fact - [tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle — the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle." + }, + { + "expansion_handle": "memory:01M1X6P2KVN5DQ41TVK0JMYGCV", + "id": "01M1X6TDAMZBV81VF8JTZJ9PTG", + "kind": "memory", + "score": 0.38715291023254395, + "summary": "project:fact - [tags: http proxy environment reqwest rust corporate] reqwest respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` environment variables by default (with `default-tls` or `rustls-tls`). In a corporate network, these may redirect traffic through an intercepting proxy that breaks mTLS or adds latency. To disable proxy usage entirely: `reqwest::ClientBuilder::no_proxy()`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 927.7096, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1311, + "mcp_result_bytes": 1410, + "wire_bytes": 1447, + "reported_used_tokens": 1410, + "working_set_bytes": 294481920, + "peak_working_set_bytes": 295399424 + }, + { + "query": "parsing server-sent events when a single TCP chunk contains a partial SSE frame", + "ranked": [ + "http-streaming-bodies" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P2JNVC441XJQFSMYT0SJ", + "id": "01M1X6TE7RJR1B9A8KZ19177HQ", + "kind": "memory", + "score": 0.9667426943778992, + "summary": "project:fact - [2026-09-07] [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding — a chunk may split across frame boundaries." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 861.8673, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 859, + "mcp_result_bytes": 940, + "wire_bytes": 977, + "reported_used_tokens": 940, + "working_set_bytes": 294481920, + "peak_working_set_bytes": 295399424 + }, + { + "query": "reqwest does not use the system proxy settings on Windows", + "ranked": [ + "http-proxy-env", + "http-tls-roots", + "http-connection-pooling", + "http-streaming-bodies" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P2KVN5DQ41TVK0JMYGCV", + "id": "01M1X6TF334RZ4YQJDAGMDFFRJ", + "kind": "memory", + "score": 0.9997830986976624, + "summary": "project:fact - [tags: http proxy environment reqwest rust corporate] reqwest respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` environment variables by default (with `default-tls` or `rustls-tls`). In a corporate network, these may redirect traffic through an intercepting proxy that breaks mTLS or adds latency. To disable proxy usage entirely: `reqwest::ClientBuilder::no_proxy()`." + }, + { + "expansion_handle": "memory:01M1X6P2HH2CGAQBG5W2V177J6", + "id": "01M1X6TF33VKQTQAMPKFBCQ9VB", + "kind": "memory", + "score": 0.9808586239814758, + "summary": "project:fact - [tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle — the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle." + }, + { + "expansion_handle": "memory:01M1X6P2E2AZRTGV4X4V6ND9WP", + "id": "01M1X6TF33A7WZPDJ4JTR7ESXQ", + "kind": "memory", + "score": 0.719273030757904, + "summary": "project:fact - [tags: http reqwest connection-pool keep-alive rust] reqwest's `Client` holds a connection pool; always create ONE `Client` instance and clone it for each handler — cloning is cheap (Arc under the hood). Creating a `Client::new()` per request defeats connection pooling and causes TCP connection exhaustion under load. The default pool settings: max_idle_per_host=usize::MAX (unbounded), idle_timeout=90s." + }, + { + "expansion_handle": "memory:01M1X6P2JNVC441XJQFSMYT0SJ", + "id": "01M1X6TF33WS7Z975583KRJDDD", + "kind": "memory", + "score": 0.7009692192077637, + "summary": "project:fact - [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding — a chunk may split across frame boundaries." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 947.9492, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2497, + "mcp_result_bytes": 2632, + "wire_bytes": 2669, + "reported_used_tokens": 2632, + "working_set_bytes": 294481920, + "peak_working_set_bytes": 295399424 + }, + { + "query": "insta snapshot tests fail in CI because output includes a timestamp", + "ranked": [ + "testing-snapshot-churn", + "ci-flaky-quarantine" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P2MX1SRAWDA3HNGSBVT6", + "id": "01M1X6TG04BDRVX490Q6EGVTM0", + "kind": "memory", + "score": 0.999855637550354, + "summary": "project:fact - [tags: testing snapshot insta assert churn rust] Snapshot tests (e.g. with the `insta` crate) fail whenever the output changes, even for intended changes. In CI, they fail loudly; locally, `cargo insta review` walks you through accepting or rejecting changes." + }, + { + "expansion_handle": "memory:01M1X6P3H0TD9NJ1PFEC45G42A", + "id": "01M1X6TG04YKH9RJA1R45FXWNR", + "kind": "memory", + "score": 0.5997360348701477, + "summary": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal — a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 823.4245999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1196, + "mcp_result_bytes": 1295, + "wire_bytes": 1332, + "reported_used_tokens": 1295, + "working_set_bytes": 294486016, + "peak_working_set_bytes": 295399424 + }, + { + "query": "two test workers writing to the same temp directory path race each other", + "ranked": [ + "testing-temp-dirs-ci" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P2P2TSKGVDHTVF5Y34FZ", + "id": "01M1X6TGSVHW6B7XEKC61RCTZ5", + "kind": "memory", + "score": 0.9889234900474548, + "summary": "project:fact - [tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 878.9519, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 755, + "mcp_result_bytes": 836, + "wire_bytes": 873, + "reported_used_tokens": 836, + "working_set_bytes": 294486016, + "peak_working_set_bytes": 295399424 + }, + { + "query": "test passes locally but fails on a slow CI runner due to a 100ms sleep", + "ranked": [ + "testing-time-dependent-flakes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P2Q8X75PWP6KHNCWMTJZ", + "id": "01M1X6THNAHQXNG3JZW63HGWW1", + "kind": "memory", + "score": 0.808289110660553, + "summary": "project:fact - [tags: testing time flaky clock mock rust] Tests that depend on wall-clock time are inherently flaky under load (slow CI runners, GC pauses). Abstract time behind a trait (`Clock: Fn() -> SystemTime`) injected at construction, and supply a fake in tests. For tests checking that something happened \"within N seconds\", use a generous multiple of the expected duration (10x is not unreasonable for CI)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 938.7784, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 790, + "mcp_result_bytes": 875, + "wire_bytes": 912, + "reported_used_tokens": 875, + "working_set_bytes": 294486016, + "peak_working_set_bytes": 295407616 + }, + { + "query": "proptest found a hash collision in text normalization that example tests missed", + "ranked": [ + "testing-property-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P2RBNN1JHA889PRFA63Y", + "id": "01M1X6TJJT19GXRNWP4WXH4AMM", + "kind": "memory", + "score": 0.9994783997535706, + "summary": "project:fact - [tags: testing property-based proptest quickcheck rust] Property-based tests (proptest, quickcheck) find edge cases that example-based tests miss. For kimetsu's memory text normalization, proptest found that zero-width joiner characters and right-to-left marks caused hash collisions. Run proptest with `PROPTEST_CASES=10000` in CI for thorough coverage." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 943.2481, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 744, + "mcp_result_bytes": 825, + "wire_bytes": 862, + "reported_used_tokens": 825, + "working_set_bytes": 294486016, + "peak_working_set_bytes": 295407616 + }, + { + "query": "set_var in tests races when cargo test runs them in parallel", + "ranked": [ + "testing-serial-vs-parallel" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P2SBPNK9PEDW9QRXPYZJ", + "id": "01M1X6TKGD4JHV527ADD5936JQ", + "kind": "memory", + "score": 0.9997344613075256, + "summary": "project:fact - [2026-09-07] [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 925.4599999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 832, + "mcp_result_bytes": 913, + "wire_bytes": 950, + "reported_used_tokens": 913, + "working_set_bytes": 294494208, + "peak_working_set_bytes": 295415808 + }, + { + "query": "hardcoded JSON fixtures broke after a schema migration", + "ranked": [ + "testing-fixture-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P2X1Q84DSXWGJTFEX3G2", + "id": "01M1X6TMD3GC10F2MJFRHE6NCZ", + "kind": "memory", + "score": 0.9998371601104736, + "summary": "project:fact - [2026-09-07] [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 769.0437, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 783, + "mcp_result_bytes": 864, + "wire_bytes": 901, + "reported_used_tokens": 864, + "working_set_bytes": 294658048, + "peak_working_set_bytes": 295567360 + }, + { + "query": "debug print in the MCP handler corrupts the JSON-Lines protocol stream", + "ranked": [ + "mcp-stdout-protocol" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P2XXR71TXJXVQBRVTSF6", + "id": "01M1X6TN60P1R3Q0ZVVXXS3E0D", + "kind": "memory", + "score": 0.9997472167015076, + "summary": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 975.8611000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 705, + "mcp_result_bytes": 786, + "wire_bytes": 823, + "reported_used_tokens": 786, + "working_set_bytes": 294731776, + "peak_working_set_bytes": 295645184 + }, + { + "query": "kimetsu MCP tool call times out because embedding model is re-initialized every call", + "ranked": [ + "mcp-tool-timeouts", + "mcp-schema-validation", + "kimetsu-bench-remote-embedder-singleton" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P2YZ4YRDGDVJV5XMKCP1", + "id": "01M1X6TP3X3EJ1ZJ6ADMZS7NYE", + "kind": "memory", + "score": 0.9995898604393004, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking — in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize — keep it in a process-global `OnceLock`)." + }, + { + "expansion_handle": "memory:01M1X6P31407BS4T0BV7PGT98H", + "id": "01M1X6TP3XRGMXGF4YFAEWVXS2", + "kind": "memory", + "score": 0.6027993559837341, + "summary": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array — omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error." + }, + { + "expansion_handle": "memory:01M1X6P3WKNTTE5DK6PSVCEB7R", + "id": "01M1X6TP3X21F81YVCKKWNCVBG", + "kind": "memory", + "score": 0.5117799639701843, + "summary": "project:fact - [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 831.0237999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2085, + "mcp_result_bytes": 2202, + "wire_bytes": 2239, + "reported_used_tokens": 2202, + "working_set_bytes": 294772736, + "peak_working_set_bytes": 295686144 + }, + { + "query": "env var set after host launch is not visible to the MCP server process", + "ranked": [ + "mcp-env-propagation", + "kimetsu-daemon-lifecycle" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P302K2RV969H5CEVQWE9", + "id": "01M1X6TPY37R0JWYC19FR8M4V9", + "kind": "memory", + "score": 0.9984827637672424, + "summary": "project:fact - [2026-09-07] [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment — changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate." + }, + { + "expansion_handle": "memory:01M1X6P3J1H7C1NPH0T2G7FWCC", + "id": "01M1X6TPY3075ZPEN7VPSEJR3R", + "kind": "memory", + "score": 0.9977922439575196, + "summary": "project:fact - [2026-09-07] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 998.9179, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1267, + "mcp_result_bytes": 1366, + "wire_bytes": 1403, + "reported_used_tokens": 1366, + "working_set_bytes": 294793216, + "peak_working_set_bytes": 295714816 + }, + { + "query": "MCP tool call fails because a required field is missing from the JSON input", + "ranked": [ + "mcp-schema-validation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P31407BS4T0BV7PGT98H", + "id": "01M1X6TQWZ5B3T64V7ED900M6Y", + "kind": "memory", + "score": 0.998538613319397, + "summary": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array — omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 915.9673, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 798, + "mcp_result_bytes": 879, + "wire_bytes": 916, + "reported_used_tokens": 879, + "working_set_bytes": 294801408, + "peak_working_set_bytes": 295714816 + }, + { + "query": "Claude Code rejects the tool name with a hyphen in it", + "ranked": [ + "mcp-tool-naming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P32468TZD6PXNN0J84CQ", + "id": "01M1X6TRSPKYV5FAZCZZ854V1H", + "kind": "memory", + "score": 0.9982439279556274, + "summary": "project:fact - [tags: mcp tool naming convention kimetsu] MCP tool names must be valid identifiers for all host agents. Claude Code restricts tool names to `[a-zA-Z0-9_-]` and max 64 chars. Use `snake_case` (kimetsu_brain_context, kimetsu_brain_record) — hyphen is technically allowed but some hosts reject it." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 890.1919, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 687, + "mcp_result_bytes": 768, + "wire_bytes": 805, + "reported_used_tokens": 768, + "working_set_bytes": 294891520, + "peak_working_set_bytes": 295804928 + }, + { + "query": "MCP response path uses backslashes and the host rejects it", + "ranked": [ + "mcp-transcript-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P337XNQ9HBRDB7E4246Z", + "id": "01M1X6TSNCSZ7GHAETJ4FKQNDK", + "kind": "memory", + "score": 0.9984637498855592, + "summary": "project:fact - [tags: mcp transcript paths kimetsu hooks runs] kimetsu writes run transcripts to `/.kimetsu/runs//`. The post-session hook reads the latest run's transcript to trigger memory harvest. On Windows, the path uses backslashes internally but the MCP JSON must use forward slashes or the host may reject path-type arguments." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 914.7967, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 724, + "mcp_result_bytes": 805, + "wire_bytes": 842, + "reported_used_tokens": 805, + "working_set_bytes": 294903808, + "peak_working_set_bytes": 295813120 + }, + { + "query": "AWS credentials not found — which env var does kimetsu read for Bedrock?", + "ranked": [ + "aws-credentials-chain", + "aws-region-resolution", + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P34CEH6KSKA057ZQRBCP", + "id": "01M1X6TTJ1HB6BNANXB239NEWQ", + "kind": "memory", + "score": 0.9990235567092896, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + }, + { + "expansion_handle": "memory:01M1X6P35QVP8D7M962ZGK168S", + "id": "01M1X6TTJ1WZTYJ8HFX2781EPJ", + "kind": "memory", + "score": 0.9968422651290894, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X6NZRCKJJR7B15FGFKTS4W", + "id": "01M1X6TTJ19AN40YXA8ERJK6EW", + "kind": "memory", + "score": 0.9849756360054016, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1X6NZXZMGW8CD9YMNHQ3ZTX", + "id": "01M1X6TTJ11RNHSXZBRC6D0VD0", + "kind": "memory", + "score": 0.9203452467918396, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 947.4548000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3455, + "mcp_result_bytes": 3618, + "wire_bytes": 3655, + "reported_used_tokens": 3618, + "working_set_bytes": 294903808, + "peak_working_set_bytes": 295817216 + }, + { + "query": "Bedrock InvokeModel fails because the region is not configured", + "ranked": [ + "aws-region-resolution", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P35QVP8D7M962ZGK168S", + "id": "01M1X6TVFWN9APZT2PF094VP2W", + "kind": "memory", + "score": 0.99688321352005, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1X6NZXZMGW8CD9YMNHQ3ZTX", + "id": "01M1X6TVFW1DJYV0N1RRE33TJE", + "kind": "memory", + "score": 0.6450709104537964, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1013.5312000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1810, + "mcp_result_bytes": 1929, + "wire_bytes": 1966, + "reported_used_tokens": 1929, + "working_set_bytes": 294903808, + "peak_working_set_bytes": 295821312 + }, + { + "query": "how do I handle ThrottlingException from Bedrock with exponential backoff?", + "ranked": [ + "aws-retry-throttling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P36TQNTE0Z7YTV5BM0YV", + "id": "01M1X6TWFFFSJ34F0JR1TC8CRC", + "kind": "memory", + "score": 0.9997082352638244, + "summary": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with ±25% jitter." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 994.9831, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 771, + "mcp_result_bytes": 868, + "wire_bytes": 905, + "reported_used_tokens": 868, + "working_set_bytes": 294907904, + "peak_working_set_bytes": 295821312 + }, + { + "query": "generating a presigned S3 URL for brain export without exposing credentials", + "ranked": [ + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P37Y0AZY1Q54BEN2E91E", + "id": "01M1X6TXEH8C75HCQMQWCNWKKQ", + "kind": "memory", + "score": 0.9990487694740297, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time — clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 931.8839, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 875, + "mcp_result_bytes": 956, + "wire_bytes": 993, + "reported_used_tokens": 956, + "working_set_bytes": 294907904, + "peak_working_set_bytes": 295821312 + }, + { + "query": "IMDSv2 token required for instance metadata — PUT before GET", + "ranked": [ + "aws-instance-metadata" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P392Y3GSQ55B0ZTJV7CZ", + "id": "01M1X6TYBMB7TC1CQTFQ7QF4XY", + "kind": "memory", + "score": 0.9997182488441468, + "summary": "project:fact - [2026-09-07] [tags: aws imds instance-metadata ec2 token] The AWS Instance Metadata Service v2 (IMDSv2) requires a session token: PUT `http://169.254.169.254/latest/api/token` with `X-aws-ec2-metadata-token-ttl-seconds: 21600` to get a token, then GET metadata with `X-aws-ec2-metadata-token: `. IMDSv1 (no token) is disabled on hardened instances. The metadata endpoint is only reachable from within EC2 — a connection timeout means you're not on EC2." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 945.0785000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 851, + "mcp_result_bytes": 932, + "wire_bytes": 969, + "reported_used_tokens": 932, + "working_set_bytes": 294907904, + "peak_working_set_bytes": 295821312 + }, + { + "query": "Cargo cache key strategy for GitHub Actions to avoid toolchain version collisions", + "ranked": [ + "ci-cache-keys" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3D60EXFZ940W9PTRB37", + "id": "01M1X6TZ9E15D2DFXR7NNFAMTV", + "kind": "memory", + "score": 0.998869240283966, + "summary": "project:fact - [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key — macOS and Windows have incompatible artifact formats." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 997.9987, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 788, + "mcp_result_bytes": 869, + "wire_bytes": 906, + "reported_used_tokens": 869, + "working_set_bytes": 294907904, + "peak_working_set_bytes": 295821312 + }, + { + "query": "CI matrix has 18 jobs and costs too much — how do I reduce it?", + "ranked": [ + "ci-matrix-explosion" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3E2Z6GNT2HV8ZC7M4QK", + "id": "01M1X6V08TDGKGMWH8JJB9NWPF", + "kind": "memory", + "score": 0.999057948589325, + "summary": "project:fact - [tags: ci github-actions matrix jobs resources] A CI matrix combining OS (3) x Rust toolchain (3) x features (2) = 18 jobs. Each spawns a runner; at $0.008/min for Ubuntu and $0.016/min for Windows, a 10-minute build costs $2.40 per push. Reduce: test the full matrix only on PRs to main; on feature branches, test only Linux+stable." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 974.2088, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 722, + "mcp_result_bytes": 803, + "wire_bytes": 840, + "reported_used_tokens": 803, + "working_set_bytes": 294907904, + "peak_working_set_bytes": 295825408 + }, + { + "query": "GitHub Actions secret accidentally printed in build logs", + "ranked": [ + "ci-secrets-masking", + "ci-cache-keys", + "ci-artifact-retention" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3F12CWS8FE5S50H7TYH", + "id": "01M1X6V16VMG16XS7GDBZVAA19", + "kind": "memory", + "score": 0.9963951706886292, + "summary": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output — but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable." + }, + { + "expansion_handle": "memory:01M1X6P3D60EXFZ940W9PTRB37", + "id": "01M1X6V16VM2HBSH5WHWM7PWPE", + "kind": "memory", + "score": 0.4342843890190125, + "summary": "project:fact - [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key — macOS and Windows have incompatible artifact formats." + }, + { + "expansion_handle": "memory:01M1X6P3G3ZGS62RZRNJXYR8N4", + "id": "01M1X6V16VZV94K0MKM7BZCCK5", + "kind": "memory", + "score": 0.3422144949436188, + "summary": "project:fact - [tags: ci github-actions artifacts retention benchmark] GitHub Actions artifacts are retained for 90 days (default). For benchmark results, use `actions/upload-artifact` with `retention-days: 365` for long-term tracking. The free tier has 500MB storage — per-combo JSON files from kimetsu bench (each ~60KB) add up fast if you upload them on every push." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 912.6428, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1791, + "mcp_result_bytes": 1908, + "wire_bytes": 1945, + "reported_used_tokens": 1908, + "working_set_bytes": 294916096, + "peak_working_set_bytes": 295829504 + }, + { + "query": "how long do GitHub Actions artifacts persist and what's the storage limit?", + "ranked": [ + "ci-artifact-retention" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3G3ZGS62RZRNJXYR8N4", + "id": "01M1X6V23CGDQ808DE2CG4PGBC", + "kind": "memory", + "score": 0.999624252319336, + "summary": "project:fact - [tags: ci github-actions artifacts retention benchmark] GitHub Actions artifacts are retained for 90 days (default). For benchmark results, use `actions/upload-artifact` with `retention-days: 365` for long-term tracking. The free tier has 500MB storage — per-combo JSON files from kimetsu bench (each ~60KB) add up fast if you upload them on every push." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 981.2126, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 744, + "mcp_result_bytes": 825, + "wire_bytes": 862, + "reported_used_tokens": 825, + "working_set_bytes": 294924288, + "peak_working_set_bytes": 295841792 + }, + { + "query": "timing-based test flake in CI — quarantine or fix?", + "ranked": [ + "ci-flaky-quarantine", + "testing-time-dependent-flakes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3H0TD9NJ1PFEC45G42A", + "id": "01M1X6V322JRC3B5NQNV2V73H2", + "kind": "memory", + "score": 0.9994743466377258, + "summary": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal — a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output." + }, + { + "expansion_handle": "memory:01M1X6P2Q8X75PWP6KHNCWMTJZ", + "id": "01M1X6V322C4809S9DN02KGMX4", + "kind": "memory", + "score": 0.9849997162818908, + "summary": "project:fact - [tags: testing time flaky clock mock rust] Tests that depend on wall-clock time are inherently flaky under load (slow CI runners, GC pauses). Abstract time behind a trait (`Clock: Fn() -> SystemTime`) injected at construction, and supply a fake in tests. For tests checking that something happened \"within N seconds\", use a generous multiple of the expected duration (10x is not unreasonable for CI)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 944.8933000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1340, + "mcp_result_bytes": 1443, + "wire_bytes": 1480, + "reported_used_tokens": 1443, + "working_set_bytes": 294932480, + "peak_working_set_bytes": 295854080 + }, + { + "query": "kimetsu doctor says the MCP server is running — how do I stop it before an update?", + "ranked": [ + "kimetsu-daemon-lifecycle", + "mcp-env-propagation", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3J1H7C1NPH0T2G7FWCC", + "id": "01M1X6V40E8NG90AGE03QYK1EB", + "kind": "memory", + "score": 0.9989782571792604, + "summary": "project:fact - [2026-09-07] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1X6P302K2RV969H5CEVQWE9", + "id": "01M1X6V40EY1GJQTKKTHD7XD67", + "kind": "memory", + "score": 0.9049031734466552, + "summary": "project:fact - [2026-09-07] [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment — changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate." + }, + { + "expansion_handle": "memory:01M1X6NZN0FVTV36T1B6KBFMKZ", + "id": "01M1X6V40EACE1V3ASY6EKNB6J", + "kind": "memory", + "score": 0.4812128245830536, + "summary": "project:fact - [2026-09-07] [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1009.7391, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2046, + "mcp_result_bytes": 2211, + "wire_bytes": 2248, + "reported_used_tokens": 2211, + "working_set_bytes": 294940672, + "peak_working_set_bytes": 295854080 + }, + { + "query": "noise capsules consuming token budget without contributing retrieval signal", + "ranked": [ + "kimetsu-capsule-budgets" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3K1QHGWBDZK08Q9G2WJ", + "id": "01M1X6V4Z69AE0W00M1MBKP78M", + "kind": "memory", + "score": 0.9997420907020568, + "summary": "project:fact - [tags: kimetsu capsule tokens budget retrieval] kimetsu retrieval enforces a token budget per capsule type: memory capsules are capped at 6000 tokens total (across all retrieved memories), file capsules at 3000 tokens. When a memory is large and would exceed the budget, it is truncated at a sentence boundary. The budget is enforced AFTER reranking — reranking may reorder results so that a truncated high-ranked memory displaces a full lower-ranked one." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 777.2112, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 847, + "mcp_result_bytes": 928, + "wire_bytes": 965, + "reported_used_tokens": 928, + "working_set_bytes": 294940672, + "peak_working_set_bytes": 295854080 + }, + { + "query": "kimetsu_brain_record writes to the wrong brain location — user vs project scope", + "ranked": [ + "kimetsu-memory-scopes", + "kimetsu-write-tools-gate", + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3KY5MF7R4RJB34EHA6R", + "id": "01M1X6V5QSNFC4J5T03FC1ZZ07", + "kind": "memory", + "score": 0.999030828475952, + "summary": "project:fact - [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available — if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope." + }, + { + "expansion_handle": "memory:01M1X6P3Q18YAX03ZC3P1KS0CF", + "id": "01M1X6V5QSG70X6W226QY6CGZQ", + "kind": "memory", + "score": 0.9838979840278624, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level — disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1X6P00PJD8FDG3QR2RS7QAK", + "id": "01M1X6V5QS3V6WP72ZKFSX41G5", + "kind": "memory", + "score": 0.3852712512016296, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 911.9002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2098, + "mcp_result_bytes": 2215, + "wire_bytes": 2252, + "reported_used_tokens": 2215, + "working_set_bytes": 294940672, + "peak_working_set_bytes": 295854080 + }, + { + "query": "how do I configure kimetsu to use Claude Haiku for harvesting but Opus for the agent?", + "ranked": [ + "kimetsu-distiller-config" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3MZ6G96CDQ3F49KXK93", + "id": "01M1X6V6M43YZXWSM6D6C0Z8Y6", + "kind": "memory", + "score": 0.9989088773727416, + "summary": "project:fact - [tags: kimetsu distiller harvest config provider] The kimetsu distiller (auto-harvester) uses a SEPARATE provider configuration from the main agent: `distiller.provider`, `distiller.model`, `distiller.api_key`. This allows running the agent on an expensive model (Claude Opus) while harvesting with a cheap model (Claude Haiku). If `distiller.provider` is not set, it inherits `provider`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 941.1107, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 778, + "mcp_result_bytes": 859, + "wire_bytes": 896, + "reported_used_tokens": 859, + "working_set_bytes": 294940672, + "peak_working_set_bytes": 295854080 + }, + { + "query": "first agent turn is slow because kimetsu proactive hook runs embedding inference", + "ranked": [ + "kimetsu-proactive-hooks", + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3P27FMRCY89DFW7MFKS", + "id": "01M1X6V7HNM710D6FZ0KDH5WXC", + "kind": "memory", + "score": 0.999568521976471, + "summary": "project:fact - [2026-09-07] [tags: kimetsu proactive hooks context injection] kimetsu's proactive context injection runs before each agent turn (pre-turn hook) and injects relevant memories into the system prompt prefix. The hook invocation adds latency to the first token: embedding inference + vector search + reranking + context formatting. On a cold start, this can be 1-3 seconds." + }, + { + "expansion_handle": "memory:01M1X6P2YZ4YRDGDVJV5XMKCP1", + "id": "01M1X6V7HNFS87HD26ZB609T2B", + "kind": "memory", + "score": 0.9405298233032228, + "summary": "project:fact - [2026-09-07] [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking — in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize — keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 979.679, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1403, + "mcp_result_bytes": 1502, + "wire_bytes": 1539, + "reported_used_tokens": 1502, + "working_set_bytes": 294940672, + "peak_working_set_bytes": 295854080 + }, + { + "query": "make the kimetsu brain read-only for certain repos on a shared remote server", + "ranked": [ + "kimetsu-write-tools-gate", + "remote-ingest-split-roots", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3Q18YAX03ZC3P1KS0CF", + "id": "01M1X6V8GNP9TPXE2ZPS3DRJ8P", + "kind": "memory", + "score": 0.997682809829712, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level — disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1X6NZK0NCCC1P19NW0KM4XV", + "id": "01M1X6V8GNJC3ZSEHMZK6QAWM7", + "kind": "memory", + "score": 0.9957050681114196, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1X6NZN0FVTV36T1B6KBFMKZ", + "id": "01M1X6V8GNY3CWACG49GQY0A04", + "kind": "memory", + "score": 0.9909282326698304, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 918.7103, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2725, + "mcp_result_bytes": 2890, + "wire_bytes": 2927, + "reported_used_tokens": 2890, + "working_set_bytes": 294940672, + "peak_working_set_bytes": 295854080 + }, + { + "query": "kimetsu FTS search misses 'deadlocking' when memory says 'deadlock'", + "ranked": [ + "kimetsu-query-stemming", + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3TM4S5HK5WSJCSCQQHF", + "id": "01M1X6V9CY304YPQSATXDBH499", + "kind": "memory", + "score": 0.9904030561447144, + "summary": "project:fact - [2026-09-07] [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression." + }, + { + "expansion_handle": "memory:01M1X6NZHYGSE0FR8XFDPNJP6E", + "id": "01M1X6V9CYDGV6A8EAKCXCTNXX", + "kind": "memory", + "score": 0.91664320230484, + "summary": "project:fact - [2026-09-07] [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure — `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1141.5236, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1363, + "mcp_result_bytes": 1478, + "wire_bytes": 1515, + "reported_used_tokens": 1478, + "working_set_bytes": 295002112, + "peak_working_set_bytes": 295911424 + }, + { + "query": "how does pool size affect retrieval recall and latency in the bench?", + "ranked": [ + "kimetsu-rerank-pool" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3VMMSAJGSK3MBD4B2GV", + "id": "01M1X6VAGJDM5F834J7KBRF6TZ", + "kind": "memory", + "score": 0.9998373985290528, + "summary": "project:fact - [tags: kimetsu reranker pool size ann retrieval] kimetsu's retrieval pipeline: ANN (approximate nearest neighbor) retrieves a pool of candidates, then the reranker reorders them, then the top-K are returned. The pool size (default 6 for production, 12 in bench) controls the recall-latency tradeoff: larger pool = higher recall = more reranker calls = more latency. For the jina-tiny reranker, pool 12 adds ~80ms vs pool 6." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 911.9508, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 813, + "mcp_result_bytes": 894, + "wire_bytes": 931, + "reported_used_tokens": 894, + "working_set_bytes": 295002112, + "peak_working_set_bytes": 295919616 + }, + { + "query": "second embedder in a remote bench run gets worse results than the first", + "ranked": [ + "kimetsu-bench-remote-embedder-singleton" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3WKNTTE5DK6PSVCEB7R", + "id": "01M1X6VBD3QGDKMJGS0GJTEGYZ", + "kind": "memory", + "score": 0.9939629435539246, + "summary": "project:fact - [2026-09-07] [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 913.0019, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 895, + "mcp_result_bytes": 976, + "wire_bytes": 1013, + "reported_used_tokens": 976, + "working_set_bytes": 295137280, + "peak_working_set_bytes": 296054784 + }, + { + "query": "what is the expected JSON schema for kimetsu brain bench dataset files?", + "ranked": [ + "kimetsu-eval-fixture-shape", + "testing-fixture-drift", + "kimetsu-mrr-metric", + "mcp-schema-validation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3XM0BN6GY22JB0THAHB", + "id": "01M1X6VC9WHKSDA5QHBCDQ11TC", + "kind": "memory", + "score": 0.9996767044067384, + "summary": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` — a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases)." + }, + { + "expansion_handle": "memory:01M1X6P2X1Q84DSXWGJTFEX3G2", + "id": "01M1X6VC9WZ413V6QH147HAHRK", + "kind": "memory", + "score": 0.9682880640029908, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + }, + { + "expansion_handle": "memory:01M1X6P3YTF6X09YDCSHS6P0JQ", + "id": "01M1X6VC9W9ZHJSP0MJB1PWJH3", + "kind": "memory", + "score": 0.8818408250808716, + "summary": "project:fact - [tags: kimetsu bench mrr recall metrics evaluation] kimetsu bench reports MRR (Mean Reciprocal Rank) and Recall@K. MRR is 1/rank_of_first_relevant_result, averaged across cases; it penalizes models that rank the correct answer 2nd or 3rd. Recall@K is the fraction of cases where at least one relevant answer appears in the top K." + }, + { + "expansion_handle": "memory:01M1X6P31407BS4T0BV7PGT98H", + "id": "01M1X6VC9WNWMF3KPJYDEJDQE1", + "kind": "memory", + "score": 0.6527947187423706, + "summary": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array — omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 944.4823, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2424, + "mcp_result_bytes": 2603, + "wire_bytes": 2640, + "reported_used_tokens": 2603, + "working_set_bytes": 295190528, + "peak_working_set_bytes": 296103936 + }, + { + "query": "what does MRR mean and how do I interpret a 0.01 difference between combos?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 947.7141, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 295305216, + "peak_working_set_bytes": 296218624 + }, + { + "query": "SQLITE_BUSY keeps appearing even with WAL mode enabled", + "ranked": [ + "sqlite-busy-timeout-wal", + "sqlite-wal-network-drive" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0A6JDJBPYZTYEXJX1EZ", + "id": "01M1X6VE4TQTVPV4Z3YNYETZ24", + "kind": "memory", + "score": 0.9982662796974182, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + }, + { + "expansion_handle": "memory:01M1X6P0CJBPZG72ZW2NCZ8ST6", + "id": "01M1X6VE4TDCZ7RKEB48C99FVW", + "kind": "memory", + "score": 0.7844027280807495, + "summary": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 961.9783, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1423, + "mcp_result_bytes": 1522, + "wire_bytes": 1559, + "reported_used_tokens": 1522, + "working_set_bytes": 295346176, + "peak_working_set_bytes": 296259584 + }, + { + "query": "my brain file got huge again right after I compacted it", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 897.7642999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 295350272, + "peak_working_set_bytes": 296259584 + }, + { + "query": "all my FTS queries stopped returning results after I changed the tokenizer config", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 952.4657, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 295350272, + "peak_working_set_bytes": 296263680 + }, + { + "query": "something is preventing the kimetsu binary from being replaced during update", + "ranked": [ + "kimetsu-daemon-lifecycle", + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P3J1H7C1NPH0T2G7FWCC", + "id": "01M1X6VGXEWR2QDTSSCM44NPV0", + "kind": "memory", + "score": 0.9678457975387572, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1X6P08364QKMJ37XXNKFWJS", + "id": "01M1X6VGXE309S34A232KF1FNV", + "kind": "memory", + "score": 0.9395453929901124, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics — mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 0.6666666666666666, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 869.7433, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1657, + "mcp_result_bytes": 1756, + "wire_bytes": 1793, + "reported_used_tokens": 1756, + "working_set_bytes": 295354368, + "peak_working_set_bytes": 296267776 + }, + { + "query": "tool call results not appearing in the context — is the semantic floor too high?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 981.0944999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 295354368, + "peak_working_set_bytes": 296275968 + }, + { + "query": "CARGO_INCREMENTAL=0 in CI prevents a class of spurious compilation errors", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0QQQS1JZYGKXH9ZX9RW", + "id": "01M1X6VJQ35MG6TQYT9G2W695T", + "kind": "memory", + "score": 0.7995238304138184, + "summary": "project:fact - [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 951.0018, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 877, + "mcp_result_bytes": 958, + "wire_bytes": 995, + "reported_used_tokens": 958, + "working_set_bytes": 295354368, + "peak_working_set_bytes": 296275968 + }, + { + "query": "how do I check whether my Cargo workspace respects the MSRV constraint?", + "ranked": [ + "cargo-msrv", + "cargo-dev-dep-leak", + "cargo-patch-section", + "cargo-target-dir-sharing" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0TQV281HN337V782S05", + "id": "01M1X6VKMHD7RPPNB564J0X4Y2", + "kind": "memory", + "score": 0.9921064376831056, + "summary": "project:fact - [tags: cargo rust msrv edition compatibility] Set `rust-version` in each `Cargo.toml` to declare the minimum supported Rust version (MSRV). Cargo enforces this with `--check`: `cargo check` fails if the toolchain is older than `rust-version`. Keep MSRV as old as your oldest supported deployment target." + }, + { + "expansion_handle": "memory:01M1X6P0NMHGDXYV47S0SSW2XA", + "id": "01M1X6VKMH6KAFNPMDQTEA6C9X", + "kind": "memory", + "score": 0.887407660484314, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + }, + { + "expansion_handle": "memory:01M1X6P0SRM17TYNHXSXEWSP31", + "id": "01M1X6VKMH2PGN2DJK448NTH54", + "kind": "memory", + "score": 0.7220955491065979, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace — including transitive deps — that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1X6P0PQ9PDB9P815QJTY464", + "id": "01M1X6VKMHDRQTBQX91SFBY0ZQ", + "kind": "memory", + "score": 0.4095200598239898, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps — use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 937.0001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2682, + "mcp_result_bytes": 2821, + "wire_bytes": 2858, + "reported_used_tokens": 2821, + "working_set_bytes": 295354368, + "peak_working_set_bytes": 296275968 + }, + { + "query": "rusqlite connection opened but ON DELETE CASCADE cascade never fires", + "ranked": [ + "sqlite-foreign-keys-default-off" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P0FMEPPEMPPJJ176K5CE", + "id": "01M1X6VMHXMTKFW9G4RVFVWR6M", + "kind": "memory", + "score": 0.9922945499420166, + "summary": "project:fact - [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting — every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 911.2325, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 735, + "mcp_result_bytes": 816, + "wire_bytes": 853, + "reported_used_tokens": 816, + "working_set_bytes": 295354368, + "peak_working_set_bytes": 296275968 + }, + { + "query": "I cannot connect to kimetsu-remote — something about TLS cert validation failed", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 893.6359, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 295354368, + "peak_working_set_bytes": 296275968 + }, + { + "query": "graceful shutdown fails because in-flight SQLite queries are still running when pool closes", + "ranked": [ + "tokio-shutdown-ordering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P2CZ739FX2TTZAGZGTRW", + "id": "01M1X6VPA5YD571Q2V9YWX0H3H", + "kind": "memory", + "score": 0.9996342658996582, + "summary": "project:fact - [2026-09-07] [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries — the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 889.4746, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 947, + "mcp_result_bytes": 1028, + "wire_bytes": 1065, + "reported_used_tokens": 1028, + "working_set_bytes": 295374848, + "peak_working_set_bytes": 296288256 + }, + { + "query": "kimetsu-remote response takes 8 seconds — which stage is slow?", + "ranked": [ + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P2YZ4YRDGDVJV5XMKCP1", + "id": "01M1X6VQ67PE24QFP63EED6JFH", + "kind": "memory", + "score": 0.9876242876052856, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking — in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize — keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 955.9803, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 858, + "mcp_result_bytes": 939, + "wire_bytes": 976, + "reported_used_tokens": 939, + "working_set_bytes": 295374848, + "peak_working_set_bytes": 296288256 + }, + { + "query": "git reflog to rescue accidentally deleted branch", + "ranked": [ + "git-reflog-rescue" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P23PDPP71F97F0Z839MX", + "id": "01M1X6VR41N967WTBNTVE501NM", + "kind": "memory", + "score": 0.998464822769165, + "summary": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone — they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only — remote reflog is not accessible via normal git commands." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 988.5865, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 761, + "mcp_result_bytes": 842, + "wire_bytes": 879, + "reported_used_tokens": 842, + "working_set_bytes": 295374848, + "peak_working_set_bytes": 296288256 + }, + { + "query": "git submodule --remote advances the pinned SHA unexpectedly", + "ranked": [ + "git-submodule-pinning", + "git-reflog-rescue", + "ci-secrets-masking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P22PHYS4T5P5F3PE7N1M", + "id": "01M1X6VS3HAD4N84TKQJ8WBWT8", + "kind": "memory", + "score": 0.9998551607131958, + "summary": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip — this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version." + }, + { + "expansion_handle": "memory:01M1X6P23PDPP71F97F0Z839MX", + "id": "01M1X6VS3HFAM2NS1YDCYY3PDK", + "kind": "memory", + "score": 0.8857361078262329, + "summary": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone — they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only — remote reflog is not accessible via normal git commands." + }, + { + "expansion_handle": "memory:01M1X6P3F12CWS8FE5S50H7TYH", + "id": "01M1X6VS3HQWZMYJKAH48Y9VQT", + "kind": "memory", + "score": 0.8434544205665588, + "summary": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output — but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 902.4643, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1771, + "mcp_result_bytes": 1888, + "wire_bytes": 1925, + "reported_used_tokens": 1888, + "working_set_bytes": 295391232, + "peak_working_set_bytes": 296296448 + }, + { + "query": "axum SSE streaming drops the last event when client disconnects", + "ranked": [ + "http-streaming-bodies" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P2JNVC441XJQFSMYT0SJ", + "id": "01M1X6VSZ0VRG0ZV1KP62KZ0X4", + "kind": "memory", + "score": 0.9926375150680542, + "summary": "project:fact - [2026-09-07] [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding — a chunk may split across frame boundaries." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 947.4878, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 859, + "mcp_result_bytes": 940, + "wire_bytes": 977, + "reported_used_tokens": 940, + "working_set_bytes": 295407616, + "peak_working_set_bytes": 296321024 + }, + { + "query": "how do I detect that I am running inside a git worktree vs the main checkout?", + "ranked": [ + "git-worktree-brain-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1YWTX8Y6NQRBF82KS3J", + "id": "01M1X6VTWP07Z6888CDJMS6EWB", + "kind": "memory", + "score": 0.9857924580574036, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root — if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 932.0207, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 881, + "mcp_result_bytes": 962, + "wire_bytes": 999, + "reported_used_tokens": 962, + "working_set_bytes": 295407616, + "peak_working_set_bytes": 296325120 + }, + { + "query": "ONNX Runtime intra-op threads causing CPU contention during parallel bench", + "ranked": [ + "onnx-ort-threading", + "tokio-blocking-in-async" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P1XX3WCG6HCFJES7B7FT", + "id": "01M1X6VVSV0N016CEGSCKZJPJH", + "kind": "memory", + "score": 0.9999210834503174, + "summary": "project:fact - [tags: onnx ort thread-pool parallelism cpu] ORT (ONNX Runtime) creates its own inter-op and intra-op thread pools. In a multi-process bench setup, each child inherits these pools and they compete for CPU cores. Set `SessionOptionsBuilder::with_intra_threads(1).with_inter_threads(1)` if you're running many parallel bench processes — this sacrifices per-inference throughput for lower contention." + }, + { + "expansion_handle": "memory:01M1X6P24PG6KXJZQB4K5G8MKX", + "id": "01M1X6VVSVJH3GDRJ80J7JVT5G", + "kind": "memory", + "score": 0.5390238761901855, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking — never call rusqlite directly from an async fn without spawn_blocking." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 848.3687, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1328, + "mcp_result_bytes": 1427, + "wire_bytes": 1464, + "reported_used_tokens": 1427, + "working_set_bytes": 295444480, + "peak_working_set_bytes": 296357888 + }, + { + "query": "what is the right way to supply AWS session token alongside access key and secret?", + "ranked": [ + "aws-credentials-chain" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6P34CEH6KSKA057ZQRBCP", + "id": "01M1X6VWMAPS9ZX7H8Q9BCFVC1", + "kind": "memory", + "score": 0.9493365287780762, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 904.8004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 895, + "mcp_result_bytes": 976, + "wire_bytes": 1013, + "reported_used_tokens": 976, + "working_set_bytes": 295444480, + "peak_working_set_bytes": 296357888 + } + ], + "id": "existing-development-100", + "dimension": "retrieval", + "tier": "hard", + "score": 0.8182539682539681, + "skipped": false, + "detail": "positive-recall@4=0.84 mrr=0.85 stale-hit=n/a resolution=n/a false-injection=0.538 (n=13) positive-n=197 negative-n=13 (210 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 0.8182539682539681, + 1 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 0.8182539682539681, + "n": 1, + "ci95": null + } + }, + "overall_index": 0.8182539682539681, + "scenario_weighted_index": 0.8182539682539681 +} diff --git a/docs/audits/2026-09-07-answerability/results/missing-fact-development/1-baseline.stderr.log b/docs/audits/2026-09-07-answerability/results/missing-fact-development/1-baseline.stderr.log new file mode 100644 index 0000000..c513814 --- /dev/null +++ b/docs/audits/2026-09-07-answerability/results/missing-fact-development/1-baseline.stderr.log @@ -0,0 +1,10 @@ +brainbench: 4 scenario(s) to run + [1/4] copper-unseen-project | dim=retrieval tier=hard ... + -> score=0.72 | positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.833 (n=6) positive-n=12 negative-n=6 (18 queries) + [2/4] willow-unseen-project | dim=retrieval tier=hard ... + -> score=0.72 | positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.833 (n=6) positive-n=12 negative-n=6 (18 queries) + [3/4] marble-unseen-project | dim=retrieval tier=hard ... + -> score=0.78 | positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.667 (n=6) positive-n=12 negative-n=6 (18 queries) + [4/4] kestrel-unseen-project | dim=retrieval tier=hard ... + -> score=0.72 | positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.833 (n=6) positive-n=12 negative-n=6 (18 queries) +kbench brainbench: report saved -> E:\tmp\kimetsu-brain-hardening\bench\local\runs\brainbench\2026-09-07T05-54-19.6861228Z.json diff --git a/docs/audits/2026-09-07-answerability/results/missing-fact-development/1-baseline.stdout.log b/docs/audits/2026-09-07-answerability/results/missing-fact-development/1-baseline.stdout.log new file mode 100644 index 0000000..4d7837d --- /dev/null +++ b/docs/audits/2026-09-07-answerability/results/missing-fact-development/1-baseline.stdout.log @@ -0,0 +1,2130 @@ +{ + "generated_at": "2026-09-07T05:54:19.6857432Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-retrieval\\validation-frozen.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "Which TCP port should I connect to for Copper staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6VYTF4626NZZFD75CCKA3", + "id": "01M1X6W0N97TBE3SQVHD9EM7X9", + "kind": "memory", + "score": 0.9998394250869752, + "summary": "project:fact - Copper staging HTTP listener binds TCP port 6319. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1792.5176000000001, + "first_query": true, + "server_startup_ms": 72.62540000000001, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 636710912, + "peak_working_set_bytes": 684904448 + }, + { + "query": "Where should Copper diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6VYTXGJW5YTTAPBJZXW18", + "id": "01M1X6W115XE2E9K3FTRTCDYJ3", + "kind": "memory", + "score": 0.9978247880935668, + "summary": "project:fact - Copper diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X6VYWD6E0MSWCQX73RVVJT", + "id": "01M1X6W115V7WSZB2QY79FQ9WD", + "kind": "memory", + "score": 0.6390834450721741, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 352.26869999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 754, + "mcp_result_bytes": 853, + "wire_bytes": 888, + "reported_used_tokens": 853, + "working_set_bytes": 637181952, + "peak_working_set_bytes": 684904448 + }, + { + "query": "Which command rolls back Copper to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6VYV9SPY5S1WTD6WD1KY3", + "id": "01M1X6W1C1S7JC1M7N0613RSRA", + "kind": "memory", + "score": 0.9998852014541626, + "summary": "project:fact - To roll back Copper to the previous release, run `copperctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 346.6184, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 637370368, + "peak_working_set_bytes": 684904448 + }, + { + "query": "Which region hosts Copper production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6VYVNHJ2ZEZSRYRGWG6DZ", + "id": "01M1X6W1PWQY1HRK2GX4QXWFS2", + "kind": "memory", + "score": 0.9999468326568604, + "summary": "project:fact - Copper production runs in region eu-north-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 344.7042, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 609, + "reported_used_tokens": 574, + "working_set_bytes": 637485056, + "peak_working_set_bytes": 684904448 + }, + { + "query": "At what UTC time do daily Copper database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6VYW06MCYZPC4JY0ATTDA", + "id": "01M1X6W21KWV30TPJ5YVSNZHKG", + "kind": "memory", + "score": 0.9999791383743286, + "summary": "project:fact - Copper daily database backups start at 02:40 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 350.64869999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 587, + "reported_used_tokens": 552, + "working_set_bytes": 637661184, + "peak_working_set_bytes": 684904448 + }, + { + "query": "Which database and journal mode does Copper use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6VYWD6E0MSWCQX73RVVJT", + "id": "01M1X6W2CV4QR72H9J19YRS7B0", + "kind": "memory", + "score": 0.9999594688415528, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 358.66540000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 637722624, + "peak_working_set_bytes": 684904448 + }, + { + "query": "¿A qué puerto TCP debo conectarme para staging de Copper?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6VYTF4626NZZFD75CCKA3", + "id": "01M1X6W2R01F4C03322DRH6DJ8", + "kind": "memory", + "score": 0.9998682737350464, + "summary": "project:fact - Copper staging HTTP listener binds TCP port 6319. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 358.5179, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 640077824, + "peak_working_set_bytes": 684904448 + }, + { + "query": "¿Dónde deben escribirse los mensajes de diagnóstico de Copper?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6VYTXGJW5YTTAPBJZXW18", + "id": "01M1X6W333YJCYQNRHMN856ES0", + "kind": "memory", + "score": 0.9995033740997314, + "summary": "project:fact - Copper diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 363.23870000000005, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 488, + "mcp_result_bytes": 569, + "wire_bytes": 604, + "reported_used_tokens": 569, + "working_set_bytes": 640528384, + "peak_working_set_bytes": 684904448 + }, + { + "query": "¿Qué comando revierte Copper a la versión anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6VYV9SPY5S1WTD6WD1KY3", + "id": "01M1X6W3ECDG36CN2D0SRHAPWF", + "kind": "memory", + "score": 0.9887914657592772, + "summary": "project:fact - To roll back Copper to the previous release, run `copperctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 361.0142, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 616, + "reported_used_tokens": 580, + "working_set_bytes": 640536576, + "peak_working_set_bytes": 684904448 + }, + { + "query": "¿En qué región está desplegado Copper en producción?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6VYVNHJ2ZEZSRYRGWG6DZ", + "id": "01M1X6W3SYNWD3QJCXJZBQ3WT6", + "kind": "memory", + "score": 0.9999468326568604, + "summary": "project:fact - Copper production runs in region eu-north-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 372.3626, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 640552960, + "peak_working_set_bytes": 684904448 + }, + { + "query": "¿A qué hora UTC empiezan las copias diarias de la base de datos de Copper?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6VYW06MCYZPC4JY0ATTDA", + "id": "01M1X6W45VG93628VB8HX2XA9C", + "kind": "memory", + "score": 0.9999747276306152, + "summary": "project:fact - Copper daily database backups start at 02:40 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 373.41380000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 640823296, + "peak_working_set_bytes": 684904448 + }, + { + "query": "¿Qué base de datos y modo de registro usa Copper para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6VYWD6E0MSWCQX73RVVJT", + "id": "01M1X6W4GZV2KBQPXGR4V2767R", + "kind": "memory", + "score": 0.9983224272727966, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 355.87390000000005, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 640851968, + "peak_working_set_bytes": 684904448 + }, + { + "query": "What authentication password is configured for the Copper staging listener?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6VYTF4626NZZFD75CCKA3", + "id": "01M1X6W4W49ZXJP95V4ARSADYQ", + "kind": "memory", + "score": 0.9784963130950928, + "summary": "project:fact - Copper staging HTTP listener binds TCP port 6319. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 351.9925, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 615, + "reported_used_tokens": 579, + "working_set_bytes": 640929792, + "peak_working_set_bytes": 684904448 + }, + { + "query": "What encryption key protects the Copper database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 352.9818, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 646758400, + "peak_working_set_bytes": 684904448 + }, + { + "query": "How many production replicas run in the Copper deployment region?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6VYVNHJ2ZEZSRYRGWG6DZ", + "id": "01M1X6W5JG006RJDNN1S6RBR3C", + "kind": "memory", + "score": 0.8394170999526978, + "summary": "project:fact - Copper production runs in region eu-north-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 366.6964, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 646803456, + "peak_working_set_bytes": 684904448 + }, + { + "query": "¿Qué contraseña exige el servidor de staging de Copper?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6VYTF4626NZZFD75CCKA3", + "id": "01M1X6W5XNG54HFV390E36YMW5", + "kind": "memory", + "score": 0.898059606552124, + "summary": "project:fact - Copper staging HTTP listener binds TCP port 6319. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 355.79110000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 497, + "mcp_result_bytes": 578, + "wire_bytes": 614, + "reported_used_tokens": 578, + "working_set_bytes": 646946816, + "peak_working_set_bytes": 684904448 + }, + { + "query": "¿Cuántos días se conservan las copias de seguridad de Copper?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6VYW06MCYZPC4JY0ATTDA", + "id": "01M1X6W68R8T1VB0MJ2JSFEEFN", + "kind": "memory", + "score": 0.9550348520278932, + "summary": "project:fact - Copper daily database backups start at 02:40 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 362.2124, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 647012352, + "peak_working_set_bytes": 684904448 + }, + { + "query": "¿Qué versión de SQLite requiere Copper?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6VYWD6E0MSWCQX73RVVJT", + "id": "01M1X6W6M0TW6SBRY68JPS68A5", + "kind": "memory", + "score": 0.6008884310722351, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 350.2858, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 647061504, + "peak_working_set_bytes": 684904448 + } + ], + "id": "copper-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 0.7222222222222222, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.833 (n=6) positive-n=12 negative-n=6 (18 queries)" + }, + { + "observations": [ + { + "query": "Which TCP port should I connect to for Willow staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6W80485CMCCPMETKBZ0SX", + "id": "01M1X6W9SD69T8C5W8A5FA3Y35", + "kind": "memory", + "score": 0.9999420642852784, + "summary": "project:fact - Willow staging HTTP listener binds TCP port 7421. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1769.1063, + "first_query": true, + "server_startup_ms": 72.8164, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 633749504, + "peak_working_set_bytes": 685101056 + }, + { + "query": "Where should Willow diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6W80M86SH2X85WHMQTBT2", + "id": "01M1X6WA4HR1APZXPPFK6SYZFH", + "kind": "memory", + "score": 0.9971635937690736, + "summary": "project:fact - Willow diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X6W821WEPBVZKB1N95W3VJ", + "id": "01M1X6WA4H6138J0Z6KNGAWAVG", + "kind": "memory", + "score": 0.8971153497695923, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 347.5552, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 754, + "mcp_result_bytes": 853, + "wire_bytes": 888, + "reported_used_tokens": 853, + "working_set_bytes": 634281984, + "peak_working_set_bytes": 685101056 + }, + { + "query": "Which command rolls back Willow to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6W80ZW8B16PYR088WFEPF", + "id": "01M1X6WAFCYJA3725GZ9MKWC9G", + "kind": "memory", + "score": 0.9998542070388794, + "summary": "project:fact - To roll back Willow to the previous release, run `willowctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 352.1509, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 634376192, + "peak_working_set_bytes": 685101056 + }, + { + "query": "Which region hosts Willow production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6W81AHS659WR9H3NH38Y3", + "id": "01M1X6WAT99Q24ZFZNDZQ6GRH9", + "kind": "memory", + "score": 0.9999713897705078, + "summary": "project:fact - Willow production runs in region us-west-2. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 341.2932, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 634396672, + "peak_working_set_bytes": 685101056 + }, + { + "query": "At what UTC time do daily Willow database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6W81NG5523YCKRTEHZK4F", + "id": "01M1X6WB53JHH3FGT87H3BVXPJ", + "kind": "memory", + "score": 0.9999792575836182, + "summary": "project:fact - Willow daily database backups start at 04:15 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 359.1488, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 587, + "reported_used_tokens": 552, + "working_set_bytes": 634556416, + "peak_working_set_bytes": 685101056 + }, + { + "query": "Which database and journal mode does Willow use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6W821WEPBVZKB1N95W3VJ", + "id": "01M1X6WBGAS48VNTZY28767PH0", + "kind": "memory", + "score": 0.9999637603759766, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 355.4484, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 634601472, + "peak_working_set_bytes": 685101056 + }, + { + "query": "¿A qué puerto TCP debo conectarme para staging de Willow?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6W80485CMCCPMETKBZ0SX", + "id": "01M1X6WBVMD71XYQK6VBADCTPS", + "kind": "memory", + "score": 0.9999486207962036, + "summary": "project:fact - Willow staging HTTP listener binds TCP port 7421. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 382.2115, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 636870656, + "peak_working_set_bytes": 685101056 + }, + { + "query": "¿Dónde deben escribirse los mensajes de diagnóstico de Willow?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6W80M86SH2X85WHMQTBT2", + "id": "01M1X6WC7CKZAQF7M88RNJ4ZTV", + "kind": "memory", + "score": 0.9996838569641112, + "summary": "project:fact - Willow diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 353.988, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 488, + "mcp_result_bytes": 569, + "wire_bytes": 604, + "reported_used_tokens": 569, + "working_set_bytes": 637177856, + "peak_working_set_bytes": 685101056 + }, + { + "query": "¿Qué comando revierte Willow a la versión anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6W80ZW8B16PYR088WFEPF", + "id": "01M1X6WCJEESGZ6N6AJDP0AMAV", + "kind": "memory", + "score": 0.98951655626297, + "summary": "project:fact - To roll back Willow to the previous release, run `willowctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 352.9806, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 497, + "mcp_result_bytes": 578, + "wire_bytes": 614, + "reported_used_tokens": 578, + "working_set_bytes": 637231104, + "peak_working_set_bytes": 685101056 + }, + { + "query": "¿En qué región está desplegado Willow en producción?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6W81AHS659WR9H3NH38Y3", + "id": "01M1X6WCXFE03SKBW6E97AJV8J", + "kind": "memory", + "score": 0.9999579191207886, + "summary": "project:fact - Willow production runs in region us-west-2. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 351.45029999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 637267968, + "peak_working_set_bytes": 685101056 + }, + { + "query": "¿A qué hora UTC empiezan las copias diarias de la base de datos de Willow?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6W81NG5523YCKRTEHZK4F", + "id": "01M1X6WD8H2WN4ZTD26MJT04PJ", + "kind": "memory", + "score": 0.99997878074646, + "summary": "project:fact - Willow daily database backups start at 04:15 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 364.5579, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 469, + "mcp_result_bytes": 550, + "wire_bytes": 586, + "reported_used_tokens": 550, + "working_set_bytes": 637501440, + "peak_working_set_bytes": 685101056 + }, + { + "query": "¿Qué base de datos y modo de registro usa Willow para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6W821WEPBVZKB1N95W3VJ", + "id": "01M1X6WDM39QAP5J93GXRYSGZ9", + "kind": "memory", + "score": 0.999204695224762, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 362.1442, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 491, + "mcp_result_bytes": 572, + "wire_bytes": 608, + "reported_used_tokens": 572, + "working_set_bytes": 637534208, + "peak_working_set_bytes": 685101056 + }, + { + "query": "What authentication password is configured for the Willow staging listener?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6W80485CMCCPMETKBZ0SX", + "id": "01M1X6WDZ6XEC892Y4EM4RKWFR", + "kind": "memory", + "score": 0.9932246804237366, + "summary": "project:fact - Willow staging HTTP listener binds TCP port 7421. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 353.0434, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 615, + "reported_used_tokens": 579, + "working_set_bytes": 637566976, + "peak_working_set_bytes": 685101056 + }, + { + "query": "What encryption key protects the Willow database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 358.7416, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643379200, + "peak_working_set_bytes": 685101056 + }, + { + "query": "How many production replicas run in the Willow deployment region?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6W81AHS659WR9H3NH38Y3", + "id": "01M1X6WENDTRV75G218J1CY7E9", + "kind": "memory", + "score": 0.9349143505096436, + "summary": "project:fact - Willow production runs in region us-west-2. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 360.8331, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 643395584, + "peak_working_set_bytes": 685101056 + }, + { + "query": "¿Qué contraseña exige el servidor de staging de Willow?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6W80485CMCCPMETKBZ0SX", + "id": "01M1X6WF0V07AW9HEBDND2PK3W", + "kind": "memory", + "score": 0.9681325554847716, + "summary": "project:fact - Willow staging HTTP listener binds TCP port 7421. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 369.6667, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 615, + "reported_used_tokens": 579, + "working_set_bytes": 643452928, + "peak_working_set_bytes": 685101056 + }, + { + "query": "¿Cuántos días se conservan las copias de seguridad de Willow?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6W81NG5523YCKRTEHZK4F", + "id": "01M1X6WFE0D2V1FH1MM43PSE3E", + "kind": "memory", + "score": 0.9746375679969788, + "summary": "project:fact - Willow daily database backups start at 04:15 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 420.6737, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 643534848, + "peak_working_set_bytes": 685101056 + }, + { + "query": "¿Qué versión de SQLite requiere Willow?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6W821WEPBVZKB1N95W3VJ", + "id": "01M1X6WFT2EK90AMG94WSFHAWT", + "kind": "memory", + "score": 0.7611488103866577, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 381.91740000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 643727360, + "peak_working_set_bytes": 685101056 + } + ], + "id": "willow-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 0.7222222222222222, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.833 (n=6) positive-n=12 negative-n=6 (18 queries)" + }, + { + "observations": [ + { + "query": "Which TCP port should I connect to for Marble staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WH5KFBVXFH7JCSQKXN7N", + "id": "01M1X6WJZK5SG45Q2XYKCQC8R8", + "kind": "memory", + "score": 0.9996737241744996, + "summary": "project:fact - Marble staging HTTP listener binds TCP port 8533. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1782.3248, + "first_query": true, + "server_startup_ms": 81.5187, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 633012224, + "peak_working_set_bytes": 685076480 + }, + { + "query": "Where should Marble diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WH63Z8E8859BYQX8T299", + "id": "01M1X6WKAPMRF6TQSBDSNSCC7W", + "kind": "memory", + "score": 0.9971815347671508, + "summary": "project:fact - Marble diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X6WH7H9Z6WC7SKQJKA9KQM", + "id": "01M1X6WKAPRXYGBNS7NXSEYGX9", + "kind": "memory", + "score": 0.6012999415397644, + "summary": "project:fact - Marble stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 348.7635, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 754, + "mcp_result_bytes": 853, + "wire_bytes": 888, + "reported_used_tokens": 853, + "working_set_bytes": 633520128, + "peak_working_set_bytes": 685076480 + }, + { + "query": "Which command rolls back Marble to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WH6EBZRFDNYZVT3CKHF3", + "id": "01M1X6WKNKDBR1CNYN9VP9FQA3", + "kind": "memory", + "score": 0.9997585415840148, + "summary": "project:fact - To roll back Marble to the previous release, run `marblectl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 355.8333, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 633569280, + "peak_working_set_bytes": 685076480 + }, + { + "query": "Which region hosts Marble production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WH6T405TD74Y4JWMEE4W", + "id": "01M1X6WM14ZNQ8168FWS9EJ9MZ", + "kind": "memory", + "score": 0.999954104423523, + "summary": "project:fact - Marble production runs in region ap-south-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 377.5579, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 633597952, + "peak_working_set_bytes": 685076480 + }, + { + "query": "At what UTC time do daily Marble database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WH76P6HKX6RM89XS5BC8", + "id": "01M1X6WMCNNVG26BHZQTKNPGW1", + "kind": "memory", + "score": 0.999979853630066, + "summary": "project:fact - Marble daily database backups start at 01:25 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 351.1092, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 587, + "reported_used_tokens": 552, + "working_set_bytes": 633720832, + "peak_working_set_bytes": 685076480 + }, + { + "query": "Which database and journal mode does Marble use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WH7H9Z6WC7SKQJKA9KQM", + "id": "01M1X6WMQJRYKSPPCN2H1320T4", + "kind": "memory", + "score": 0.9999568462371826, + "summary": "project:fact - Marble stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 349.9595, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 633802752, + "peak_working_set_bytes": 685076480 + }, + { + "query": "¿A qué puerto TCP debo conectarme para staging de Marble?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WH5KFBVXFH7JCSQKXN7N", + "id": "01M1X6WN2GE3213F4D2J1ATF9P", + "kind": "memory", + "score": 0.9998871088027954, + "summary": "project:fact - Marble staging HTTP listener binds TCP port 8533. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 354.5989, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 636735488, + "peak_working_set_bytes": 685076480 + }, + { + "query": "¿Dónde deben escribirse los mensajes de diagnóstico de Marble?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WH63Z8E8859BYQX8T299", + "id": "01M1X6WNDNHA3BBGXRVSTWXQC6", + "kind": "memory", + "score": 0.9992142915725708, + "summary": "project:fact - Marble diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 354.1943, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 488, + "mcp_result_bytes": 569, + "wire_bytes": 604, + "reported_used_tokens": 569, + "working_set_bytes": 637349888, + "peak_working_set_bytes": 685076480 + }, + { + "query": "¿Qué comando revierte Marble a la versión anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WH6EBZRFDNYZVT3CKHF3", + "id": "01M1X6WNRTVDM4G9XDT7HETQVV", + "kind": "memory", + "score": 0.976457178592682, + "summary": "project:fact - To roll back Marble to the previous release, run `marblectl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 354.989, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 616, + "reported_used_tokens": 580, + "working_set_bytes": 637501440, + "peak_working_set_bytes": 685076480 + }, + { + "query": "¿En qué región está desplegado Marble en producción?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WH6T405TD74Y4JWMEE4W", + "id": "01M1X6WP3YCYXD80FZ5P30JQCA", + "kind": "memory", + "score": 0.9999542236328124, + "summary": "project:fact - Marble production runs in region ap-south-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 357.1646, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 637575168, + "peak_working_set_bytes": 685076480 + }, + { + "query": "¿A qué hora UTC empiezan las copias diarias de la base de datos de Marble?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WH76P6HKX6RM89XS5BC8", + "id": "01M1X6WPF09JZV24DWGVRWR62E", + "kind": "memory", + "score": 0.9999665021896362, + "summary": "project:fact - Marble daily database backups start at 01:25 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 366.478, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 638054400, + "peak_working_set_bytes": 685076480 + }, + { + "query": "¿Qué base de datos y modo de registro usa Marble para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WH7H9Z6WC7SKQJKA9KQM", + "id": "01M1X6WPTGDTBATW1MX4ZYFBCG", + "kind": "memory", + "score": 0.9953057169914246, + "summary": "project:fact - Marble stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 363.191, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 638078976, + "peak_working_set_bytes": 685076480 + }, + { + "query": "What authentication password is configured for the Marble staging listener?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WH5KFBVXFH7JCSQKXN7N", + "id": "01M1X6WQ5ZV4ECKPP9CNH6TSBP", + "kind": "memory", + "score": 0.9787366390228271, + "summary": "project:fact - Marble staging HTTP listener binds TCP port 8533. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 373.0707, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 615, + "reported_used_tokens": 579, + "working_set_bytes": 638222336, + "peak_working_set_bytes": 685076480 + }, + { + "query": "What encryption key protects the Marble database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 366.5888, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644038656, + "peak_working_set_bytes": 685076480 + }, + { + "query": "How many production replicas run in the Marble deployment region?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WH6T405TD74Y4JWMEE4W", + "id": "01M1X6WQWXKKX03ZWBAKY5WJZ9", + "kind": "memory", + "score": 0.8181904554367065, + "summary": "project:fact - Marble production runs in region ap-south-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 355.18899999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 644067328, + "peak_working_set_bytes": 685076480 + }, + { + "query": "¿Qué contraseña exige el servidor de staging de Marble?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WH5KFBVXFH7JCSQKXN7N", + "id": "01M1X6WR86ZNC1NGZ0QY02BCZ9", + "kind": "memory", + "score": 0.8728806972503662, + "summary": "project:fact - Marble staging HTTP listener binds TCP port 8533. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 364.2946, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 615, + "reported_used_tokens": 579, + "working_set_bytes": 644182016, + "peak_working_set_bytes": 685076480 + }, + { + "query": "¿Cuántos días se conservan las copias de seguridad de Marble?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WH76P6HKX6RM89XS5BC8", + "id": "01M1X6WRKQSEVG0FG6TCDTE280", + "kind": "memory", + "score": 0.9691649079322816, + "summary": "project:fact - Marble daily database backups start at 01:25 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 372.3377, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 644218880, + "peak_working_set_bytes": 685076480 + }, + { + "query": "¿Qué versión de SQLite requiere Marble?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 374.79659999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644444160, + "peak_working_set_bytes": 685076480 + } + ], + "id": "marble-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 0.7777777777777778, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.667 (n=6) positive-n=12 negative-n=6 (18 queries)" + }, + { + "observations": [ + { + "query": "Which TCP port should I connect to for Kestrel staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WTC3B4CC1MV6P9AMCKWM", + "id": "01M1X6WW6XC5HWH7GG96YRRE0M", + "kind": "memory", + "score": 0.9999393224716188, + "summary": "project:fact - Kestrel staging HTTP listener binds TCP port 9647. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1806.4251, + "first_query": true, + "server_startup_ms": 82.701, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 636485632, + "peak_working_set_bytes": 685027328 + }, + { + "query": "Where should Kestrel diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WTCJEBFY783Z0H883YSJ", + "id": "01M1X6WWJ4EFVRVY9SNX4Y0B39", + "kind": "memory", + "score": 0.9987480640411376, + "summary": "project:fact - Kestrel diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X6WTE0HZF58SY8864D6TKG", + "id": "01M1X6WWJ4EB6GMBHVGCF14JXW", + "kind": "memory", + "score": 0.9422296285629272, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 347.71020000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 756, + "mcp_result_bytes": 855, + "wire_bytes": 890, + "reported_used_tokens": 855, + "working_set_bytes": 638533632, + "peak_working_set_bytes": 685027328 + }, + { + "query": "Which command rolls back Kestrel to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WTCXN4X9VPN914H5G8BK", + "id": "01M1X6WWX1Y5V6QY6W8N5W92RB", + "kind": "memory", + "score": 0.9997856020927428, + "summary": "project:fact - To roll back Kestrel to the previous release, run `kestrelctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 353.4188, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 501, + "mcp_result_bytes": 582, + "wire_bytes": 617, + "reported_used_tokens": 582, + "working_set_bytes": 638812160, + "peak_working_set_bytes": 685027328 + }, + { + "query": "Which region hosts Kestrel production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WTDAAPJ9TWGJRVR1AN05", + "id": "01M1X6WX80S0M6SXJ5BDEDVBGZ", + "kind": "memory", + "score": 0.9999779462814332, + "summary": "project:fact - Kestrel production runs in region eu-west-3. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 345.63100000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 609, + "reported_used_tokens": 574, + "working_set_bytes": 638844928, + "peak_working_set_bytes": 685027328 + }, + { + "query": "At what UTC time do daily Kestrel database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WTDNZFPX3HR9G8QGR65V", + "id": "01M1X6WXJX916V9BE7C2JXWR1R", + "kind": "memory", + "score": 0.999980330467224, + "summary": "project:fact - Kestrel daily database backups start at 03:50 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 354.9054, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 472, + "mcp_result_bytes": 553, + "wire_bytes": 588, + "reported_used_tokens": 553, + "working_set_bytes": 639127552, + "peak_working_set_bytes": 685027328 + }, + { + "query": "Which database and journal mode does Kestrel use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WTE0HZF58SY8864D6TKG", + "id": "01M1X6WXY7C3JFA2TV8WBABR26", + "kind": "memory", + "score": 0.999975323677063, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 361.6535, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 639156224, + "peak_working_set_bytes": 685027328 + }, + { + "query": "¿A qué puerto TCP debo conectarme para staging de Kestrel?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WTC3B4CC1MV6P9AMCKWM", + "id": "01M1X6WY9F827A6XSV517Y77BG", + "kind": "memory", + "score": 0.9999423027038574, + "summary": "project:fact - Kestrel staging HTTP listener binds TCP port 9647. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 362.04609999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 641765376, + "peak_working_set_bytes": 685027328 + }, + { + "query": "¿Dónde deben escribirse los mensajes de diagnóstico de Kestrel?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WTCJEBFY783Z0H883YSJ", + "id": "01M1X6WYMX97AMDX43VRYFVD7A", + "kind": "memory", + "score": 0.9997678399086, + "summary": "project:fact - Kestrel diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 362.5642, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 489, + "mcp_result_bytes": 570, + "wire_bytes": 605, + "reported_used_tokens": 570, + "working_set_bytes": 641912832, + "peak_working_set_bytes": 685027328 + }, + { + "query": "¿Qué comando revierte Kestrel a la versión anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WTCXN4X9VPN914H5G8BK", + "id": "01M1X6WZ09NES6A195AWWMM48M", + "kind": "memory", + "score": 0.9766082763671876, + "summary": "project:fact - To roll back Kestrel to the previous release, run `kestrelctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 365.9868, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 501, + "mcp_result_bytes": 582, + "wire_bytes": 618, + "reported_used_tokens": 582, + "working_set_bytes": 642027520, + "peak_working_set_bytes": 685027328 + }, + { + "query": "¿En qué región está desplegado Kestrel en producción?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WTDAAPJ9TWGJRVR1AN05", + "id": "01M1X6WZCF49YM4ADRJXZMN0XW", + "kind": "memory", + "score": 0.999974250793457, + "summary": "project:fact - Kestrel production runs in region eu-west-3. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 388.0634, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 642060288, + "peak_working_set_bytes": 685027328 + }, + { + "query": "¿A qué hora UTC empiezan las copias diarias de la base de datos de Kestrel?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WTDNZFPX3HR9G8QGR65V", + "id": "01M1X6WZQM63MSRWR02R1YHQ8K", + "kind": "memory", + "score": 0.9999799728393556, + "summary": "project:fact - Kestrel daily database backups start at 03:50 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 357.6211, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 472, + "mcp_result_bytes": 553, + "wire_bytes": 589, + "reported_used_tokens": 553, + "working_set_bytes": 642473984, + "peak_working_set_bytes": 685027328 + }, + { + "query": "¿Qué base de datos y modo de registro usa Kestrel para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WTE0HZF58SY8864D6TKG", + "id": "01M1X6X02T09C7GYHPTEWEQ2MR", + "kind": "memory", + "score": 0.9993937015533448, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 362.96450000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 642560000, + "peak_working_set_bytes": 685027328 + }, + { + "query": "What authentication password is configured for the Kestrel staging listener?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WTC3B4CC1MV6P9AMCKWM", + "id": "01M1X6X0E4AEW6528008DMP3Z0", + "kind": "memory", + "score": 0.9726881980895996, + "summary": "project:fact - Kestrel staging HTTP listener binds TCP port 9647. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 360.3472, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 616, + "reported_used_tokens": 580, + "working_set_bytes": 642605056, + "peak_working_set_bytes": 685027328 + }, + { + "query": "What encryption key protects the Kestrel database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 357.52909999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 646750208, + "peak_working_set_bytes": 685027328 + }, + { + "query": "How many production replicas run in the Kestrel deployment region?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WTDAAPJ9TWGJRVR1AN05", + "id": "01M1X6X14S8XXX7GRGQW7CDS1C", + "kind": "memory", + "score": 0.958982229232788, + "summary": "project:fact - Kestrel production runs in region eu-west-3. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 377.41859999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 646770688, + "peak_working_set_bytes": 685027328 + }, + { + "query": "¿Qué contraseña exige el servidor de staging de Kestrel?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WTC3B4CC1MV6P9AMCKWM", + "id": "01M1X6X1GC3PFVBE560XRJQHNQ", + "kind": "memory", + "score": 0.9609549045562744, + "summary": "project:fact - Kestrel staging HTTP listener binds TCP port 9647. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 356.8262, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 616, + "reported_used_tokens": 580, + "working_set_bytes": 646873088, + "peak_working_set_bytes": 685027328 + }, + { + "query": "¿Cuántos días se conservan las copias de seguridad de Kestrel?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WTDNZFPX3HR9G8QGR65V", + "id": "01M1X6X1VN0Y4MQE20A6T65SR6", + "kind": "memory", + "score": 0.9748653173446656, + "summary": "project:fact - Kestrel daily database backups start at 03:50 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 366.2564, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 472, + "mcp_result_bytes": 553, + "wire_bytes": 589, + "reported_used_tokens": 553, + "working_set_bytes": 646914048, + "peak_working_set_bytes": 685027328 + }, + { + "query": "¿Qué versión de SQLite requiere Kestrel?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6WTE0HZF58SY8864D6TKG", + "id": "01M1X6X2743EGXA7PNE2C8P9KZ", + "kind": "memory", + "score": 0.6894522309303284, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 361.3078, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 647028736, + "peak_working_set_bytes": 685027328 + } + ], + "id": "kestrel-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 0.7222222222222222, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.833 (n=6) positive-n=12 negative-n=6 (18 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 2.9444444444444446, + 4 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 0.7361111111111112, + "n": 4, + "ci95": 0.027222222222222234 + } + }, + "overall_index": 0.7361111111111112, + "scenario_weighted_index": 0.7361111111111112 +} diff --git a/docs/audits/2026-09-07-answerability/results/missing-fact-development/1-candidate.stderr.log b/docs/audits/2026-09-07-answerability/results/missing-fact-development/1-candidate.stderr.log new file mode 100644 index 0000000..b62024f --- /dev/null +++ b/docs/audits/2026-09-07-answerability/results/missing-fact-development/1-candidate.stderr.log @@ -0,0 +1,10 @@ +brainbench: 4 scenario(s) to run + [1/4] copper-unseen-project | dim=retrieval tier=hard ... + -> score=1.00 | positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.000 (n=6) positive-n=12 negative-n=6 (18 queries) + [2/4] willow-unseen-project | dim=retrieval tier=hard ... + -> score=1.00 | positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.000 (n=6) positive-n=12 negative-n=6 (18 queries) + [3/4] marble-unseen-project | dim=retrieval tier=hard ... + -> score=1.00 | positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.000 (n=6) positive-n=12 negative-n=6 (18 queries) + [4/4] kestrel-unseen-project | dim=retrieval tier=hard ... + -> score=1.00 | positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.000 (n=6) positive-n=12 negative-n=6 (18 queries) +kbench brainbench: report saved -> E:\tmp\kimetsu-brain-hardening\bench\local\runs\brainbench\2026-09-07T05-55-02.440171Z.json diff --git a/docs/audits/2026-09-07-answerability/results/missing-fact-development/1-candidate.stdout.log b/docs/audits/2026-09-07-answerability/results/missing-fact-development/1-candidate.stdout.log new file mode 100644 index 0000000..2cee319 --- /dev/null +++ b/docs/audits/2026-09-07-answerability/results/missing-fact-development/1-candidate.stdout.log @@ -0,0 +1,1921 @@ +{ + "generated_at": "2026-09-07T05:55:02.439744Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-retrieval\\validation-frozen.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "Which TCP port should I connect to for Copper staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6X3NNJ5VZ5X50FH82MK7M", + "id": "01M1X6X5M0SWP7Y5Q7E4ZP51G2", + "kind": "memory", + "score": 0.9998394250869752, + "summary": "project:fact - Copper staging HTTP listener binds TCP port 6319. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1883.5029, + "first_query": true, + "server_startup_ms": 73.5735, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 631738368, + "peak_working_set_bytes": 684945408 + }, + { + "query": "Where should Copper diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6X3P5Y954RYW773ATN8RB", + "id": "01M1X6X60827RCN3Q5T19MM99S", + "kind": "memory", + "score": 0.9978247880935668, + "summary": "project:fact - Copper diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X6X3QK8NSW5AR6PRD0Z5D6", + "id": "01M1X6X608N2YKP8FJNTGJKXN2", + "kind": "memory", + "score": 0.6390834450721741, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 378.62050000000005, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 754, + "mcp_result_bytes": 853, + "wire_bytes": 888, + "reported_used_tokens": 853, + "working_set_bytes": 632242176, + "peak_working_set_bytes": 684945408 + }, + { + "query": "Which command rolls back Copper to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6X3PG4F88HQ1E7VPTKX60", + "id": "01M1X6X6C1XPGAE1083W5BR50F", + "kind": "memory", + "score": 0.9998852014541626, + "summary": "project:fact - To roll back Copper to the previous release, run `copperctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 374.528, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 634347520, + "peak_working_set_bytes": 684945408 + }, + { + "query": "Which region hosts Copper production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6X3PWK0TRN2V1X9J2JF9G", + "id": "01M1X6X6Q64SKXVXT9FYYX0Z71", + "kind": "memory", + "score": 0.9999468326568604, + "summary": "project:fact - Copper production runs in region eu-north-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 350.43809999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 609, + "reported_used_tokens": 574, + "working_set_bytes": 634515456, + "peak_working_set_bytes": 684945408 + }, + { + "query": "At what UTC time do daily Copper database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6X3Q8R53H7YCH0RQDV84F", + "id": "01M1X6X72CSY9YWWYNJVR4ZBNP", + "kind": "memory", + "score": 0.9999791383743286, + "summary": "project:fact - Copper daily database backups start at 02:40 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 360.62129999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 587, + "reported_used_tokens": 552, + "working_set_bytes": 634900480, + "peak_working_set_bytes": 684945408 + }, + { + "query": "Which database and journal mode does Copper use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6X3QK8NSW5AR6PRD0Z5D6", + "id": "01M1X6X7DS0GEJ916AV0QPJPSC", + "kind": "memory", + "score": 0.9999594688415528, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 370.178, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 635052032, + "peak_working_set_bytes": 684945408 + }, + { + "query": "¿A qué puerto TCP debo conectarme para staging de Copper?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6X3NNJ5VZ5X50FH82MK7M", + "id": "01M1X6X7SEDR7RMZKZSHYGXZ42", + "kind": "memory", + "score": 0.9998682737350464, + "summary": "project:fact - Copper staging HTTP listener binds TCP port 6319. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 370.764, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 637345792, + "peak_working_set_bytes": 684945408 + }, + { + "query": "¿Dónde deben escribirse los mensajes de diagnóstico de Copper?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6X3P5Y954RYW773ATN8RB", + "id": "01M1X6X84PKT9ZJR55REWY2REQ", + "kind": "memory", + "score": 0.9995033740997314, + "summary": "project:fact - Copper diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 360.773, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 488, + "mcp_result_bytes": 569, + "wire_bytes": 604, + "reported_used_tokens": 569, + "working_set_bytes": 637710336, + "peak_working_set_bytes": 684945408 + }, + { + "query": "¿Qué comando revierte Copper a la versión anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6X3PG4F88HQ1E7VPTKX60", + "id": "01M1X6X8G033SG4R9CE13A9KQF", + "kind": "memory", + "score": 0.9887914657592772, + "summary": "project:fact - To roll back Copper to the previous release, run `copperctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 357.7917, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 616, + "reported_used_tokens": 580, + "working_set_bytes": 637755392, + "peak_working_set_bytes": 684945408 + }, + { + "query": "¿En qué región está desplegado Copper en producción?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6X3PWK0TRN2V1X9J2JF9G", + "id": "01M1X6X8V1E11G1DZRHVNYR43F", + "kind": "memory", + "score": 0.9999468326568604, + "summary": "project:fact - Copper production runs in region eu-north-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 353.02549999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 637890560, + "peak_working_set_bytes": 684945408 + }, + { + "query": "¿A qué hora UTC empiezan las copias diarias de la base de datos de Copper?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6X3Q8R53H7YCH0RQDV84F", + "id": "01M1X6X96GN5V1TCN5WBJP40TB", + "kind": "memory", + "score": 0.9999747276306152, + "summary": "project:fact - Copper daily database backups start at 02:40 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 372.13960000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 638185472, + "peak_working_set_bytes": 684945408 + }, + { + "query": "¿Qué base de datos y modo de registro usa Copper para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6X3QK8NSW5AR6PRD0Z5D6", + "id": "01M1X6X9HQQ85WM4MPA3M888JT", + "kind": "memory", + "score": 0.9983224272727966, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 357.43789999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 638291968, + "peak_working_set_bytes": 684945408 + }, + { + "query": "What authentication password is configured for the Copper staging listener?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 359.9212, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 638402560, + "peak_working_set_bytes": 684945408 + }, + { + "query": "What encryption key protects the Copper database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 352.8064, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644169728, + "peak_working_set_bytes": 684945408 + }, + { + "query": "How many production replicas run in the Copper deployment region?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 373.1592, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644177920, + "peak_working_set_bytes": 684945408 + }, + { + "query": "¿Qué contraseña exige el servidor de staging de Copper?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 379.70939999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644222976, + "peak_working_set_bytes": 684945408 + }, + { + "query": "¿Cuántos días se conservan las copias de seguridad de Copper?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 360.5999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644575232, + "peak_working_set_bytes": 684945408 + }, + { + "query": "¿Qué versión de SQLite requiere Copper?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 352.8018, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644595712, + "peak_working_set_bytes": 684945408 + } + ], + "id": "copper-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.000 (n=6) positive-n=12 negative-n=6 (18 queries)" + }, + { + "observations": [ + { + "query": "Which TCP port should I connect to for Willow staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XD2VGTMJTPW3X02ZCKY7", + "id": "01M1X6XEX9ZETSNJASMCSD3JQT", + "kind": "memory", + "score": 0.9999420642852784, + "summary": "project:fact - Willow staging HTTP listener binds TCP port 7421. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1797.8110000000001, + "first_query": true, + "server_startup_ms": 78.7194, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 635105280, + "peak_working_set_bytes": 684879872 + }, + { + "query": "Where should Willow diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XD3CG3X6TZSH5Q8S53K1", + "id": "01M1X6XF8MND0EZG32TP1YPY1K", + "kind": "memory", + "score": 0.9971635937690736, + "summary": "project:fact - Willow diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X6XD4X69X1VHSS2AKWX55H", + "id": "01M1X6XF8MZ78NWGNPE17S30J8", + "kind": "memory", + "score": 0.8971153497695923, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 352.9713, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 754, + "mcp_result_bytes": 853, + "wire_bytes": 888, + "reported_used_tokens": 853, + "working_set_bytes": 635588608, + "peak_working_set_bytes": 684879872 + }, + { + "query": "Which command rolls back Willow to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XD3SG64YZRJ5269S9EAD", + "id": "01M1X6XFKTVVJ82B5AD7BMG03V", + "kind": "memory", + "score": 0.9998542070388794, + "summary": "project:fact - To roll back Willow to the previous release, run `willowctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 387.6426, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 637579264, + "peak_working_set_bytes": 684879872 + }, + { + "query": "Which region hosts Willow production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XD4530FQAWBFEEKWHR9D", + "id": "01M1X6XGSM6QN70SYZAYPZHPCA", + "kind": "memory", + "score": 0.9999713897705078, + "summary": "project:fact - Willow production runs in region us-west-2. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1174.5757999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 637612032, + "peak_working_set_bytes": 684879872 + }, + { + "query": "At what UTC time do daily Willow database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XD4HWE7BESKQ4CF5BP1T", + "id": "01M1X6XH4J9PFDKCF6ARQCANBN", + "kind": "memory", + "score": 0.9999792575836182, + "summary": "project:fact - Willow daily database backups start at 04:15 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 582.992, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 587, + "reported_used_tokens": 552, + "working_set_bytes": 637833216, + "peak_working_set_bytes": 684879872 + }, + { + "query": "Which database and journal mode does Willow use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XD4X69X1VHSS2AKWX55H", + "id": "01M1X6XHPR2DAAE5ZYZSSAHHZM", + "kind": "memory", + "score": 0.9999637603759766, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 426.6592, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 637853696, + "peak_working_set_bytes": 684879872 + }, + { + "query": "¿A qué puerto TCP debo conectarme para staging de Willow?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XD2VGTMJTPW3X02ZCKY7", + "id": "01M1X6XJ45DQGTJ0FM07K03C6C", + "kind": "memory", + "score": 0.9999486207962036, + "summary": "project:fact - Willow staging HTTP listener binds TCP port 7421. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 509.51239999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 640262144, + "peak_working_set_bytes": 684879872 + }, + { + "query": "¿Dónde deben escribirse los mensajes de diagnóstico de Willow?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XD3CG3X6TZSH5Q8S53K1", + "id": "01M1X6XJZH0GKS9S7CZ8RV0T9K", + "kind": "memory", + "score": 0.9996838569641112, + "summary": "project:fact - Willow diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 799.9312, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 488, + "mcp_result_bytes": 569, + "wire_bytes": 604, + "reported_used_tokens": 569, + "working_set_bytes": 640577536, + "peak_working_set_bytes": 684879872 + }, + { + "query": "¿Qué comando revierte Willow a la versión anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XD3SG64YZRJ5269S9EAD", + "id": "01M1X6XKD24PZR4EJT7FN9RPW2", + "kind": "memory", + "score": 0.98951655626297, + "summary": "project:fact - To roll back Willow to the previous release, run `willowctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.601, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 497, + "mcp_result_bytes": 578, + "wire_bytes": 614, + "reported_used_tokens": 578, + "working_set_bytes": 640663552, + "peak_working_set_bytes": 684879872 + }, + { + "query": "¿En qué región está desplegado Willow en producción?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XD4530FQAWBFEEKWHR9D", + "id": "01M1X6XKR65B2Q0ZKS2BRA01JS", + "kind": "memory", + "score": 0.9999579191207886, + "summary": "project:fact - Willow production runs in region us-west-2. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 511.5941, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 640761856, + "peak_working_set_bytes": 684879872 + }, + { + "query": "¿A qué hora UTC empiezan las copias diarias de la base de datos de Willow?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XD4HWE7BESKQ4CF5BP1T", + "id": "01M1X6XM88A82148ZMAX1DT86P", + "kind": "memory", + "score": 0.99997878074646, + "summary": "project:fact - Willow daily database backups start at 04:15 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 530.131, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 469, + "mcp_result_bytes": 550, + "wire_bytes": 586, + "reported_used_tokens": 550, + "working_set_bytes": 641073152, + "peak_working_set_bytes": 684879872 + }, + { + "query": "¿Qué base de datos y modo de registro usa Willow para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XD4X69X1VHSS2AKWX55H", + "id": "01M1X6XMRZMAFTN3BJZCYRY1YC", + "kind": "memory", + "score": 0.999204695224762, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 466.5879, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 491, + "mcp_result_bytes": 572, + "wire_bytes": 608, + "reported_used_tokens": 572, + "working_set_bytes": 641175552, + "peak_working_set_bytes": 684879872 + }, + { + "query": "What authentication password is configured for the Willow staging listener?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 773.0234, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 641261568, + "peak_working_set_bytes": 684879872 + }, + { + "query": "What encryption key protects the Willow database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 435.5034, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 647110656, + "peak_working_set_bytes": 684879872 + }, + { + "query": "How many production replicas run in the Willow deployment region?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 662.1342, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 647208960, + "peak_working_set_bytes": 684879872 + }, + { + "query": "¿Qué contraseña exige el servidor de staging de Willow?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 356.422, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 647319552, + "peak_working_set_bytes": 684879872 + }, + { + "query": "¿Cuántos días se conservan las copias de seguridad de Willow?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 365.8991, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 647499776, + "peak_working_set_bytes": 684879872 + }, + { + "query": "¿Qué versión de SQLite requiere Willow?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 597.4815, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 647507968, + "peak_working_set_bytes": 684879872 + } + ], + "id": "willow-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.000 (n=6) positive-n=12 negative-n=6 (18 queries)" + }, + { + "observations": [ + { + "query": "Which TCP port should I connect to for Marble staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XTQNED9YAVHNM15TKWEW", + "id": "01M1X6XWJHKXKEWEZVQVEJ5PHV", + "kind": "memory", + "score": 0.9996737241744996, + "summary": "project:fact - Marble staging HTTP listener binds TCP port 8533. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1791.9889, + "first_query": true, + "server_startup_ms": 73.4707, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 635297792, + "peak_working_set_bytes": 684945408 + }, + { + "query": "Where should Marble diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XTR72YBJ4PB7PQZ3J8HR", + "id": "01M1X6XWXTRNT2ASC0BRV82PYK", + "kind": "memory", + "score": 0.9971815347671508, + "summary": "project:fact - Marble diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X6XTSPW7WY7P8JJF27R42C", + "id": "01M1X6XWXT0JS2V5HCYTA41S47", + "kind": "memory", + "score": 0.6012999415397644, + "summary": "project:fact - Marble stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 349.1746, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 754, + "mcp_result_bytes": 853, + "wire_bytes": 888, + "reported_used_tokens": 853, + "working_set_bytes": 635752448, + "peak_working_set_bytes": 684945408 + }, + { + "query": "Which command rolls back Marble to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XTRK6X63J00C620VH1FQ", + "id": "01M1X6XX8RM3GKS2H1C1DM7E36", + "kind": "memory", + "score": 0.9997585415840148, + "summary": "project:fact - To roll back Marble to the previous release, run `marblectl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 357.39500000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 637992960, + "peak_working_set_bytes": 684945408 + }, + { + "query": "Which region hosts Marble production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XTRYY3705RAMVH6MM50M", + "id": "01M1X6XXKWZ3S1RZTK4XM0YY3A", + "kind": "memory", + "score": 0.999954104423523, + "summary": "project:fact - Marble production runs in region ap-south-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 349.84409999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 638017536, + "peak_working_set_bytes": 684945408 + }, + { + "query": "At what UTC time do daily Marble database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XTSA31QBTC5584FRZ438", + "id": "01M1X6XXZ1GZYTSR28BS75CQ45", + "kind": "memory", + "score": 0.999979853630066, + "summary": "project:fact - Marble daily database backups start at 01:25 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.9635, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 587, + "reported_used_tokens": 552, + "working_set_bytes": 638238720, + "peak_working_set_bytes": 684945408 + }, + { + "query": "Which database and journal mode does Marble use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XTSPW7WY7P8JJF27R42C", + "id": "01M1X6XYA18PD4Q2CZ4Y01NCVP", + "kind": "memory", + "score": 0.9999568462371826, + "summary": "project:fact - Marble stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 352.7874, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 638349312, + "peak_working_set_bytes": 684945408 + }, + { + "query": "¿A qué puerto TCP debo conectarme para staging de Marble?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XTQNED9YAVHNM15TKWEW", + "id": "01M1X6XYNA5010MGK5164CHY1M", + "kind": "memory", + "score": 0.9998871088027954, + "summary": "project:fact - Marble staging HTTP listener binds TCP port 8533. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 364.5638, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 640847872, + "peak_working_set_bytes": 684945408 + }, + { + "query": "¿Dónde deben escribirse los mensajes de diagnóstico de Marble?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XTR72YBJ4PB7PQZ3J8HR", + "id": "01M1X6XZ0QC8B80CXHKD841EGT", + "kind": "memory", + "score": 0.9992142915725708, + "summary": "project:fact - Marble diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 367.2181, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 488, + "mcp_result_bytes": 569, + "wire_bytes": 604, + "reported_used_tokens": 569, + "working_set_bytes": 641118208, + "peak_working_set_bytes": 684945408 + }, + { + "query": "¿Qué comando revierte Marble a la versión anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XTRK6X63J00C620VH1FQ", + "id": "01M1X6XZCS1MD6ZGJM2JEPVH3J", + "kind": "memory", + "score": 0.976457178592682, + "summary": "project:fact - To roll back Marble to the previous release, run `marblectl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 380.04949999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 616, + "reported_used_tokens": 580, + "working_set_bytes": 641257472, + "peak_working_set_bytes": 684945408 + }, + { + "query": "¿En qué región está desplegado Marble en producción?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XTRYY3705RAMVH6MM50M", + "id": "01M1X6XZQT3HZN037XSMJRZNJW", + "kind": "memory", + "score": 0.9999542236328124, + "summary": "project:fact - Marble production runs in region ap-south-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 354.76829999999995, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 641486848, + "peak_working_set_bytes": 684945408 + }, + { + "query": "¿A qué hora UTC empiezan las copias diarias de la base de datos de Marble?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XTSA31QBTC5584FRZ438", + "id": "01M1X6Y02YSN6WQZ9S5NC94W5N", + "kind": "memory", + "score": 0.9999665021896362, + "summary": "project:fact - Marble daily database backups start at 01:25 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 359.5495, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 641867776, + "peak_working_set_bytes": 684945408 + }, + { + "query": "¿Qué base de datos y modo de registro usa Marble para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6XTSPW7WY7P8JJF27R42C", + "id": "01M1X6Y0E9VBNAXNC0RWH5KTH8", + "kind": "memory", + "score": 0.9953057169914246, + "summary": "project:fact - Marble stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 363.5991, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 641941504, + "peak_working_set_bytes": 684945408 + }, + { + "query": "What authentication password is configured for the Marble staging listener?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 362.6478, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 642183168, + "peak_working_set_bytes": 684945408 + }, + { + "query": "What encryption key protects the Marble database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 368.088, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 648101888, + "peak_working_set_bytes": 684945408 + }, + { + "query": "How many production replicas run in the Marble deployment region?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 368.7263, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 648167424, + "peak_working_set_bytes": 684945408 + }, + { + "query": "¿Qué contraseña exige el servidor de staging de Marble?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 364.96079999999995, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 648192000, + "peak_working_set_bytes": 684945408 + }, + { + "query": "¿Cuántos días se conservan las copias de seguridad de Marble?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 364.0675, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 648536064, + "peak_working_set_bytes": 684945408 + }, + { + "query": "¿Qué versión de SQLite requiere Marble?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 388.1593, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 648646656, + "peak_working_set_bytes": 684945408 + } + ], + "id": "marble-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.000 (n=6) positive-n=12 negative-n=6 (18 queries)" + }, + { + "observations": [ + { + "query": "Which TCP port should I connect to for Kestrel staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Y3ZKK4NVNBVQ4RVC0Z18", + "id": "01M1X6Y5TT3ZESQ3BF6YDCSFJ3", + "kind": "memory", + "score": 0.9999393224716188, + "summary": "project:fact - Kestrel staging HTTP listener binds TCP port 9647. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1786.8591000000001, + "first_query": true, + "server_startup_ms": 74.20309999999999, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 638173184, + "peak_working_set_bytes": 684789760 + }, + { + "query": "Where should Kestrel diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Y406VP0GS5SQS3J2CYB0", + "id": "01M1X6Y667NV5BY1XV28DF2YPM", + "kind": "memory", + "score": 0.9987480640411376, + "summary": "project:fact - Kestrel diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X6Y41MZ7BSF841D5WF7EK0", + "id": "01M1X6Y6675C92GGJ4JY76340W", + "kind": "memory", + "score": 0.9422296285629272, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 353.5111, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 756, + "mcp_result_bytes": 855, + "wire_bytes": 890, + "reported_used_tokens": 855, + "working_set_bytes": 640311296, + "peak_working_set_bytes": 684789760 + }, + { + "query": "Which command rolls back Kestrel to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Y40HQPXPBQAKK7X65NYG", + "id": "01M1X6Y6H5Z534E0DZ0325B3GG", + "kind": "memory", + "score": 0.9997856020927428, + "summary": "project:fact - To roll back Kestrel to the previous release, run `kestrelctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 359.4194, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 501, + "mcp_result_bytes": 582, + "wire_bytes": 617, + "reported_used_tokens": 582, + "working_set_bytes": 642461696, + "peak_working_set_bytes": 684789760 + }, + { + "query": "Which region hosts Kestrel production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Y40XGDYXN1JE1VCWJC40", + "id": "01M1X6Y6WGFGB4YS34D00N4DTC", + "kind": "memory", + "score": 0.9999779462814332, + "summary": "project:fact - Kestrel production runs in region eu-west-3. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 357.8371, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 609, + "reported_used_tokens": 574, + "working_set_bytes": 642621440, + "peak_working_set_bytes": 684789760 + }, + { + "query": "At what UTC time do daily Kestrel database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Y419AG550M415PJQKWGG", + "id": "01M1X6Y77Q7CDZV4XWATS1SV8K", + "kind": "memory", + "score": 0.999980330467224, + "summary": "project:fact - Kestrel daily database backups start at 03:50 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 360.0407, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 472, + "mcp_result_bytes": 553, + "wire_bytes": 588, + "reported_used_tokens": 553, + "working_set_bytes": 642875392, + "peak_working_set_bytes": 684789760 + }, + { + "query": "Which database and journal mode does Kestrel use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Y41MZ7BSF841D5WF7EK0", + "id": "01M1X6Y7KMTK5T8RH3G7BQ2F19", + "kind": "memory", + "score": 0.999975323677063, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 380.5959, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 643137536, + "peak_working_set_bytes": 684789760 + }, + { + "query": "¿A qué puerto TCP debo conectarme para staging de Kestrel?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Y3ZKK4NVNBVQ4RVC0Z18", + "id": "01M1X6Y7YQP9NE0RZD0B8F19S0", + "kind": "memory", + "score": 0.9999423027038574, + "summary": "project:fact - Kestrel staging HTTP listener binds TCP port 9647. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 360.7013, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 645648384, + "peak_working_set_bytes": 684789760 + }, + { + "query": "¿Dónde deben escribirse los mensajes de diagnóstico de Kestrel?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Y406VP0GS5SQS3J2CYB0", + "id": "01M1X6Y8A5V1N70N6BP4X5DD7A", + "kind": "memory", + "score": 0.9997678399086, + "summary": "project:fact - Kestrel diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 374.92350000000005, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 489, + "mcp_result_bytes": 570, + "wire_bytes": 605, + "reported_used_tokens": 570, + "working_set_bytes": 645791744, + "peak_working_set_bytes": 684789760 + }, + { + "query": "¿Qué comando revierte Kestrel a la versión anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Y40HQPXPBQAKK7X65NYG", + "id": "01M1X6Y8NQA1P7CS8J7C2QXXM3", + "kind": "memory", + "score": 0.9766082763671876, + "summary": "project:fact - To roll back Kestrel to the previous release, run `kestrelctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.6341, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 501, + "mcp_result_bytes": 582, + "wire_bytes": 618, + "reported_used_tokens": 582, + "working_set_bytes": 646041600, + "peak_working_set_bytes": 684789760 + }, + { + "query": "¿En qué región está desplegado Kestrel en producción?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Y40XGDYXN1JE1VCWJC40", + "id": "01M1X6Y90W8F50YD8ST6KZXTKW", + "kind": "memory", + "score": 0.999974250793457, + "summary": "project:fact - Kestrel production runs in region eu-west-3. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 355.6515, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 646066176, + "peak_working_set_bytes": 684789760 + }, + { + "query": "¿A qué hora UTC empiezan las copias diarias de la base de datos de Kestrel?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Y419AG550M415PJQKWGG", + "id": "01M1X6Y9C89TY5HEXM9ZBZNRE9", + "kind": "memory", + "score": 0.9999799728393556, + "summary": "project:fact - Kestrel daily database backups start at 03:50 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 369.5423, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 472, + "mcp_result_bytes": 553, + "wire_bytes": 589, + "reported_used_tokens": 553, + "working_set_bytes": 646369280, + "peak_working_set_bytes": 684789760 + }, + { + "query": "¿Qué base de datos y modo de registro usa Kestrel para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Y41MZ7BSF841D5WF7EK0", + "id": "01M1X6Y9QME8S7J76WY0B085PA", + "kind": "memory", + "score": 0.9993937015533448, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 394.824, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 646397952, + "peak_working_set_bytes": 684789760 + }, + { + "query": "What authentication password is configured for the Kestrel staging listener?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 399.1674, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 646524928, + "peak_working_set_bytes": 684789760 + }, + { + "query": "What encryption key protects the Kestrel database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 394.31899999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 650592256, + "peak_working_set_bytes": 684789760 + }, + { + "query": "How many production replicas run in the Kestrel deployment region?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 393.2575, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 650661888, + "peak_working_set_bytes": 684789760 + }, + { + "query": "¿Qué contraseña exige el servidor de staging de Kestrel?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 356.7486, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 650739712, + "peak_working_set_bytes": 684789760 + }, + { + "query": "¿Cuántos días se conservan las copias de seguridad de Kestrel?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 358.8879, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 650870784, + "peak_working_set_bytes": 684789760 + }, + { + "query": "¿Qué versión de SQLite requiere Kestrel?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 355.9336, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 650932224, + "peak_working_set_bytes": 684789760 + } + ], + "id": "kestrel-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.000 (n=6) positive-n=12 negative-n=6 (18 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 4.0, + 4 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 1.0, + "n": 4, + "ci95": 0.0 + } + }, + "overall_index": 1.0, + "scenario_weighted_index": 1.0 +} diff --git a/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/1-baseline.stderr.log b/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/1-baseline.stderr.log new file mode 100644 index 0000000..2330646 --- /dev/null +++ b/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/1-baseline.stderr.log @@ -0,0 +1,10 @@ +brainbench: 4 scenario(s) to run + [1/4] copper-unseen-project | dim=retrieval tier=hard ... + -> score=0.72 | positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.833 (n=6) positive-n=12 negative-n=6 (18 queries) + [2/4] willow-unseen-project | dim=retrieval tier=hard ... + -> score=0.72 | positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.833 (n=6) positive-n=12 negative-n=6 (18 queries) + [3/4] marble-unseen-project | dim=retrieval tier=hard ... + -> score=0.78 | positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.667 (n=6) positive-n=12 negative-n=6 (18 queries) + [4/4] kestrel-unseen-project | dim=retrieval tier=hard ... + -> score=0.72 | positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.833 (n=6) positive-n=12 negative-n=6 (18 queries) +kbench brainbench: report saved -> E:\tmp\kimetsu-brain-hardening\bench\local\runs\brainbench\2026-09-07T05-57-27.2173892Z.json diff --git a/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/1-baseline.stdout.log b/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/1-baseline.stdout.log new file mode 100644 index 0000000..dac713d --- /dev/null +++ b/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/1-baseline.stdout.log @@ -0,0 +1,2130 @@ +{ + "generated_at": "2026-09-07T05:57:27.2168915Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-retrieval\\validation-frozen.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "Which TCP port should I connect to for Copper staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71MNDYNM9PJ3X8SQS8CHB", + "id": "01M1X71PG5N0TEAT9HJRFXYR7N", + "kind": "memory", + "score": 0.9998394250869752, + "summary": "project:fact - Copper staging HTTP listener binds TCP port 6319. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1785.0068999999999, + "first_query": true, + "server_startup_ms": 73.7585, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 631394304, + "peak_working_set_bytes": 684806144 + }, + { + "query": "Where should Copper diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71MNY1V4CPMPAVNNB158V", + "id": "01M1X71PVBP7W3BT8661W6GPTX", + "kind": "memory", + "score": 0.9978247880935668, + "summary": "project:fact - Copper diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X71MQCE02FBBWMMZ0WYF1H", + "id": "01M1X71PVBP7SMFNYBGYGHNB23", + "kind": "memory", + "score": 0.6390834450721741, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 353.1834, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 754, + "mcp_result_bytes": 853, + "wire_bytes": 888, + "reported_used_tokens": 853, + "working_set_bytes": 631873536, + "peak_working_set_bytes": 684806144 + }, + { + "query": "Which command rolls back Copper to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71MP9FT994KQSQYJSTPXR", + "id": "01M1X71Q6H1BDWGJYXGEA71GTM", + "kind": "memory", + "score": 0.9998852014541626, + "summary": "project:fact - To roll back Copper to the previous release, run `copperctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 364.6724, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 632033280, + "peak_working_set_bytes": 684806144 + }, + { + "query": "Which region hosts Copper production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71MPNZ50242NN0MEQFN0R", + "id": "01M1X71QHQNFAJD51D8GNYFKQH", + "kind": "memory", + "score": 0.9999468326568604, + "summary": "project:fact - Copper production runs in region eu-north-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 353.53240000000005, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 609, + "reported_used_tokens": 574, + "working_set_bytes": 632131584, + "peak_working_set_bytes": 684806144 + }, + { + "query": "At what UTC time do daily Copper database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71MQ1XWWDD6X724YGF2DY", + "id": "01M1X71QWT2ARWZJSQ6YMNK13K", + "kind": "memory", + "score": 0.9999791383743286, + "summary": "project:fact - Copper daily database backups start at 02:40 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 351.0684, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 587, + "reported_used_tokens": 552, + "working_set_bytes": 632295424, + "peak_working_set_bytes": 684806144 + }, + { + "query": "Which database and journal mode does Copper use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71MQCE02FBBWMMZ0WYF1H", + "id": "01M1X71R7V2BXHZYSHPQ5DWWB2", + "kind": "memory", + "score": 0.9999594688415528, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 357.2317, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 632442880, + "peak_working_set_bytes": 684806144 + }, + { + "query": "¿A qué puerto TCP debo conectarme para staging de Copper?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71MNDYNM9PJ3X8SQS8CHB", + "id": "01M1X71RK6SYKK12Q7JD9FHJ3W", + "kind": "memory", + "score": 0.9998682737350464, + "summary": "project:fact - Copper staging HTTP listener binds TCP port 6319. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 366.8775, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 634761216, + "peak_working_set_bytes": 684806144 + }, + { + "query": "¿Dónde deben escribirse los mensajes de diagnóstico de Copper?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71MNY1V4CPMPAVNNB158V", + "id": "01M1X71RYQQNP5261B5JHET3H6", + "kind": "memory", + "score": 0.9995033740997314, + "summary": "project:fact - Copper diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 373.0024, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 488, + "mcp_result_bytes": 569, + "wire_bytes": 604, + "reported_used_tokens": 569, + "working_set_bytes": 635011072, + "peak_working_set_bytes": 684806144 + }, + { + "query": "¿Qué comando revierte Copper a la versión anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71MP9FT994KQSQYJSTPXR", + "id": "01M1X71SAZ3XZJCSQEHE5KFZ3S", + "kind": "memory", + "score": 0.9887914657592772, + "summary": "project:fact - To roll back Copper to the previous release, run `copperctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 381.6808, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 616, + "reported_used_tokens": 580, + "working_set_bytes": 635027456, + "peak_working_set_bytes": 684806144 + }, + { + "query": "¿En qué región está desplegado Copper en producción?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71MPNZ50242NN0MEQFN0R", + "id": "01M1X71SP7CR8J0SWYN1FDBYQR", + "kind": "memory", + "score": 0.9999468326568604, + "summary": "project:fact - Copper production runs in region eu-north-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 360.3695, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 635088896, + "peak_working_set_bytes": 684806144 + }, + { + "query": "¿A qué hora UTC empiezan las copias diarias de la base de datos de Copper?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71MQ1XWWDD6X724YGF2DY", + "id": "01M1X71T1EC2BAXM19SPBPQ8C2", + "kind": "memory", + "score": 0.9999747276306152, + "summary": "project:fact - Copper daily database backups start at 02:40 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 369.9482, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 635338752, + "peak_working_set_bytes": 684806144 + }, + { + "query": "¿Qué base de datos y modo de registro usa Copper para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71MQCE02FBBWMMZ0WYF1H", + "id": "01M1X71TD65MN5ZQGVG0HQB24T", + "kind": "memory", + "score": 0.9983224272727966, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 365.0951, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 635424768, + "peak_working_set_bytes": 684806144 + }, + { + "query": "What authentication password is configured for the Copper staging listener?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71MNDYNM9PJ3X8SQS8CHB", + "id": "01M1X71TR9ADJDCKH4GN7CF7BJ", + "kind": "memory", + "score": 0.9784963130950928, + "summary": "project:fact - Copper staging HTTP listener binds TCP port 6319. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 357.0822, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 615, + "reported_used_tokens": 579, + "working_set_bytes": 635473920, + "peak_working_set_bytes": 684806144 + }, + { + "query": "What encryption key protects the Copper database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 366.1139, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 641339392, + "peak_working_set_bytes": 684806144 + }, + { + "query": "How many production replicas run in the Copper deployment region?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71MPNZ50242NN0MEQFN0R", + "id": "01M1X71VETF4K10A8Z7035XTP9", + "kind": "memory", + "score": 0.8394170999526978, + "summary": "project:fact - Copper production runs in region eu-north-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 360.72900000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 641462272, + "peak_working_set_bytes": 684806144 + }, + { + "query": "¿Qué contraseña exige el servidor de staging de Copper?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71MNDYNM9PJ3X8SQS8CHB", + "id": "01M1X71VTF4WKW4YFDX7DNCQC2", + "kind": "memory", + "score": 0.898059606552124, + "summary": "project:fact - Copper staging HTTP listener binds TCP port 6319. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 366.1863, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 497, + "mcp_result_bytes": 578, + "wire_bytes": 614, + "reported_used_tokens": 578, + "working_set_bytes": 641474560, + "peak_working_set_bytes": 684806144 + }, + { + "query": "¿Cuántos días se conservan las copias de seguridad de Copper?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71MQ1XWWDD6X724YGF2DY", + "id": "01M1X71W5ZR1V1923YRCZPKMKN", + "kind": "memory", + "score": 0.9550348520278932, + "summary": "project:fact - Copper daily database backups start at 02:40 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 369.2679, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 641544192, + "peak_working_set_bytes": 684806144 + }, + { + "query": "¿Qué versión de SQLite requiere Copper?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71MQCE02FBBWMMZ0WYF1H", + "id": "01M1X71WJ46GBR0FRYE2N8BAMV", + "kind": "memory", + "score": 0.6008884310722351, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 387.2348, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 641548288, + "peak_working_set_bytes": 684806144 + } + ], + "id": "copper-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 0.7222222222222222, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.833 (n=6) positive-n=12 negative-n=6 (18 queries)" + }, + { + "observations": [ + { + "query": "Which TCP port should I connect to for Willow staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71XYTC0PCNBW56YEBN3C7", + "id": "01M1X71ZVN3Y3WNJC6YXQ2VP85", + "kind": "memory", + "score": 0.9999420642852784, + "summary": "project:fact - Willow staging HTTP listener binds TCP port 7421. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1866.3472, + "first_query": true, + "server_startup_ms": 73.1306, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 634626048, + "peak_working_set_bytes": 684875776 + }, + { + "query": "Where should Willow diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71XZCP833VMQ93Q81HFVQ", + "id": "01M1X7207HPNNHECR625HJ0VXY", + "kind": "memory", + "score": 0.9971635937690736, + "summary": "project:fact - Willow diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X71Y0V0NW250F15FYE8BHG", + "id": "01M1X7207HFNS3P6N3F4FX56E6", + "kind": "memory", + "score": 0.8971153497695923, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 361.4796, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 754, + "mcp_result_bytes": 853, + "wire_bytes": 888, + "reported_used_tokens": 853, + "working_set_bytes": 635150336, + "peak_working_set_bytes": 684875776 + }, + { + "query": "Which command rolls back Willow to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71XZRTP3HGW6ZSS65GPNV", + "id": "01M1X720JDP4YCM0N2S5CCZJKV", + "kind": "memory", + "score": 0.9998542070388794, + "summary": "project:fact - To roll back Willow to the previous release, run `willowctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 350.03319999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 635248640, + "peak_working_set_bytes": 684875776 + }, + { + "query": "Which region hosts Willow production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71Y056WNBS97ATYCHCPAT", + "id": "01M1X720XJAV2W9SV8XXZGSYBZ", + "kind": "memory", + "score": 0.9999713897705078, + "summary": "project:fact - Willow production runs in region us-west-2. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 354.5244, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 635297792, + "peak_working_set_bytes": 684875776 + }, + { + "query": "At what UTC time do daily Willow database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71Y0G0RNA2GTQAB4W727W", + "id": "01M1X7218VKJPJE5QAZVTPV9JK", + "kind": "memory", + "score": 0.9999792575836182, + "summary": "project:fact - Willow daily database backups start at 04:15 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 373.4737, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 587, + "reported_used_tokens": 552, + "working_set_bytes": 635449344, + "peak_working_set_bytes": 684875776 + }, + { + "query": "Which database and journal mode does Willow use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71Y0V0NW250F15FYE8BHG", + "id": "01M1X721MWSFS3GF5WVJEGATK6", + "kind": "memory", + "score": 0.9999637603759766, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 374.6258, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 635490304, + "peak_working_set_bytes": 684875776 + }, + { + "query": "¿A qué puerto TCP debo conectarme para staging de Willow?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71XYTC0PCNBW56YEBN3C7", + "id": "01M1X72203D14A1EREZ0AQGFY6", + "kind": "memory", + "score": 0.9999486207962036, + "summary": "project:fact - Willow staging HTTP listener binds TCP port 7421. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 365.4285, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 637943808, + "peak_working_set_bytes": 684875776 + }, + { + "query": "¿Dónde deben escribirse los mensajes de diagnóstico de Willow?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71XZCP833VMQ93Q81HFVQ", + "id": "01M1X722BBG13MB6G9BK3D8777", + "kind": "memory", + "score": 0.9996838569641112, + "summary": "project:fact - Willow diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.4494, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 488, + "mcp_result_bytes": 569, + "wire_bytes": 604, + "reported_used_tokens": 569, + "working_set_bytes": 638386176, + "peak_working_set_bytes": 684875776 + }, + { + "query": "¿Qué comando revierte Willow a la versión anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71XZRTP3HGW6ZSS65GPNV", + "id": "01M1X722PFHZ2JX9PZ77MY4ZRP", + "kind": "memory", + "score": 0.98951655626297, + "summary": "project:fact - To roll back Willow to the previous release, run `willowctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.20550000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 497, + "mcp_result_bytes": 578, + "wire_bytes": 614, + "reported_used_tokens": 578, + "working_set_bytes": 638496768, + "peak_working_set_bytes": 684875776 + }, + { + "query": "¿En qué región está desplegado Willow en producción?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71Y056WNBS97ATYCHCPAT", + "id": "01M1X7231M4V1F4WEJ2G6HEZGJ", + "kind": "memory", + "score": 0.9999579191207886, + "summary": "project:fact - Willow production runs in region us-west-2. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 359.7043, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 638529536, + "peak_working_set_bytes": 684875776 + }, + { + "query": "¿A qué hora UTC empiezan las copias diarias de la base de datos de Willow?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71Y0G0RNA2GTQAB4W727W", + "id": "01M1X723CYHRGEEZRQKJMSS2X3", + "kind": "memory", + "score": 0.99997878074646, + "summary": "project:fact - Willow daily database backups start at 04:15 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 364.096, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 469, + "mcp_result_bytes": 550, + "wire_bytes": 586, + "reported_used_tokens": 550, + "working_set_bytes": 638775296, + "peak_working_set_bytes": 684875776 + }, + { + "query": "¿Qué base de datos y modo de registro usa Willow para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71Y0V0NW250F15FYE8BHG", + "id": "01M1X723R98F1WXZ4JFR6PT42B", + "kind": "memory", + "score": 0.999204695224762, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 360.98789999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 491, + "mcp_result_bytes": 572, + "wire_bytes": 608, + "reported_used_tokens": 572, + "working_set_bytes": 638816256, + "peak_working_set_bytes": 684875776 + }, + { + "query": "What authentication password is configured for the Willow staging listener?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71XYTC0PCNBW56YEBN3C7", + "id": "01M1X7243PBDSNRBFYEE6HRJYE", + "kind": "memory", + "score": 0.9932246804237366, + "summary": "project:fact - Willow staging HTTP listener binds TCP port 7421. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 361.0675, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 615, + "reported_used_tokens": 579, + "working_set_bytes": 638885888, + "peak_working_set_bytes": 684875776 + }, + { + "query": "What encryption key protects the Willow database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 363.61339999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644792320, + "peak_working_set_bytes": 684875776 + }, + { + "query": "How many production replicas run in the Willow deployment region?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71Y056WNBS97ATYCHCPAT", + "id": "01M1X724V0K8WVX8KG8BZHQ9S6", + "kind": "memory", + "score": 0.9349143505096436, + "summary": "project:fact - Willow production runs in region us-west-2. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 387.5478, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 644861952, + "peak_working_set_bytes": 684875776 + }, + { + "query": "¿Qué contraseña exige el servidor de staging de Willow?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71XYTC0PCNBW56YEBN3C7", + "id": "01M1X72568T2CXRDA6B2T9JTS5", + "kind": "memory", + "score": 0.9681325554847716, + "summary": "project:fact - Willow staging HTTP listener binds TCP port 7421. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 353.5084, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 615, + "reported_used_tokens": 579, + "working_set_bytes": 644902912, + "peak_working_set_bytes": 684875776 + }, + { + "query": "¿Cuántos días se conservan las copias de seguridad de Willow?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71Y0G0RNA2GTQAB4W727W", + "id": "01M1X725HBBCDFYW38N5MQGTZB", + "kind": "memory", + "score": 0.9746375679969788, + "summary": "project:fact - Willow daily database backups start at 04:15 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 356.54019999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 644939776, + "peak_working_set_bytes": 684875776 + }, + { + "query": "¿Qué versión de SQLite requiere Willow?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X71Y0V0NW250F15FYE8BHG", + "id": "01M1X725WE1SX6RPB9NMDCWFMF", + "kind": "memory", + "score": 0.7611488103866577, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 352.27950000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 644993024, + "peak_working_set_bytes": 684875776 + } + ], + "id": "willow-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 0.7222222222222222, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.833 (n=6) positive-n=12 negative-n=6 (18 queries)" + }, + { + "observations": [ + { + "query": "Which TCP port should I connect to for Marble staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X727GMVC50GY8DKEFRE4M5", + "id": "01M1X729BDRG9DJ6ECVPD966YZ", + "kind": "memory", + "score": 0.9996737241744996, + "summary": "project:fact - Marble staging HTTP listener binds TCP port 8533. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1773.0963, + "first_query": true, + "server_startup_ms": 72.9162, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 633303040, + "peak_working_set_bytes": 685076480 + }, + { + "query": "Where should Marble diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X727H33NDEE3FB9WX65292", + "id": "01M1X729PSCDW4PSKQ5Y4KXT03", + "kind": "memory", + "score": 0.9971815347671508, + "summary": "project:fact - Marble diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X727JJK6XWTG4J8QMGY1ZP", + "id": "01M1X729PSDH23287N122YW9WG", + "kind": "memory", + "score": 0.6012999415397644, + "summary": "project:fact - Marble stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 363.1606, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 754, + "mcp_result_bytes": 853, + "wire_bytes": 888, + "reported_used_tokens": 853, + "working_set_bytes": 633843712, + "peak_working_set_bytes": 685076480 + }, + { + "query": "Which command rolls back Marble to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X727HE87E87FQHNCMYMXE1", + "id": "01M1X72A2GPHPY19S3HAZ15RM2", + "kind": "memory", + "score": 0.9997585415840148, + "summary": "project:fact - To roll back Marble to the previous release, run `marblectl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 370.149, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 633978880, + "peak_working_set_bytes": 685076480 + }, + { + "query": "Which region hosts Marble production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X727HTMRN9TYZZW1TD16HZ", + "id": "01M1X72ADGAQK37VXY2MQA08JC", + "kind": "memory", + "score": 0.999954104423523, + "summary": "project:fact - Marble production runs in region ap-south-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 347.05609999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 634007552, + "peak_working_set_bytes": 685076480 + }, + { + "query": "At what UTC time do daily Marble database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X727J6FTK6KWNT7T2YMVKY", + "id": "01M1X72ARB1VBXNTTQV2ZPBTMQ", + "kind": "memory", + "score": 0.999979853630066, + "summary": "project:fact - Marble daily database backups start at 01:25 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 358.711, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 587, + "reported_used_tokens": 552, + "working_set_bytes": 634150912, + "peak_working_set_bytes": 685076480 + }, + { + "query": "Which database and journal mode does Marble use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X727JJK6XWTG4J8QMGY1ZP", + "id": "01M1X72B3J4NNDH2JDTS4423T0", + "kind": "memory", + "score": 0.9999568462371826, + "summary": "project:fact - Marble stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 353.8916, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 634306560, + "peak_working_set_bytes": 685076480 + }, + { + "query": "¿A qué puerto TCP debo conectarme para staging de Marble?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X727GMVC50GY8DKEFRE4M5", + "id": "01M1X72BERFS0YDB31ECN034JY", + "kind": "memory", + "score": 0.9998871088027954, + "summary": "project:fact - Marble staging HTTP listener binds TCP port 8533. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 368.4077, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 636669952, + "peak_working_set_bytes": 685076480 + }, + { + "query": "¿Dónde deben escribirse los mensajes de diagnóstico de Marble?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X727H33NDEE3FB9WX65292", + "id": "01M1X72BT88PG7JT7HX31BH3GH", + "kind": "memory", + "score": 0.9992142915725708, + "summary": "project:fact - Marble diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 360.4196, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 488, + "mcp_result_bytes": 569, + "wire_bytes": 604, + "reported_used_tokens": 569, + "working_set_bytes": 637104128, + "peak_working_set_bytes": 685076480 + }, + { + "query": "¿Qué comando revierte Marble a la versión anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X727HE87E87FQHNCMYMXE1", + "id": "01M1X72C5F3QR58E5P5DST092S", + "kind": "memory", + "score": 0.976457178592682, + "summary": "project:fact - To roll back Marble to the previous release, run `marblectl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 362.1443, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 616, + "reported_used_tokens": 580, + "working_set_bytes": 637161472, + "peak_working_set_bytes": 685076480 + }, + { + "query": "¿En qué región está desplegado Marble en producción?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X727HTMRN9TYZZW1TD16HZ", + "id": "01M1X72CHY07XP4KYH11F0YRCD", + "kind": "memory", + "score": 0.9999542236328124, + "summary": "project:fact - Marble production runs in region ap-south-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 401.4961, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 637186048, + "peak_working_set_bytes": 685076480 + }, + { + "query": "¿A qué hora UTC empiezan las copias diarias de la base de datos de Marble?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X727J6FTK6KWNT7T2YMVKY", + "id": "01M1X72CYAFS14C4PDXTAHSA3N", + "kind": "memory", + "score": 0.9999665021896362, + "summary": "project:fact - Marble daily database backups start at 01:25 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 422.5777, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 637493248, + "peak_working_set_bytes": 685076480 + }, + { + "query": "¿Qué base de datos y modo de registro usa Marble para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X727JJK6XWTG4J8QMGY1ZP", + "id": "01M1X72DC1AQHZ2MRRVQWBESZ2", + "kind": "memory", + "score": 0.9953057169914246, + "summary": "project:fact - Marble stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 416.2232, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 637652992, + "peak_working_set_bytes": 685076480 + }, + { + "query": "What authentication password is configured for the Marble staging listener?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X727GMVC50GY8DKEFRE4M5", + "id": "01M1X72DR3M5NBVJ6CFKTPXMBC", + "kind": "memory", + "score": 0.9787366390228271, + "summary": "project:fact - Marble staging HTTP listener binds TCP port 8533. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 379.47999999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 615, + "reported_used_tokens": 579, + "working_set_bytes": 637722624, + "peak_working_set_bytes": 685076480 + }, + { + "query": "What encryption key protects the Marble database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 363.7939, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643563520, + "peak_working_set_bytes": 685076480 + }, + { + "query": "How many production replicas run in the Marble deployment region?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X727HTMRN9TYZZW1TD16HZ", + "id": "01M1X72EEQ3X1AR8446JZ5GSSV", + "kind": "memory", + "score": 0.8181904554367065, + "summary": "project:fact - Marble production runs in region ap-south-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 357.0978, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 643567616, + "peak_working_set_bytes": 685076480 + }, + { + "query": "¿Qué contraseña exige el servidor de staging de Marble?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X727GMVC50GY8DKEFRE4M5", + "id": "01M1X72ET25H8HVNEK095TGVTB", + "kind": "memory", + "score": 0.8728806972503662, + "summary": "project:fact - Marble staging HTTP listener binds TCP port 8533. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 356.6876, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 615, + "reported_used_tokens": 579, + "working_set_bytes": 643657728, + "peak_working_set_bytes": 685076480 + }, + { + "query": "¿Cuántos días se conservan las copias de seguridad de Marble?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X727J6FTK6KWNT7T2YMVKY", + "id": "01M1X72F53JBH1Y89MPZ8ZAHFA", + "kind": "memory", + "score": 0.9691649079322816, + "summary": "project:fact - Marble daily database backups start at 01:25 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 361.50120000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 643756032, + "peak_working_set_bytes": 685076480 + }, + { + "query": "¿Qué versión de SQLite requiere Marble?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 354.6435, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643760128, + "peak_working_set_bytes": 685076480 + } + ], + "id": "marble-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 0.7777777777777778, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.667 (n=6) positive-n=12 negative-n=6 (18 queries)" + }, + { + "observations": [ + { + "query": "Which TCP port should I connect to for Kestrel staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72GYXRMH0RZFYXMQAWJDG", + "id": "01M1X72JSNRHF8DPWJGBRR2X6N", + "kind": "memory", + "score": 0.9999393224716188, + "summary": "project:fact - Kestrel staging HTTP listener binds TCP port 9647. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1796.6894, + "first_query": true, + "server_startup_ms": 75.3927, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 633917440, + "peak_working_set_bytes": 684920832 + }, + { + "query": "Where should Kestrel diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72GZEW7S19KADK0PZBR2C", + "id": "01M1X72K54ZHSF6AZX85HPNCJ9", + "kind": "memory", + "score": 0.9987480640411376, + "summary": "project:fact - Kestrel diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X72H17WCT1WQJ24Q6MHHCV", + "id": "01M1X72K543EFWD2QVJ8PJ8F2W", + "kind": "memory", + "score": 0.9422296285629272, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 354.5085, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 756, + "mcp_result_bytes": 855, + "wire_bytes": 890, + "reported_used_tokens": 855, + "working_set_bytes": 636039168, + "peak_working_set_bytes": 684920832 + }, + { + "query": "Which command rolls back Kestrel to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72GZSVRN19QDYSCVB4Q15", + "id": "01M1X72KG6DRQEWPVCK77WB7PD", + "kind": "memory", + "score": 0.9997856020927428, + "summary": "project:fact - To roll back Kestrel to the previous release, run `kestrelctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 352.1329, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 501, + "mcp_result_bytes": 582, + "wire_bytes": 617, + "reported_used_tokens": 582, + "working_set_bytes": 636416000, + "peak_working_set_bytes": 684920832 + }, + { + "query": "Which region hosts Kestrel production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72H05N0636E65RDJQ4FQ8", + "id": "01M1X72KV1945BKJZ7RTYK7J76", + "kind": "memory", + "score": 0.9999779462814332, + "summary": "project:fact - Kestrel production runs in region eu-west-3. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 361.4742, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 609, + "reported_used_tokens": 574, + "working_set_bytes": 636456960, + "peak_working_set_bytes": 684920832 + }, + { + "query": "At what UTC time do daily Kestrel database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72H0W13A6GHA15KETR966", + "id": "01M1X72M6HQS0ZMJ2JW85DS071", + "kind": "memory", + "score": 0.999980330467224, + "summary": "project:fact - Kestrel daily database backups start at 03:50 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 359.24089999999995, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 472, + "mcp_result_bytes": 553, + "wire_bytes": 588, + "reported_used_tokens": 553, + "working_set_bytes": 636600320, + "peak_working_set_bytes": 684920832 + }, + { + "query": "Which database and journal mode does Kestrel use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72H17WCT1WQJ24Q6MHHCV", + "id": "01M1X72MHS3G2ERGKPH3KWFVVW", + "kind": "memory", + "score": 0.999975323677063, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 360.8176, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 636637184, + "peak_working_set_bytes": 684920832 + }, + { + "query": "¿A qué puerto TCP debo conectarme para staging de Kestrel?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72GYXRMH0RZFYXMQAWJDG", + "id": "01M1X72MX5TN622A0MEJXHGAYT", + "kind": "memory", + "score": 0.9999423027038574, + "summary": "project:fact - Kestrel staging HTTP listener binds TCP port 9647. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 364.9794, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 639184896, + "peak_working_set_bytes": 684920832 + }, + { + "query": "¿Dónde deben escribirse los mensajes de diagnóstico de Kestrel?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72GZEW7S19KADK0PZBR2C", + "id": "01M1X72N8QXXBNJT3WYEEGDGKJ", + "kind": "memory", + "score": 0.9997678399086, + "summary": "project:fact - Kestrel diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 380.63239999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 489, + "mcp_result_bytes": 570, + "wire_bytes": 605, + "reported_used_tokens": 570, + "working_set_bytes": 639291392, + "peak_working_set_bytes": 684920832 + }, + { + "query": "¿Qué comando revierte Kestrel a la versión anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72GZSVRN19QDYSCVB4Q15", + "id": "01M1X72NN2CEE8TDW5VVVRK69H", + "kind": "memory", + "score": 0.9766082763671876, + "summary": "project:fact - To roll back Kestrel to the previous release, run `kestrelctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 378.7312, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 501, + "mcp_result_bytes": 582, + "wire_bytes": 618, + "reported_used_tokens": 582, + "working_set_bytes": 639406080, + "peak_working_set_bytes": 684920832 + }, + { + "query": "¿En qué región está desplegado Kestrel en producción?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72H05N0636E65RDJQ4FQ8", + "id": "01M1X72P053AX0Z8YWV87BMNZ3", + "kind": "memory", + "score": 0.999974250793457, + "summary": "project:fact - Kestrel production runs in region eu-west-3. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.9003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 639442944, + "peak_working_set_bytes": 684920832 + }, + { + "query": "¿A qué hora UTC empiezan las copias diarias de la base de datos de Kestrel?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72H0W13A6GHA15KETR966", + "id": "01M1X72PBBD5GGRD0B4DJ3TWR7", + "kind": "memory", + "score": 0.9999799728393556, + "summary": "project:fact - Kestrel daily database backups start at 03:50 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 363.1298, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 472, + "mcp_result_bytes": 553, + "wire_bytes": 589, + "reported_used_tokens": 553, + "working_set_bytes": 639725568, + "peak_working_set_bytes": 684920832 + }, + { + "query": "¿Qué base de datos y modo de registro usa Kestrel para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72H17WCT1WQJ24Q6MHHCV", + "id": "01M1X72PPM5PRECK62TJ355WWF", + "kind": "memory", + "score": 0.9993937015533448, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 364.2815, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 639746048, + "peak_working_set_bytes": 684920832 + }, + { + "query": "What authentication password is configured for the Kestrel staging listener?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72GYXRMH0RZFYXMQAWJDG", + "id": "01M1X72Q21WXNDJWHS7Y3V6H77", + "kind": "memory", + "score": 0.9726881980895996, + "summary": "project:fact - Kestrel staging HTTP listener binds TCP port 9647. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 367.7531, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 616, + "reported_used_tokens": 580, + "working_set_bytes": 639885312, + "peak_working_set_bytes": 684920832 + }, + { + "query": "What encryption key protects the Kestrel database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 357.1373, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644059136, + "peak_working_set_bytes": 684920832 + }, + { + "query": "How many production replicas run in the Kestrel deployment region?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72H05N0636E65RDJQ4FQ8", + "id": "01M1X72QRRVMV3WP9DCZRK26E1", + "kind": "memory", + "score": 0.958982229232788, + "summary": "project:fact - Kestrel production runs in region eu-west-3. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 364.6513, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 644169728, + "peak_working_set_bytes": 684920832 + }, + { + "query": "¿Qué contraseña exige el servidor de staging de Kestrel?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72GYXRMH0RZFYXMQAWJDG", + "id": "01M1X72R4CJ0XXCRST6213145H", + "kind": "memory", + "score": 0.9609549045562744, + "summary": "project:fact - Kestrel staging HTTP listener binds TCP port 9647. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 369.5267, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 616, + "reported_used_tokens": 580, + "working_set_bytes": 644268032, + "peak_working_set_bytes": 684920832 + }, + { + "query": "¿Cuántos días se conservan las copias de seguridad de Kestrel?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72H0W13A6GHA15KETR966", + "id": "01M1X72RG3KNR3G3ZG5AN7CTCG", + "kind": "memory", + "score": 0.9748653173446656, + "summary": "project:fact - Kestrel daily database backups start at 03:50 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 384.18370000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 472, + "mcp_result_bytes": 553, + "wire_bytes": 589, + "reported_used_tokens": 553, + "working_set_bytes": 644440064, + "peak_working_set_bytes": 684920832 + }, + { + "query": "¿Qué versión de SQLite requiere Kestrel?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72H17WCT1WQJ24Q6MHHCV", + "id": "01M1X72RWF5FCGFVMQ0M3NSAT4", + "kind": "memory", + "score": 0.6894522309303284, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 868.6243000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 644513792, + "peak_working_set_bytes": 684920832 + } + ], + "id": "kestrel-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 0.7222222222222222, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.833 (n=6) positive-n=12 negative-n=6 (18 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 2.9444444444444446, + 4 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 0.7361111111111112, + "n": 4, + "ci95": 0.027222222222222234 + } + }, + "overall_index": 0.7361111111111112, + "scenario_weighted_index": 0.7361111111111112 +} diff --git a/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/1-candidate.stderr.log b/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/1-candidate.stderr.log new file mode 100644 index 0000000..46b6a08 --- /dev/null +++ b/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/1-candidate.stderr.log @@ -0,0 +1,10 @@ +brainbench: 4 scenario(s) to run + [1/4] copper-unseen-project | dim=retrieval tier=hard ... + -> score=1.00 | positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.000 (n=6) positive-n=12 negative-n=6 (18 queries) + [2/4] willow-unseen-project | dim=retrieval tier=hard ... + -> score=1.00 | positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.000 (n=6) positive-n=12 negative-n=6 (18 queries) + [3/4] marble-unseen-project | dim=retrieval tier=hard ... + -> score=1.00 | positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.000 (n=6) positive-n=12 negative-n=6 (18 queries) + [4/4] kestrel-unseen-project | dim=retrieval tier=hard ... + -> score=1.00 | positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.000 (n=6) positive-n=12 negative-n=6 (18 queries) +kbench brainbench: report saved -> E:\tmp\kimetsu-brain-hardening\bench\local\runs\brainbench\2026-09-07T05-58-06.4903036Z.json diff --git a/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/1-candidate.stdout.log b/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/1-candidate.stdout.log new file mode 100644 index 0000000..b048a78 --- /dev/null +++ b/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/1-candidate.stdout.log @@ -0,0 +1,1921 @@ +{ + "generated_at": "2026-09-07T05:58:06.4898775Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-retrieval\\validation-frozen.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "Which TCP port should I connect to for Copper staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72TSZCTRBH5XP4A1SP4DA", + "id": "01M1X72WPD6D1WERKRDE9P516N", + "kind": "memory", + "score": 0.9998394250869752, + "summary": "project:fact - Copper staging HTTP listener binds TCP port 6319. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1874.9685, + "first_query": true, + "server_startup_ms": 74.1371, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 636510208, + "peak_working_set_bytes": 685019136 + }, + { + "query": "Where should Copper diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72TTGX8NYTSHETBSD9DG2", + "id": "01M1X72X31RZDTPC2E7ETJXTXQ", + "kind": "memory", + "score": 0.9978247880935668, + "summary": "project:fact - Copper diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X72TVYESSMSVGDYVF615B6", + "id": "01M1X72X318MSFCB1ER341VXZA", + "kind": "memory", + "score": 0.6390834450721741, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 378.31710000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 754, + "mcp_result_bytes": 853, + "wire_bytes": 888, + "reported_used_tokens": 853, + "working_set_bytes": 637009920, + "peak_working_set_bytes": 685019136 + }, + { + "query": "Which command rolls back Copper to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72TTW5WDNEBD9DPK8WDFY", + "id": "01M1X72XEQE18WAHQ7YR2M6AZT", + "kind": "memory", + "score": 0.9998852014541626, + "summary": "project:fact - To roll back Copper to the previous release, run `copperctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 384.0297, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 639234048, + "peak_working_set_bytes": 685019136 + }, + { + "query": "Which region hosts Copper production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72TV7A6DA6368D04C09DY", + "id": "01M1X72XY3GXR0436SQT2CG9ZD", + "kind": "memory", + "score": 0.9999468326568604, + "summary": "project:fact - Copper production runs in region eu-north-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 494.4572, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 609, + "reported_used_tokens": 574, + "working_set_bytes": 639328256, + "peak_working_set_bytes": 685019136 + }, + { + "query": "At what UTC time do daily Copper database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72TVKXMBA4VFV7DW0YEC7", + "id": "01M1X72YBAVGR6DG5FPCHPM17K", + "kind": "memory", + "score": 0.9999791383743286, + "summary": "project:fact - Copper daily database backups start at 02:40 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 420.66540000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 587, + "reported_used_tokens": 552, + "working_set_bytes": 639500288, + "peak_working_set_bytes": 685019136 + }, + { + "query": "Which database and journal mode does Copper use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72TVYESSMSVGDYVF615B6", + "id": "01M1X72YQPNQEG7VQ22YQ7R2WK", + "kind": "memory", + "score": 0.9999594688415528, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 399.2042, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 639782912, + "peak_working_set_bytes": 685019136 + }, + { + "query": "¿A qué puerto TCP debo conectarme para staging de Copper?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72TSZCTRBH5XP4A1SP4DA", + "id": "01M1X72Z3JN29J0SZTP3BRPYNA", + "kind": "memory", + "score": 0.9998682737350464, + "summary": "project:fact - Copper staging HTTP listener binds TCP port 6319. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 368.17080000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 642162688, + "peak_working_set_bytes": 685019136 + }, + { + "query": "¿Dónde deben escribirse los mensajes de diagnóstico de Copper?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72TTGX8NYTSHETBSD9DG2", + "id": "01M1X72ZEZYY5JN88XEJ1N3FPC", + "kind": "memory", + "score": 0.9995033740997314, + "summary": "project:fact - Copper diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 358.5598, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 488, + "mcp_result_bytes": 569, + "wire_bytes": 604, + "reported_used_tokens": 569, + "working_set_bytes": 642531328, + "peak_working_set_bytes": 685019136 + }, + { + "query": "¿Qué comando revierte Copper a la versión anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72TTW5WDNEBD9DPK8WDFY", + "id": "01M1X72ZT5HNKKN3A8XYZNYYKJ", + "kind": "memory", + "score": 0.9887914657592772, + "summary": "project:fact - To roll back Copper to the previous release, run `copperctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.8206, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 616, + "reported_used_tokens": 580, + "working_set_bytes": 642572288, + "peak_working_set_bytes": 685019136 + }, + { + "query": "¿En qué región está desplegado Copper en producción?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72TV7A6DA6368D04C09DY", + "id": "01M1X7305F7B0FJ3N8H2SNYCCP", + "kind": "memory", + "score": 0.9999468326568604, + "summary": "project:fact - Copper production runs in region eu-north-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 364.0555, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 642666496, + "peak_working_set_bytes": 685019136 + }, + { + "query": "¿A qué hora UTC empiezan las copias diarias de la base de datos de Copper?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72TVKXMBA4VFV7DW0YEC7", + "id": "01M1X730GPR4GP5Z2F58GAME0H", + "kind": "memory", + "score": 0.9999747276306152, + "summary": "project:fact - Copper daily database backups start at 02:40 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 367.7949, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 642969600, + "peak_working_set_bytes": 685019136 + }, + { + "query": "¿Qué base de datos y modo de registro usa Copper para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X72TVYESSMSVGDYVF615B6", + "id": "01M1X730WDB54PQRD3TG576B8J", + "kind": "memory", + "score": 0.9983224272727966, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 368.5271, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 643047424, + "peak_working_set_bytes": 685019136 + }, + { + "query": "What authentication password is configured for the Copper staging listener?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 371.72139999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643117056, + "peak_working_set_bytes": 685019136 + }, + { + "query": "What encryption key protects the Copper database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 398.0603, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 648998912, + "peak_working_set_bytes": 685019136 + }, + { + "query": "How many production replicas run in the Copper deployment region?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 359.8829, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 649052160, + "peak_working_set_bytes": 685019136 + }, + { + "query": "¿Qué contraseña exige el servidor de staging de Copper?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 358.9117, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 649179136, + "peak_working_set_bytes": 685019136 + }, + { + "query": "¿Cuántos días se conservan las copias de seguridad de Copper?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 362.636, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 649416704, + "peak_working_set_bytes": 685019136 + }, + { + "query": "¿Qué versión de SQLite requiere Copper?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 355.341, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 649437184, + "peak_working_set_bytes": 685019136 + } + ], + "id": "copper-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.000 (n=6) positive-n=12 negative-n=6 (18 queries)" + }, + { + "observations": [ + { + "query": "Which TCP port should I connect to for Willow staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X734E3R7KBEAZ3G19MRD8Q", + "id": "01M1X7368S46HG6TB6Y7MQJCJA", + "kind": "memory", + "score": 0.9999420642852784, + "summary": "project:fact - Willow staging HTTP listener binds TCP port 7421. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 2127.8483, + "first_query": true, + "server_startup_ms": 82.55, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 633872384, + "peak_working_set_bytes": 684879872 + }, + { + "query": "Where should Willow diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X734EJ6Y5DKJRHZCDFJCA1", + "id": "01M1X737136NDBATAJ76NQZFD2", + "kind": "memory", + "score": 0.9971635937690736, + "summary": "project:fact - Willow diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X734G0SVTQ6A95YRS7W2HM", + "id": "01M1X73714YM8P84YNJEY5R2ZT", + "kind": "memory", + "score": 0.8971153497695923, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 452.9002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 754, + "mcp_result_bytes": 853, + "wire_bytes": 888, + "reported_used_tokens": 853, + "working_set_bytes": 634376192, + "peak_working_set_bytes": 684879872 + }, + { + "query": "Which command rolls back Willow to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X734EYZMGPXJJG798WB5EA", + "id": "01M1X737D3P0XK2GSJJ7EZ6VM2", + "kind": "memory", + "score": 0.9998542070388794, + "summary": "project:fact - To roll back Willow to the previous release, run `willowctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 391.2379, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 636596224, + "peak_working_set_bytes": 684879872 + }, + { + "query": "Which region hosts Willow production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X734FA553ERZT95KJRFZXA", + "id": "01M1X737SA7WTCX675SXMHDX1H", + "kind": "memory", + "score": 0.9999713897705078, + "summary": "project:fact - Willow production runs in region us-west-2. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 374.3574, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 636751872, + "peak_working_set_bytes": 684879872 + }, + { + "query": "At what UTC time do daily Willow database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X734FNC8X64Y4RDB7HMZD7", + "id": "01M1X7384EMTNYBA6AV6TCM6R1", + "kind": "memory", + "score": 0.9999792575836182, + "summary": "project:fact - Willow daily database backups start at 04:15 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 355.7612, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 587, + "reported_used_tokens": 552, + "working_set_bytes": 636911616, + "peak_working_set_bytes": 684879872 + }, + { + "query": "Which database and journal mode does Willow use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X734G0SVTQ6A95YRS7W2HM", + "id": "01M1X738FEJQVAEFEW428P9M71", + "kind": "memory", + "score": 0.9999637603759766, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 363.3105, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 636948480, + "peak_working_set_bytes": 684879872 + }, + { + "query": "¿A qué puerto TCP debo conectarme para staging de Willow?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X734E3R7KBEAZ3G19MRD8Q", + "id": "01M1X738TX4TW1SC0SNWKYQ4S9", + "kind": "memory", + "score": 0.9999486207962036, + "summary": "project:fact - Willow staging HTTP listener binds TCP port 7421. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 373.5032, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 639356928, + "peak_working_set_bytes": 684879872 + }, + { + "query": "¿Dónde deben escribirse los mensajes de diagnóstico de Willow?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X734EJ6Y5DKJRHZCDFJCA1", + "id": "01M1X7396XSMH1RXVT51QYV9SD", + "kind": "memory", + "score": 0.9996838569641112, + "summary": "project:fact - Willow diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 370.3033, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 488, + "mcp_result_bytes": 569, + "wire_bytes": 604, + "reported_used_tokens": 569, + "working_set_bytes": 639946752, + "peak_working_set_bytes": 684879872 + }, + { + "query": "¿Qué comando revierte Willow a la versión anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X734EYZMGPXJJG798WB5EA", + "id": "01M1X739J8711F6R7TZW1D3CWH", + "kind": "memory", + "score": 0.98951655626297, + "summary": "project:fact - To roll back Willow to the previous release, run `willowctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 360.3769, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 497, + "mcp_result_bytes": 578, + "wire_bytes": 614, + "reported_used_tokens": 578, + "working_set_bytes": 640045056, + "peak_working_set_bytes": 684879872 + }, + { + "query": "¿En qué región está desplegado Willow en producción?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X734FA553ERZT95KJRFZXA", + "id": "01M1X739XJRGT4HPEYPEHK2JKD", + "kind": "memory", + "score": 0.9999579191207886, + "summary": "project:fact - Willow production runs in region us-west-2. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 368.3913, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 640237568, + "peak_working_set_bytes": 684879872 + }, + { + "query": "¿A qué hora UTC empiezan las copias diarias de la base de datos de Willow?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X734FNC8X64Y4RDB7HMZD7", + "id": "01M1X73AA619RWHZFZ8N9R01Y1", + "kind": "memory", + "score": 0.99997878074646, + "summary": "project:fact - Willow daily database backups start at 04:15 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 400.6685, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 469, + "mcp_result_bytes": 550, + "wire_bytes": 586, + "reported_used_tokens": 550, + "working_set_bytes": 640577536, + "peak_working_set_bytes": 684879872 + }, + { + "query": "¿Qué base de datos y modo de registro usa Willow para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X734G0SVTQ6A95YRS7W2HM", + "id": "01M1X73ANS8NX905HF5ARPB72P", + "kind": "memory", + "score": 0.999204695224762, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 376.4599, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 491, + "mcp_result_bytes": 572, + "wire_bytes": 608, + "reported_used_tokens": 572, + "working_set_bytes": 640712704, + "peak_working_set_bytes": 684879872 + }, + { + "query": "What authentication password is configured for the Willow staging listener?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 375.4146, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 640991232, + "peak_working_set_bytes": 684879872 + }, + { + "query": "What encryption key protects the Willow database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 374.1562, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 646864896, + "peak_working_set_bytes": 684879872 + }, + { + "query": "How many production replicas run in the Willow deployment region?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 386.0922, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 647004160, + "peak_working_set_bytes": 684879872 + }, + { + "query": "¿Qué contraseña exige el servidor de staging de Willow?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 463.5531, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 647122944, + "peak_working_set_bytes": 684879872 + }, + { + "query": "¿Cuántos días se conservan las copias de seguridad de Willow?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 422.67139999999995, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 647299072, + "peak_working_set_bytes": 684879872 + }, + { + "query": "¿Qué versión de SQLite requiere Willow?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 407.32370000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 647389184, + "peak_working_set_bytes": 684879872 + } + ], + "id": "willow-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.000 (n=6) positive-n=12 negative-n=6 (18 queries)" + }, + { + "observations": [ + { + "query": "Which TCP port should I connect to for Marble staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73EFTPK7D5G6W0Q2R9ZHQ", + "id": "01M1X73GBJDV176BEXMHR317S2", + "kind": "memory", + "score": 0.9996737241744996, + "summary": "project:fact - Marble staging HTTP listener binds TCP port 8533. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1838.8174, + "first_query": true, + "server_startup_ms": 75.7183, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 633483264, + "peak_working_set_bytes": 684716032 + }, + { + "query": "Where should Marble diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73EG90V7J8TSY1YP6Z08S", + "id": "01M1X73GPM6J3KP5RG5K6TXBAS", + "kind": "memory", + "score": 0.9971815347671508, + "summary": "project:fact - Marble diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X73EHTVTVNW3MFSG09NYDT", + "id": "01M1X73GPNDDN09Y2J7K97VJC1", + "kind": "memory", + "score": 0.6012999415397644, + "summary": "project:fact - Marble stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 347.279, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 754, + "mcp_result_bytes": 853, + "wire_bytes": 888, + "reported_used_tokens": 853, + "working_set_bytes": 633958400, + "peak_working_set_bytes": 684716032 + }, + { + "query": "Which command rolls back Marble to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73EGPE9JSETHHQPQDDHTR", + "id": "01M1X73H1RTVW1S21R2PKP4C65", + "kind": "memory", + "score": 0.9997585415840148, + "summary": "project:fact - To roll back Marble to the previous release, run `marblectl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 359.86899999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 635793408, + "peak_working_set_bytes": 684716032 + }, + { + "query": "Which region hosts Marble production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73EH2E4SM8ZSMZZ5JNDX4", + "id": "01M1X73HCSCNQCCJ1Z5Y15F0EK", + "kind": "memory", + "score": 0.999954104423523, + "summary": "project:fact - Marble production runs in region ap-south-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 345.8051, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 636002304, + "peak_working_set_bytes": 684716032 + }, + { + "query": "At what UTC time do daily Marble database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73EHEASFR0B8WKNDYQ0JB", + "id": "01M1X73HR0WBS5SE7WVDKMAR5V", + "kind": "memory", + "score": 0.999979853630066, + "summary": "project:fact - Marble daily database backups start at 01:25 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 368.7463, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 587, + "reported_used_tokens": 552, + "working_set_bytes": 636166144, + "peak_working_set_bytes": 684716032 + }, + { + "query": "Which database and journal mode does Marble use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73EHTVTVNW3MFSG09NYDT", + "id": "01M1X73J3DR6R8ABA81X6V1H3G", + "kind": "memory", + "score": 0.9999568462371826, + "summary": "project:fact - Marble stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 368.8099, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 636227584, + "peak_working_set_bytes": 684716032 + }, + { + "query": "¿A qué puerto TCP debo conectarme para staging de Marble?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73EFTPK7D5G6W0Q2R9ZHQ", + "id": "01M1X73JFGZ3QJTPGNAP8K0214", + "kind": "memory", + "score": 0.9998871088027954, + "summary": "project:fact - Marble staging HTTP listener binds TCP port 8533. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 381.834, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 638672896, + "peak_working_set_bytes": 684716032 + }, + { + "query": "¿Dónde deben escribirse los mensajes de diagnóstico de Marble?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73EG90V7J8TSY1YP6Z08S", + "id": "01M1X73JTXXVB3FC35GG1X0FHR", + "kind": "memory", + "score": 0.9992142915725708, + "summary": "project:fact - Marble diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 362.8531, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 488, + "mcp_result_bytes": 569, + "wire_bytes": 604, + "reported_used_tokens": 569, + "working_set_bytes": 638984192, + "peak_working_set_bytes": 684716032 + }, + { + "query": "¿Qué comando revierte Marble a la versión anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73EGPE9JSETHHQPQDDHTR", + "id": "01M1X73K5YYN5PR11ZCBJ6Q3P7", + "kind": "memory", + "score": 0.976457178592682, + "summary": "project:fact - To roll back Marble to the previous release, run `marblectl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 351.7305, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 616, + "reported_used_tokens": 580, + "working_set_bytes": 639016960, + "peak_working_set_bytes": 684716032 + }, + { + "query": "¿En qué región está desplegado Marble en producción?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73EH2E4SM8ZSMZZ5JNDX4", + "id": "01M1X73KH2QEAQSJ09NCPQQCK1", + "kind": "memory", + "score": 0.9999542236328124, + "summary": "project:fact - Marble production runs in region ap-south-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 357.1974, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 639098880, + "peak_working_set_bytes": 684716032 + }, + { + "query": "¿A qué hora UTC empiezan las copias diarias de la base de datos de Marble?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73EHEASFR0B8WKNDYQ0JB", + "id": "01M1X73KW73HG1VJ76YCXFWTJV", + "kind": "memory", + "score": 0.9999665021896362, + "summary": "project:fact - Marble daily database backups start at 01:25 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 369.36429999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 639352832, + "peak_working_set_bytes": 684716032 + }, + { + "query": "¿Qué base de datos y modo de registro usa Marble para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73EHTVTVNW3MFSG09NYDT", + "id": "01M1X73M7SJW7WHGCP5HY7KPDT", + "kind": "memory", + "score": 0.9953057169914246, + "summary": "project:fact - Marble stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 358.5002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 639410176, + "peak_working_set_bytes": 684716032 + }, + { + "query": "What authentication password is configured for the Marble staging listener?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 354.3449, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 639455232, + "peak_working_set_bytes": 684716032 + }, + { + "query": "What encryption key protects the Marble database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 360.3377, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645300224, + "peak_working_set_bytes": 684716032 + }, + { + "query": "How many production replicas run in the Marble deployment region?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 365.1542, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645394432, + "peak_working_set_bytes": 684716032 + }, + { + "query": "¿Qué contraseña exige el servidor de staging de Marble?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 382.7403, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645435392, + "peak_working_set_bytes": 684716032 + }, + { + "query": "¿Cuántos días se conservan las copias de seguridad de Marble?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 392.1383, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645693440, + "peak_working_set_bytes": 684716032 + }, + { + "query": "¿Qué versión de SQLite requiere Marble?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 351.8322, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645718016, + "peak_working_set_bytes": 684716032 + } + ], + "id": "marble-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.000 (n=6) positive-n=12 negative-n=6 (18 queries)" + }, + { + "observations": [ + { + "query": "Which TCP port should I connect to for Kestrel staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73QRNJQ7DSC1JD09J6JB3", + "id": "01M1X73SJS1415PP6DFEKWR4V3", + "kind": "memory", + "score": 0.9999393224716188, + "summary": "project:fact - Kestrel staging HTTP listener binds TCP port 9647. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1830.7945000000002, + "first_query": true, + "server_startup_ms": 73.8767, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 635355136, + "peak_working_set_bytes": 684814336 + }, + { + "query": "Where should Kestrel diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73QS5ST5HDREKM31ANXES", + "id": "01M1X73T0113QVTCFV18ZC8AYZ", + "kind": "memory", + "score": 0.9987480640411376, + "summary": "project:fact - Kestrel diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X73QTMC0QMJGBCGAMKQZHV", + "id": "01M1X73T018HSGQ8DJ6G2EQFAT", + "kind": "memory", + "score": 0.9422296285629272, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 384.8595, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 756, + "mcp_result_bytes": 855, + "wire_bytes": 890, + "reported_used_tokens": 855, + "working_set_bytes": 637423616, + "peak_working_set_bytes": 684814336 + }, + { + "query": "Which command rolls back Kestrel to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73QSHYSH5J4F7VQZ5EMQD", + "id": "01M1X73TCBQR0EV478VBYBQ9V3", + "kind": "memory", + "score": 0.9997856020927428, + "summary": "project:fact - To roll back Kestrel to the previous release, run `kestrelctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 416.8803, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 501, + "mcp_result_bytes": 582, + "wire_bytes": 617, + "reported_used_tokens": 582, + "working_set_bytes": 639750144, + "peak_working_set_bytes": 684814336 + }, + { + "query": "Which region hosts Kestrel production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73QSW1BFDTY924DA22RSX", + "id": "01M1X73TRPSKF6XZAN42JBNB63", + "kind": "memory", + "score": 0.9999779462814332, + "summary": "project:fact - Kestrel production runs in region eu-west-3. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 363.543, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 609, + "reported_used_tokens": 574, + "working_set_bytes": 639905792, + "peak_working_set_bytes": 684814336 + }, + { + "query": "At what UTC time do daily Kestrel database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73QT8N6R1Y9HK4YJKSJ74", + "id": "01M1X73V3R5B4KPRGA9VJHCKZA", + "kind": "memory", + "score": 0.999980330467224, + "summary": "project:fact - Kestrel daily database backups start at 03:50 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 357.5294, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 472, + "mcp_result_bytes": 553, + "wire_bytes": 588, + "reported_used_tokens": 553, + "working_set_bytes": 640327680, + "peak_working_set_bytes": 684814336 + }, + { + "query": "Which database and journal mode does Kestrel use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73QTMC0QMJGBCGAMKQZHV", + "id": "01M1X73VEWHS67RNHNEYF2W2VJ", + "kind": "memory", + "score": 0.999975323677063, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 353.3806, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 640483328, + "peak_working_set_bytes": 684814336 + }, + { + "query": "¿A qué puerto TCP debo conectarme para staging de Kestrel?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73QRNJQ7DSC1JD09J6JB3", + "id": "01M1X73VSZN3PHNES9C1VSQ2T9", + "kind": "memory", + "score": 0.9999423027038574, + "summary": "project:fact - Kestrel staging HTTP listener binds TCP port 9647. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 361.1123, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 642949120, + "peak_working_set_bytes": 684814336 + }, + { + "query": "¿Dónde deben escribirse los mensajes de diagnóstico de Kestrel?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73QS5ST5HDREKM31ANXES", + "id": "01M1X73W5ENVPCKV8MRGGXT9MM", + "kind": "memory", + "score": 0.9997678399086, + "summary": "project:fact - Kestrel diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 364.33959999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 489, + "mcp_result_bytes": 570, + "wire_bytes": 605, + "reported_used_tokens": 570, + "working_set_bytes": 643252224, + "peak_working_set_bytes": 684814336 + }, + { + "query": "¿Qué comando revierte Kestrel a la versión anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73QSHYSH5J4F7VQZ5EMQD", + "id": "01M1X73WGKH2F1G88B84ZC1TMG", + "kind": "memory", + "score": 0.9766082763671876, + "summary": "project:fact - To roll back Kestrel to the previous release, run `kestrelctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 354.2321, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 501, + "mcp_result_bytes": 582, + "wire_bytes": 618, + "reported_used_tokens": 582, + "working_set_bytes": 643289088, + "peak_working_set_bytes": 684814336 + }, + { + "query": "¿En qué región está desplegado Kestrel en producción?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73QSW1BFDTY924DA22RSX", + "id": "01M1X73WVPDR4YR5SZ100AGRK9", + "kind": "memory", + "score": 0.999974250793457, + "summary": "project:fact - Kestrel production runs in region eu-west-3. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 362.2926, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 643379200, + "peak_working_set_bytes": 684814336 + }, + { + "query": "¿A qué hora UTC empiezan las copias diarias de la base de datos de Kestrel?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73QT8N6R1Y9HK4YJKSJ74", + "id": "01M1X73X73YWNRD37MHS20TF6N", + "kind": "memory", + "score": 0.9999799728393556, + "summary": "project:fact - Kestrel daily database backups start at 03:50 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.4208, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 472, + "mcp_result_bytes": 553, + "wire_bytes": 589, + "reported_used_tokens": 553, + "working_set_bytes": 643600384, + "peak_working_set_bytes": 684814336 + }, + { + "query": "¿Qué base de datos y modo de registro usa Kestrel para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X73QTMC0QMJGBCGAMKQZHV", + "id": "01M1X73XJD09P4R2ZVQDXZQTQJ", + "kind": "memory", + "score": 0.9993937015533448, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 372.9329, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 643727360, + "peak_working_set_bytes": 684814336 + }, + { + "query": "What authentication password is configured for the Kestrel staging listener?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 385.90590000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643854336, + "peak_working_set_bytes": 684814336 + }, + { + "query": "What encryption key protects the Kestrel database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 359.2901, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 647958528, + "peak_working_set_bytes": 684814336 + }, + { + "query": "How many production replicas run in the Kestrel deployment region?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 361.19780000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 648044544, + "peak_working_set_bytes": 684814336 + }, + { + "query": "¿Qué contraseña exige el servidor de staging de Kestrel?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 356.8509, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 648155136, + "peak_working_set_bytes": 684814336 + }, + { + "query": "¿Cuántos días se conservan las copias de seguridad de Kestrel?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 364.7518, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 648404992, + "peak_working_set_bytes": 684814336 + }, + { + "query": "¿Qué versión de SQLite requiere Kestrel?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 352.72209999999995, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 648413184, + "peak_working_set_bytes": 684814336 + } + ], + "id": "kestrel-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.000 (n=6) positive-n=12 negative-n=6 (18 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 4.0, + 4 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 1.0, + "n": 4, + "ci95": 0.0 + } + }, + "overall_index": 1.0, + "scenario_weighted_index": 1.0 +} diff --git a/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/2-baseline.stderr.log b/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/2-baseline.stderr.log new file mode 100644 index 0000000..8bbce60 --- /dev/null +++ b/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/2-baseline.stderr.log @@ -0,0 +1,10 @@ +brainbench: 4 scenario(s) to run + [1/4] copper-unseen-project | dim=retrieval tier=hard ... + -> score=0.72 | positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.833 (n=6) positive-n=12 negative-n=6 (18 queries) + [2/4] willow-unseen-project | dim=retrieval tier=hard ... + -> score=0.72 | positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.833 (n=6) positive-n=12 negative-n=6 (18 queries) + [3/4] marble-unseen-project | dim=retrieval tier=hard ... + -> score=0.78 | positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.667 (n=6) positive-n=12 negative-n=6 (18 queries) + [4/4] kestrel-unseen-project | dim=retrieval tier=hard ... + -> score=0.72 | positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.833 (n=6) positive-n=12 negative-n=6 (18 queries) +kbench brainbench: report saved -> E:\tmp\kimetsu-brain-hardening\bench\local\runs\brainbench\2026-09-07T05-59-27.5560128Z.json diff --git a/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/2-baseline.stdout.log b/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/2-baseline.stdout.log new file mode 100644 index 0000000..52b26bb --- /dev/null +++ b/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/2-baseline.stdout.log @@ -0,0 +1,2130 @@ +{ + "generated_at": "2026-09-07T05:59:27.5556696Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-retrieval\\validation-frozen.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "Which TCP port should I connect to for Copper staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7563WQ1C0TZD8N8F4JCZ1", + "id": "01M1X758HTMYMR0D13F2CED4VF", + "kind": "memory", + "score": 0.9998394250869752, + "summary": "project:fact - Copper staging HTTP listener binds TCP port 6319. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1930.3919999999998, + "first_query": true, + "server_startup_ms": 72.3665, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 638246912, + "peak_working_set_bytes": 685117440 + }, + { + "query": "Where should Copper diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7564AHEQ0AVGTD6N2QFGJ", + "id": "01M1X75923008J9BK98YDQ44K2", + "kind": "memory", + "score": 0.9978247880935668, + "summary": "project:fact - Copper diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X7565SHPQ73GM40GRQYYW5", + "id": "01M1X759235WQXQK5F2ZNY3PFV", + "kind": "memory", + "score": 0.6390834450721741, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 439.2912, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 754, + "mcp_result_bytes": 853, + "wire_bytes": 888, + "reported_used_tokens": 853, + "working_set_bytes": 638803968, + "peak_working_set_bytes": 685117440 + }, + { + "query": "Which command rolls back Copper to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7564N1Q9CB9RBRHA75XWD", + "id": "01M1X759FT6FAQ5M0PKTPY5YWY", + "kind": "memory", + "score": 0.9998852014541626, + "summary": "project:fact - To roll back Copper to the previous release, run `copperctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 515.6246, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 638926848, + "peak_working_set_bytes": 685117440 + }, + { + "query": "Which region hosts Copper production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X756514BAK04ATXQKHP4AA", + "id": "01M1X759ZYFR4JP3Y5YWBN88ZC", + "kind": "memory", + "score": 0.9999468326568604, + "summary": "project:fact - Copper production runs in region eu-north-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 509.92530000000005, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 609, + "reported_used_tokens": 574, + "working_set_bytes": 639033344, + "peak_working_set_bytes": 685117440 + }, + { + "query": "At what UTC time do daily Copper database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7565D2PG212EBJY65Q27V", + "id": "01M1X75AFZT3VR73HD1NXZ48MN", + "kind": "memory", + "score": 0.9999791383743286, + "summary": "project:fact - Copper daily database backups start at 02:40 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 516.5822, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 587, + "reported_used_tokens": 552, + "working_set_bytes": 639143936, + "peak_working_set_bytes": 685117440 + }, + { + "query": "Which database and journal mode does Copper use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7565SHPQ73GM40GRQYYW5", + "id": "01M1X75B0VQZ7ZA38QVAF1320S", + "kind": "memory", + "score": 0.9999594688415528, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 461.7396, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 639201280, + "peak_working_set_bytes": 685117440 + }, + { + "query": "¿A qué puerto TCP debo conectarme para staging de Copper?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7563WQ1C0TZD8N8F4JCZ1", + "id": "01M1X75BEHBA1KZHBC4A1H2CSH", + "kind": "memory", + "score": 0.9998682737350464, + "summary": "project:fact - Copper staging HTTP listener binds TCP port 6319. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 545.5953999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 641671168, + "peak_working_set_bytes": 685117440 + }, + { + "query": "¿Dónde deben escribirse los mensajes de diagnóstico de Copper?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7564AHEQ0AVGTD6N2QFGJ", + "id": "01M1X75BZKPCFSJFJGKD7823WM", + "kind": "memory", + "score": 0.9995033740997314, + "summary": "project:fact - Copper diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 511.65039999999993, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 488, + "mcp_result_bytes": 569, + "wire_bytes": 604, + "reported_used_tokens": 569, + "working_set_bytes": 642076672, + "peak_working_set_bytes": 685117440 + }, + { + "query": "¿Qué comando revierte Copper a la versión anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7564N1Q9CB9RBRHA75XWD", + "id": "01M1X75CFQFYY539M7SCP2F95X", + "kind": "memory", + "score": 0.9887914657592772, + "summary": "project:fact - To roll back Copper to the previous release, run `copperctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 508.6619, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 616, + "reported_used_tokens": 580, + "working_set_bytes": 642174976, + "peak_working_set_bytes": 685117440 + }, + { + "query": "¿En qué región está desplegado Copper en producción?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X756514BAK04ATXQKHP4AA", + "id": "01M1X75CZEVRKNN8KYGYRDZ4D3", + "kind": "memory", + "score": 0.9999468326568604, + "summary": "project:fact - Copper production runs in region eu-north-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 506.68120000000005, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 642293760, + "peak_working_set_bytes": 685117440 + }, + { + "query": "¿A qué hora UTC empiezan las copias diarias de la base de datos de Copper?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7565D2PG212EBJY65Q27V", + "id": "01M1X75DFHGX3TW4QCRDV4W3P4", + "kind": "memory", + "score": 0.9999747276306152, + "summary": "project:fact - Copper daily database backups start at 02:40 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 896.8049, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 642666496, + "peak_working_set_bytes": 685117440 + }, + { + "query": "¿Qué base de datos y modo de registro usa Copper para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7565SHPQ73GM40GRQYYW5", + "id": "01M1X75EFM556NHWCGEQ1R1A36", + "kind": "memory", + "score": 0.9983224272727966, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 827.5897, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 642813952, + "peak_working_set_bytes": 685117440 + }, + { + "query": "What authentication password is configured for the Copper staging listener?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7563WQ1C0TZD8N8F4JCZ1", + "id": "01M1X75F5D5GGZFD872RPVFY0W", + "kind": "memory", + "score": 0.9784963130950928, + "summary": "project:fact - Copper staging HTTP listener binds TCP port 6319. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 520.6096, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 615, + "reported_used_tokens": 579, + "working_set_bytes": 642908160, + "peak_working_set_bytes": 685117440 + }, + { + "query": "What encryption key protects the Copper database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 444.1019, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 648704000, + "peak_working_set_bytes": 685117440 + }, + { + "query": "How many production replicas run in the Copper deployment region?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X756514BAK04ATXQKHP4AA", + "id": "01M1X75G3YGK1Y0J3GQE6X1E8E", + "kind": "memory", + "score": 0.8394170999526978, + "summary": "project:fact - Copper production runs in region eu-north-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 472.55740000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 648708096, + "peak_working_set_bytes": 685117440 + }, + { + "query": "¿Qué contraseña exige el servidor de staging de Copper?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7563WQ1C0TZD8N8F4JCZ1", + "id": "01M1X75GJ46RFFFZ4998K8F53H", + "kind": "memory", + "score": 0.898059606552124, + "summary": "project:fact - Copper staging HTTP listener binds TCP port 6319. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 512.1649, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 497, + "mcp_result_bytes": 578, + "wire_bytes": 614, + "reported_used_tokens": 578, + "working_set_bytes": 648736768, + "peak_working_set_bytes": 685117440 + }, + { + "query": "¿Cuántos días se conservan las copias de seguridad de Copper?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7565D2PG212EBJY65Q27V", + "id": "01M1X75H28RW95CMR72ACGR1SP", + "kind": "memory", + "score": 0.9550348520278932, + "summary": "project:fact - Copper daily database backups start at 02:40 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 413.6865, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 648814592, + "peak_working_set_bytes": 685117440 + }, + { + "query": "¿Qué versión de SQLite requiere Copper?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7565SHPQ73GM40GRQYYW5", + "id": "01M1X75HF5CMGFNRA66ZHNA807", + "kind": "memory", + "score": 0.6008884310722351, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 364.2525, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 648835072, + "peak_working_set_bytes": 685117440 + } + ], + "id": "copper-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 0.7222222222222222, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.833 (n=6) positive-n=12 negative-n=6 (18 queries)" + }, + { + "observations": [ + { + "query": "Which TCP port should I connect to for Willow staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75JWNVQQDSDWVJ8N3HH7F", + "id": "01M1X75MSG42JKX0H7GS1B7288", + "kind": "memory", + "score": 0.9999420642852784, + "summary": "project:fact - Willow staging HTTP listener binds TCP port 7421. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1853.4305, + "first_query": true, + "server_startup_ms": 73.5093, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 634368000, + "peak_working_set_bytes": 684843008 + }, + { + "query": "Where should Willow diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75JX4SC14ZEJ070R675E5", + "id": "01M1X75N5P6A5C75VDPM8W55J0", + "kind": "memory", + "score": 0.9971635937690736, + "summary": "project:fact - Willow diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X75JYMAB5KS5W09YYCZF9N", + "id": "01M1X75N5PZXN7P66871BVGQ32", + "kind": "memory", + "score": 0.8971153497695923, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 384.993, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 754, + "mcp_result_bytes": 853, + "wire_bytes": 888, + "reported_used_tokens": 853, + "working_set_bytes": 634855424, + "peak_working_set_bytes": 684843008 + }, + { + "query": "Which command rolls back Willow to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75JXGRRZN2HGW4TY5BH73", + "id": "01M1X75NJ22NC60BK3KTJC3E7B", + "kind": "memory", + "score": 0.9998542070388794, + "summary": "project:fact - To roll back Willow to the previous release, run `willowctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 381.25730000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 634912768, + "peak_working_set_bytes": 684843008 + }, + { + "query": "Which region hosts Willow production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75JXXRBRXJFRQH85YCQGV", + "id": "01M1X75NX0F67TEGGDNVCBB81D", + "kind": "memory", + "score": 0.9999713897705078, + "summary": "project:fact - Willow production runs in region us-west-2. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 348.233, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 634937344, + "peak_working_set_bytes": 684843008 + }, + { + "query": "At what UTC time do daily Willow database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75JY9WAP59SK69BF3E7JS", + "id": "01M1X75PBGSGC8VYZ1Q30D6Q9R", + "kind": "memory", + "score": 0.9999792575836182, + "summary": "project:fact - Willow daily database backups start at 04:15 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 482.6424, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 587, + "reported_used_tokens": 552, + "working_set_bytes": 635076608, + "peak_working_set_bytes": 684843008 + }, + { + "query": "Which database and journal mode does Willow use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75JYMAB5KS5W09YYCZF9N", + "id": "01M1X75PQ0M3VVYBKJRY74Y5RK", + "kind": "memory", + "score": 0.9999637603759766, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.6245, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 635203584, + "peak_working_set_bytes": 684843008 + }, + { + "query": "¿A qué puerto TCP debo conectarme para staging de Willow?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75JWNVQQDSDWVJ8N3HH7F", + "id": "01M1X75Q29E7VXNX77RMXB930S", + "kind": "memory", + "score": 0.9999486207962036, + "summary": "project:fact - Willow staging HTTP listener binds TCP port 7421. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 369.1739, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 637595648, + "peak_working_set_bytes": 684843008 + }, + { + "query": "¿Dónde deben escribirse los mensajes de diagnóstico de Willow?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75JX4SC14ZEJ070R675E5", + "id": "01M1X75QDYZ73W1A6X4G44BJW2", + "kind": "memory", + "score": 0.9996838569641112, + "summary": "project:fact - Willow diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 400.4669, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 488, + "mcp_result_bytes": 569, + "wire_bytes": 604, + "reported_used_tokens": 569, + "working_set_bytes": 637894656, + "peak_working_set_bytes": 684843008 + }, + { + "query": "¿Qué comando revierte Willow a la versión anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75JXGRRZN2HGW4TY5BH73", + "id": "01M1X75QT676E20G0PSSQM7F74", + "kind": "memory", + "score": 0.98951655626297, + "summary": "project:fact - To roll back Willow to the previous release, run `willowctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 375.85360000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 497, + "mcp_result_bytes": 578, + "wire_bytes": 614, + "reported_used_tokens": 578, + "working_set_bytes": 637984768, + "peak_working_set_bytes": 684843008 + }, + { + "query": "¿En qué región está desplegado Willow en producción?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75JXXRBRXJFRQH85YCQGV", + "id": "01M1X75R66HC56M7BSX1ZBPXK9", + "kind": "memory", + "score": 0.9999579191207886, + "summary": "project:fact - Willow production runs in region us-west-2. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 375.9858, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 638029824, + "peak_working_set_bytes": 684843008 + }, + { + "query": "¿A qué hora UTC empiezan las copias diarias de la base de datos de Willow?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75JY9WAP59SK69BF3E7JS", + "id": "01M1X75RJ1MND8TE8HDNQEV34T", + "kind": "memory", + "score": 0.99997878074646, + "summary": "project:fact - Willow daily database backups start at 04:15 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 386.0068, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 469, + "mcp_result_bytes": 550, + "wire_bytes": 586, + "reported_used_tokens": 550, + "working_set_bytes": 638328832, + "peak_working_set_bytes": 684843008 + }, + { + "query": "¿Qué base de datos y modo de registro usa Willow para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75JYMAB5KS5W09YYCZF9N", + "id": "01M1X75RYY2FC66CTAW894185Q", + "kind": "memory", + "score": 0.999204695224762, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 397.0801, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 491, + "mcp_result_bytes": 572, + "wire_bytes": 608, + "reported_used_tokens": 572, + "working_set_bytes": 638406656, + "peak_working_set_bytes": 684843008 + }, + { + "query": "What authentication password is configured for the Willow staging listener?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75JWNVQQDSDWVJ8N3HH7F", + "id": "01M1X75SA7Y0RG2V9Q6AERV1VB", + "kind": "memory", + "score": 0.9932246804237366, + "summary": "project:fact - Willow staging HTTP listener binds TCP port 7421. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 363.0219, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 615, + "reported_used_tokens": 579, + "working_set_bytes": 638537728, + "peak_working_set_bytes": 684843008 + }, + { + "query": "What encryption key protects the Willow database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 373.0392, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644509696, + "peak_working_set_bytes": 684843008 + }, + { + "query": "How many production replicas run in the Willow deployment region?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75JXXRBRXJFRQH85YCQGV", + "id": "01M1X75T15FW4XQDSBBKX65SQD", + "kind": "memory", + "score": 0.9349143505096436, + "summary": "project:fact - Willow production runs in region us-west-2. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 866.9441, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 644538368, + "peak_working_set_bytes": 684843008 + }, + { + "query": "¿Qué contraseña exige el servidor de staging de Willow?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75JWNVQQDSDWVJ8N3HH7F", + "id": "01M1X75TWAXPXZ25N8EVVYNQ24", + "kind": "memory", + "score": 0.9681325554847716, + "summary": "project:fact - Willow staging HTTP listener binds TCP port 7421. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 358.98429999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 615, + "reported_used_tokens": 579, + "working_set_bytes": 644603904, + "peak_working_set_bytes": 684843008 + }, + { + "query": "¿Cuántos días se conservan las copias de seguridad de Willow?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75JY9WAP59SK69BF3E7JS", + "id": "01M1X75V7RWV4CD5094Y103E7Z", + "kind": "memory", + "score": 0.9746375679969788, + "summary": "project:fact - Willow daily database backups start at 04:15 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 367.15409999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 644657152, + "peak_working_set_bytes": 684843008 + }, + { + "query": "¿Qué versión de SQLite requiere Willow?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75JYMAB5KS5W09YYCZF9N", + "id": "01M1X75VK03X61510WSEF80G52", + "kind": "memory", + "score": 0.7611488103866577, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 363.61929999999995, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 644681728, + "peak_working_set_bytes": 684843008 + } + ], + "id": "willow-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 0.7222222222222222, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.833 (n=6) positive-n=12 negative-n=6 (18 queries)" + }, + { + "observations": [ + { + "query": "Which TCP port should I connect to for Marble staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75X0JK77HA6ASPC75RTCG", + "id": "01M1X75Z1M550X0AEJDG9S1557", + "kind": "memory", + "score": 0.9996737241744996, + "summary": "project:fact - Marble staging HTTP listener binds TCP port 8533. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1905.4247, + "first_query": true, + "server_startup_ms": 76.13109999999999, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 633688064, + "peak_working_set_bytes": 685088768 + }, + { + "query": "Where should Marble diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75X14DYRD8W9JBCR6X5QB", + "id": "01M1X75ZD83MDRF36BR60CFAR4", + "kind": "memory", + "score": 0.9971815347671508, + "summary": "project:fact - Marble diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X75X2JN17PRJWSCWFNWBK4", + "id": "01M1X75ZD8223YFNYA85A8ZG7M", + "kind": "memory", + "score": 0.6012999415397644, + "summary": "project:fact - Marble stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 352.64820000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 754, + "mcp_result_bytes": 853, + "wire_bytes": 888, + "reported_used_tokens": 853, + "working_set_bytes": 634216448, + "peak_working_set_bytes": 685088768 + }, + { + "query": "Which command rolls back Marble to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75X1G7W07TBCPS05X7Z5B", + "id": "01M1X75ZRHGD1N6X3DR6N2K18V", + "kind": "memory", + "score": 0.9997585415840148, + "summary": "project:fact - To roll back Marble to the previous release, run `marblectl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 362.99989999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 634277888, + "peak_working_set_bytes": 685088768 + }, + { + "query": "Which region hosts Marble production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75X1WC3N08WBGFPAXFMYV", + "id": "01M1X7603QRBKBHZ91FV1F12E8", + "kind": "memory", + "score": 0.999954104423523, + "summary": "project:fact - Marble production runs in region ap-south-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 365.5619, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 634499072, + "peak_working_set_bytes": 685088768 + }, + { + "query": "At what UTC time do daily Marble database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75X27PPN8PK15GERP157R", + "id": "01M1X760FKRCSVGF1VCP7BMDJ5", + "kind": "memory", + "score": 0.999979853630066, + "summary": "project:fact - Marble daily database backups start at 01:25 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 380.0497, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 587, + "reported_used_tokens": 552, + "working_set_bytes": 634638336, + "peak_working_set_bytes": 685088768 + }, + { + "query": "Which database and journal mode does Marble use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75X2JN17PRJWSCWFNWBK4", + "id": "01M1X760VKCZSRZQX15MBGZCS8", + "kind": "memory", + "score": 0.9999568462371826, + "summary": "project:fact - Marble stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 382.44620000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 634732544, + "peak_working_set_bytes": 685088768 + }, + { + "query": "¿A qué puerto TCP debo conectarme para staging de Marble?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75X0JK77HA6ASPC75RTCG", + "id": "01M1X761795ATESB6RFYW0PJJA", + "kind": "memory", + "score": 0.9998871088027954, + "summary": "project:fact - Marble staging HTTP listener binds TCP port 8533. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 481.5004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 637079552, + "peak_working_set_bytes": 685088768 + }, + { + "query": "¿Dónde deben escribirse los mensajes de diagnóstico de Marble?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75X14DYRD8W9JBCR6X5QB", + "id": "01M1X761PY7M61EAWJWKSKXA31", + "kind": "memory", + "score": 0.9992142915725708, + "summary": "project:fact - Marble diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 403.7398, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 488, + "mcp_result_bytes": 569, + "wire_bytes": 604, + "reported_used_tokens": 569, + "working_set_bytes": 637366272, + "peak_working_set_bytes": 685088768 + }, + { + "query": "¿Qué comando revierte Marble a la versión anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75X1G7W07TBCPS05X7Z5B", + "id": "01M1X7622RK36GK90Z72KG7D66", + "kind": "memory", + "score": 0.976457178592682, + "summary": "project:fact - To roll back Marble to the previous release, run `marblectl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 360.7405, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 616, + "reported_used_tokens": 580, + "working_set_bytes": 637382656, + "peak_working_set_bytes": 685088768 + }, + { + "query": "¿En qué región está desplegado Marble en producción?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75X1WC3N08WBGFPAXFMYV", + "id": "01M1X762DZHXTD73SD7R98CEZG", + "kind": "memory", + "score": 0.9999542236328124, + "summary": "project:fact - Marble production runs in region ap-south-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 361.8626, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 637411328, + "peak_working_set_bytes": 685088768 + }, + { + "query": "¿A qué hora UTC empiezan las copias diarias de la base de datos de Marble?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75X27PPN8PK15GERP157R", + "id": "01M1X762SQGF77DJW5XEWJ1VXW", + "kind": "memory", + "score": 0.9999665021896362, + "summary": "project:fact - Marble daily database backups start at 01:25 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 387.2454, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 637689856, + "peak_working_set_bytes": 685088768 + }, + { + "query": "¿Qué base de datos y modo de registro usa Marble para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75X2JN17PRJWSCWFNWBK4", + "id": "01M1X7635HG932WGX7NMAC7TYV", + "kind": "memory", + "score": 0.9953057169914246, + "summary": "project:fact - Marble stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 367.8838, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 637784064, + "peak_working_set_bytes": 685088768 + }, + { + "query": "What authentication password is configured for the Marble staging listener?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75X0JK77HA6ASPC75RTCG", + "id": "01M1X763N6JB6KH9G454TF4YPD", + "kind": "memory", + "score": 0.9787366390228271, + "summary": "project:fact - Marble staging HTTP listener binds TCP port 8533. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 515.4746, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 615, + "reported_used_tokens": 579, + "working_set_bytes": 637911040, + "peak_working_set_bytes": 685088768 + }, + { + "query": "What encryption key protects the Marble database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 395.02029999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643751936, + "peak_working_set_bytes": 685088768 + }, + { + "query": "How many production replicas run in the Marble deployment region?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75X1WC3N08WBGFPAXFMYV", + "id": "01M1X764ECGRSJXBJ4YREE37AJ", + "kind": "memory", + "score": 0.8181904554367065, + "summary": "project:fact - Marble production runs in region ap-south-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 404.31660000000005, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 643756032, + "peak_working_set_bytes": 685088768 + }, + { + "query": "¿Qué contraseña exige el servidor de staging de Marble?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75X0JK77HA6ASPC75RTCG", + "id": "01M1X764TK9K5XXMPBP54ZY6CA", + "kind": "memory", + "score": 0.8728806972503662, + "summary": "project:fact - Marble staging HTTP listener binds TCP port 8533. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 381.6885, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 615, + "reported_used_tokens": 579, + "working_set_bytes": 643813376, + "peak_working_set_bytes": 685088768 + }, + { + "query": "¿Cuántos días se conservan las copias de seguridad de Marble?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X75X27PPN8PK15GERP157R", + "id": "01M1X7657118WDZ4GX6WR5T2SR", + "kind": "memory", + "score": 0.9691649079322816, + "summary": "project:fact - Marble daily database backups start at 01:25 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 400.4255, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 643837952, + "peak_working_set_bytes": 685088768 + }, + { + "query": "¿Qué versión de SQLite requiere Marble?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 351.05589999999995, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643944448, + "peak_working_set_bytes": 685088768 + } + ], + "id": "marble-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 0.7777777777777778, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.667 (n=6) positive-n=12 negative-n=6 (18 queries)" + }, + { + "observations": [ + { + "query": "Which TCP port should I connect to for Kestrel staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X766Y6NBRMCZ9BHNQ17JA4", + "id": "01M1X768RYRGTRVCR56XBKASWF", + "kind": "memory", + "score": 0.9999393224716188, + "summary": "project:fact - Kestrel staging HTTP listener binds TCP port 9647. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1790.7, + "first_query": true, + "server_startup_ms": 72.8235, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 634667008, + "peak_working_set_bytes": 685305856 + }, + { + "query": "Where should Kestrel diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X766YRTMYZCF5PG3BT7JQH", + "id": "01M1X7694BYX6CMH14ND1S6KMS", + "kind": "memory", + "score": 0.9987480640411376, + "summary": "project:fact - Kestrel diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X76706EEE39556ANJACXAJ", + "id": "01M1X7694BH7H4WSB82ECGC1F0", + "kind": "memory", + "score": 0.9422296285629272, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 358.4332, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 756, + "mcp_result_bytes": 855, + "wire_bytes": 890, + "reported_used_tokens": 855, + "working_set_bytes": 636854272, + "peak_working_set_bytes": 685305856 + }, + { + "query": "Which command rolls back Kestrel to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X766Z4XAGDB7XSES05G89V", + "id": "01M1X769FNYVHGGJT9ZFQMGMF4", + "kind": "memory", + "score": 0.9997856020927428, + "summary": "project:fact - To roll back Kestrel to the previous release, run `kestrelctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 359.9409, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 501, + "mcp_result_bytes": 582, + "wire_bytes": 617, + "reported_used_tokens": 582, + "working_set_bytes": 637124608, + "peak_working_set_bytes": 685305856 + }, + { + "query": "Which region hosts Kestrel production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X766ZG52TK6XWWPHSMY3Z9", + "id": "01M1X769V44N1CAE0JPHS6P7SN", + "kind": "memory", + "score": 0.9999779462814332, + "summary": "project:fact - Kestrel production runs in region eu-west-3. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 371.1159, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 609, + "reported_used_tokens": 574, + "working_set_bytes": 637173760, + "peak_working_set_bytes": 685305856 + }, + { + "query": "At what UTC time do daily Kestrel database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X766ZVMDC5RTTWY6TXAVPZ", + "id": "01M1X76A723Q18XPFHM8QG78RK", + "kind": "memory", + "score": 0.999980330467224, + "summary": "project:fact - Kestrel daily database backups start at 03:50 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 381.5114, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 472, + "mcp_result_bytes": 553, + "wire_bytes": 588, + "reported_used_tokens": 553, + "working_set_bytes": 637382656, + "peak_working_set_bytes": 685305856 + }, + { + "query": "Which database and journal mode does Kestrel use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X76706EEE39556ANJACXAJ", + "id": "01M1X76AJBB8BH47W2ME65A15P", + "kind": "memory", + "score": 0.999975323677063, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 363.2204, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 637456384, + "peak_working_set_bytes": 685305856 + }, + { + "query": "¿A qué puerto TCP debo conectarme para staging de Kestrel?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X766Y6NBRMCZ9BHNQ17JA4", + "id": "01M1X76AXRA02VXFTZ0EFTZ5SX", + "kind": "memory", + "score": 0.9999423027038574, + "summary": "project:fact - Kestrel staging HTTP listener binds TCP port 9647. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 365.5343, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 639909888, + "peak_working_set_bytes": 685305856 + }, + { + "query": "¿Dónde deben escribirse los mensajes de diagnóstico de Kestrel?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X766YRTMYZCF5PG3BT7JQH", + "id": "01M1X76B96MDVMSCDMPN5JMCY2", + "kind": "memory", + "score": 0.9997678399086, + "summary": "project:fact - Kestrel diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 368.7294, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 489, + "mcp_result_bytes": 570, + "wire_bytes": 605, + "reported_used_tokens": 570, + "working_set_bytes": 640208896, + "peak_working_set_bytes": 685305856 + }, + { + "query": "¿Qué comando revierte Kestrel a la versión anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X766Z4XAGDB7XSES05G89V", + "id": "01M1X76BMMWFDGZRFK2TE1VT3Y", + "kind": "memory", + "score": 0.9766082763671876, + "summary": "project:fact - To roll back Kestrel to the previous release, run `kestrelctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 366.2494, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 501, + "mcp_result_bytes": 582, + "wire_bytes": 618, + "reported_used_tokens": 582, + "working_set_bytes": 640241664, + "peak_working_set_bytes": 685305856 + }, + { + "query": "¿En qué región está desplegado Kestrel en producción?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X766ZG52TK6XWWPHSMY3Z9", + "id": "01M1X76C07RSMVW13DBDGNFZXZ", + "kind": "memory", + "score": 0.999974250793457, + "summary": "project:fact - Kestrel production runs in region eu-west-3. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 365.05159999999995, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 640286720, + "peak_working_set_bytes": 685305856 + }, + { + "query": "¿A qué hora UTC empiezan las copias diarias de la base de datos de Kestrel?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X766ZVMDC5RTTWY6TXAVPZ", + "id": "01M1X76CBH1BNBQ53041ATSN95", + "kind": "memory", + "score": 0.9999799728393556, + "summary": "project:fact - Kestrel daily database backups start at 03:50 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 362.0205, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 472, + "mcp_result_bytes": 553, + "wire_bytes": 589, + "reported_used_tokens": 553, + "working_set_bytes": 640540672, + "peak_working_set_bytes": 685305856 + }, + { + "query": "¿Qué base de datos y modo de registro usa Kestrel para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X76706EEE39556ANJACXAJ", + "id": "01M1X76CQ24DG1KQ097BJKZ8GX", + "kind": "memory", + "score": 0.9993937015533448, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 369.45050000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 640647168, + "peak_working_set_bytes": 685305856 + }, + { + "query": "What authentication password is configured for the Kestrel staging listener?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X766Y6NBRMCZ9BHNQ17JA4", + "id": "01M1X76D2H9K9BWGHKTMKEJ177", + "kind": "memory", + "score": 0.9726881980895996, + "summary": "project:fact - Kestrel staging HTTP listener binds TCP port 9647. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 377.0689, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 616, + "reported_used_tokens": 580, + "working_set_bytes": 640659456, + "peak_working_set_bytes": 685305856 + }, + { + "query": "What encryption key protects the Kestrel database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 389.61560000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644767744, + "peak_working_set_bytes": 685305856 + }, + { + "query": "How many production replicas run in the Kestrel deployment region?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X766ZG52TK6XWWPHSMY3Z9", + "id": "01M1X76DT9CQMJ0C7APX3WVN9N", + "kind": "memory", + "score": 0.958982229232788, + "summary": "project:fact - Kestrel production runs in region eu-west-3. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 362.2417, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 644886528, + "peak_working_set_bytes": 685305856 + }, + { + "query": "¿Qué contraseña exige el servidor de staging de Kestrel?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X766Y6NBRMCZ9BHNQ17JA4", + "id": "01M1X76E5N62F63XHA1W2DW0J3", + "kind": "memory", + "score": 0.9609549045562744, + "summary": "project:fact - Kestrel staging HTTP listener binds TCP port 9647. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 360.3782, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 616, + "reported_used_tokens": 580, + "working_set_bytes": 644943872, + "peak_working_set_bytes": 685305856 + }, + { + "query": "¿Cuántos días se conservan las copias de seguridad de Kestrel?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X766ZVMDC5RTTWY6TXAVPZ", + "id": "01M1X76EGXAFM71FMSFXVBSK90", + "kind": "memory", + "score": 0.9748653173446656, + "summary": "project:fact - Kestrel daily database backups start at 03:50 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 362.4495, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 472, + "mcp_result_bytes": 553, + "wire_bytes": 589, + "reported_used_tokens": 553, + "working_set_bytes": 645029888, + "peak_working_set_bytes": 685305856 + }, + { + "query": "¿Qué versión de SQLite requiere Kestrel?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X76706EEE39556ANJACXAJ", + "id": "01M1X76EW8PHJ9MQGJFAW7JX2Z", + "kind": "memory", + "score": 0.6894522309303284, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 358.80640000000005, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 645062656, + "peak_working_set_bytes": 685305856 + } + ], + "id": "kestrel-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 0.7222222222222222, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.833 (n=6) positive-n=12 negative-n=6 (18 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 2.9444444444444446, + 4 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 0.7361111111111112, + "n": 4, + "ci95": 0.027222222222222234 + } + }, + "overall_index": 0.7361111111111112, + "scenario_weighted_index": 0.7361111111111112 +} diff --git a/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/2-candidate.stderr.log b/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/2-candidate.stderr.log new file mode 100644 index 0000000..3c8e61c --- /dev/null +++ b/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/2-candidate.stderr.log @@ -0,0 +1,10 @@ +brainbench: 4 scenario(s) to run + [1/4] copper-unseen-project | dim=retrieval tier=hard ... + -> score=1.00 | positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.000 (n=6) positive-n=12 negative-n=6 (18 queries) + [2/4] willow-unseen-project | dim=retrieval tier=hard ... + -> score=1.00 | positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.000 (n=6) positive-n=12 negative-n=6 (18 queries) + [3/4] marble-unseen-project | dim=retrieval tier=hard ... + -> score=1.00 | positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.000 (n=6) positive-n=12 negative-n=6 (18 queries) + [4/4] kestrel-unseen-project | dim=retrieval tier=hard ... + -> score=1.00 | positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.000 (n=6) positive-n=12 negative-n=6 (18 queries) +kbench brainbench: report saved -> E:\tmp\kimetsu-brain-hardening\bench\local\runs\brainbench\2026-09-07T05-58-44.351505Z.json diff --git a/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/2-candidate.stdout.log b/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/2-candidate.stdout.log new file mode 100644 index 0000000..a40eab1 --- /dev/null +++ b/docs/audits/2026-09-07-answerability/results/missing-fact-timing-followup/2-candidate.stdout.log @@ -0,0 +1,1921 @@ +{ + "generated_at": "2026-09-07T05:58:44.3510252Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-retrieval\\validation-frozen.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "Which TCP port should I connect to for Copper staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7415BCHYJ174CNYY5ESP2", + "id": "01M1X742ZVMMQE6VQ0EZJS356N", + "kind": "memory", + "score": 0.9998394250869752, + "summary": "project:fact - Copper staging HTTP listener binds TCP port 6319. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1786.1039, + "first_query": true, + "server_startup_ms": 87.55749999999999, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 632578048, + "peak_working_set_bytes": 685015040 + }, + { + "query": "Where should Copper diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7415T05YGJHNYW88V2R17", + "id": "01M1X743AZ256F8S7YFMZXT44H", + "kind": "memory", + "score": 0.9978247880935668, + "summary": "project:fact - Copper diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X74179ZM8H9FMP4MPJ5YKW", + "id": "01M1X743AZ5TKWVV6YSX2C2E9R", + "kind": "memory", + "score": 0.6390834450721741, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 345.5503, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 754, + "mcp_result_bytes": 853, + "wire_bytes": 888, + "reported_used_tokens": 853, + "working_set_bytes": 633077760, + "peak_working_set_bytes": 685015040 + }, + { + "query": "Which command rolls back Copper to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X741659TTYD471RQW9GR23", + "id": "01M1X743NVY7RGD2X0JBA7A1WP", + "kind": "memory", + "score": 0.9998852014541626, + "summary": "project:fact - To roll back Copper to the previous release, run `copperctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 355.9703, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 635113472, + "peak_working_set_bytes": 685015040 + }, + { + "query": "Which region hosts Copper production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7416HKRPXN4Y5RCXM49VK", + "id": "01M1X7440X3Q45PRNE411B5GVH", + "kind": "memory", + "score": 0.9999468326568604, + "summary": "project:fact - Copper production runs in region eu-north-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 346.21439999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 609, + "reported_used_tokens": 574, + "working_set_bytes": 635363328, + "peak_working_set_bytes": 685015040 + }, + { + "query": "At what UTC time do daily Copper database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7416XVY2RTN8BSYY9MG0R", + "id": "01M1X744C0CGNQCMWEAKBMAFKV", + "kind": "memory", + "score": 0.9999791383743286, + "summary": "project:fact - Copper daily database backups start at 02:40 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 359.9774, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 587, + "reported_used_tokens": 552, + "working_set_bytes": 635772928, + "peak_working_set_bytes": 685015040 + }, + { + "query": "Which database and journal mode does Copper use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74179ZM8H9FMP4MPJ5YKW", + "id": "01M1X744Q3S2THTJK18Q06WSAE", + "kind": "memory", + "score": 0.9999594688415528, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.07280000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 635850752, + "peak_working_set_bytes": 685015040 + }, + { + "query": "¿A qué puerto TCP debo conectarme para staging de Copper?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7415BCHYJ174CNYY5ESP2", + "id": "01M1X74525002Z7TVWB6FADKJ5", + "kind": "memory", + "score": 0.9998682737350464, + "summary": "project:fact - Copper staging HTTP listener binds TCP port 6319. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 366.4515, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 638296064, + "peak_working_set_bytes": 685015040 + }, + { + "query": "¿Dónde deben escribirse los mensajes de diagnóstico de Copper?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7415T05YGJHNYW88V2R17", + "id": "01M1X745DKJK042H8ZD0D3C4NF", + "kind": "memory", + "score": 0.9995033740997314, + "summary": "project:fact - Copper diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 361.3282, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 488, + "mcp_result_bytes": 569, + "wire_bytes": 604, + "reported_used_tokens": 569, + "working_set_bytes": 638763008, + "peak_working_set_bytes": 685015040 + }, + { + "query": "¿Qué comando revierte Copper a la versión anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X741659TTYD471RQW9GR23", + "id": "01M1X745S3G744MX5E600GGCTJ", + "kind": "memory", + "score": 0.9887914657592772, + "summary": "project:fact - To roll back Copper to the previous release, run `copperctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 371.9409, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 616, + "reported_used_tokens": 580, + "working_set_bytes": 638824448, + "peak_working_set_bytes": 685015040 + }, + { + "query": "¿En qué región está desplegado Copper en producción?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7416HKRPXN4Y5RCXM49VK", + "id": "01M1X7464Y88QSKW4SH5GESBQG", + "kind": "memory", + "score": 0.9999468326568604, + "summary": "project:fact - Copper production runs in region eu-north-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 366.9749, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 638898176, + "peak_working_set_bytes": 685015040 + }, + { + "query": "¿A qué hora UTC empiezan las copias diarias de la base de datos de Copper?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X7416XVY2RTN8BSYY9MG0R", + "id": "01M1X746G0X7W36GWE6RWP67G5", + "kind": "memory", + "score": 0.9999747276306152, + "summary": "project:fact - Copper daily database backups start at 02:40 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 358.07640000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 639193088, + "peak_working_set_bytes": 685015040 + }, + { + "query": "¿Qué base de datos y modo de registro usa Copper para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74179ZM8H9FMP4MPJ5YKW", + "id": "01M1X746V60P9773RMGEBS7V2S", + "kind": "memory", + "score": 0.9983224272727966, + "summary": "project:fact - Copper stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 357.4445, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 639213568, + "peak_working_set_bytes": 685015040 + }, + { + "query": "What authentication password is configured for the Copper staging listener?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 354.8418, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 639352832, + "peak_working_set_bytes": 685015040 + }, + { + "query": "What encryption key protects the Copper database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 362.0191, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645271552, + "peak_working_set_bytes": 685015040 + }, + { + "query": "How many production replicas run in the Copper deployment region?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 354.29519999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645337088, + "peak_working_set_bytes": 685015040 + }, + { + "query": "¿Qué contraseña exige el servidor de staging de Copper?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 363.89840000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645382144, + "peak_working_set_bytes": 685015040 + }, + { + "query": "¿Cuántos días se conservan las copias de seguridad de Copper?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 358.7629, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645451776, + "peak_working_set_bytes": 685015040 + }, + { + "query": "¿Qué versión de SQLite requiere Copper?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 370.8095, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645459968, + "peak_working_set_bytes": 685015040 + } + ], + "id": "copper-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.000 (n=6) positive-n=12 negative-n=6 (18 queries)" + }, + { + "observations": [ + { + "query": "Which TCP port should I connect to for Willow staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74AAFXP73Y67EH78NA71F", + "id": "01M1X74C45J8836A8VAPWFYSNA", + "kind": "memory", + "score": 0.9999420642852784, + "summary": "project:fact - Willow staging HTTP listener binds TCP port 7421. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1774.9456, + "first_query": true, + "server_startup_ms": 72.9686, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 634089472, + "peak_working_set_bytes": 684953600 + }, + { + "query": "Where should Willow diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74AAYHQ3QAY2636X45HKB", + "id": "01M1X74CF9PE3DCF9AQC6RQM16", + "kind": "memory", + "score": 0.9971635937690736, + "summary": "project:fact - Willow diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X74ACDYT6BP5K98X3TTYF4", + "id": "01M1X74CF9970SJVEE5B0J5Y9H", + "kind": "memory", + "score": 0.8971153497695923, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 346.551, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 754, + "mcp_result_bytes": 853, + "wire_bytes": 888, + "reported_used_tokens": 853, + "working_set_bytes": 634552320, + "peak_working_set_bytes": 684953600 + }, + { + "query": "Which command rolls back Willow to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74ABABAVG84KTNSXNRMFG", + "id": "01M1X74CTBN182PNB05AT69CZP", + "kind": "memory", + "score": 0.9998542070388794, + "summary": "project:fact - To roll back Willow to the previous release, run `willowctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 360.9593, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 636661760, + "peak_working_set_bytes": 684953600 + }, + { + "query": "Which region hosts Willow production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74ABPP0X4QYY4CYR1CNZT", + "id": "01M1X74D5AHC83Q24D3RPJQ94X", + "kind": "memory", + "score": 0.9999713897705078, + "summary": "project:fact - Willow production runs in region us-west-2. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 342.2693, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 636747776, + "peak_working_set_bytes": 684953600 + }, + { + "query": "At what UTC time do daily Willow database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74AC110P4ZQRZHH6M5RDK", + "id": "01M1X74DGF1815KSB1FKJ3MSEA", + "kind": "memory", + "score": 0.9999792575836182, + "summary": "project:fact - Willow daily database backups start at 04:15 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 363.5498, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 587, + "reported_used_tokens": 552, + "working_set_bytes": 636882944, + "peak_working_set_bytes": 684953600 + }, + { + "query": "Which database and journal mode does Willow use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74ACDYT6BP5K98X3TTYF4", + "id": "01M1X74DVWMQWCKQ0F24Z94E8X", + "kind": "memory", + "score": 0.9999637603759766, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 384.21509999999995, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 636985344, + "peak_working_set_bytes": 684953600 + }, + { + "query": "¿A qué puerto TCP debo conectarme para staging de Willow?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74AAFXP73Y67EH78NA71F", + "id": "01M1X74E7J5XPRYKWFC4CHDTYX", + "kind": "memory", + "score": 0.9999486207962036, + "summary": "project:fact - Willow staging HTTP listener binds TCP port 7421. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 359.0758, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 639369216, + "peak_working_set_bytes": 684953600 + }, + { + "query": "¿Dónde deben escribirse los mensajes de diagnóstico de Willow?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74AAYHQ3QAY2636X45HKB", + "id": "01M1X74EJRBQ4MGAYKATF2ZZ8K", + "kind": "memory", + "score": 0.9996838569641112, + "summary": "project:fact - Willow diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 360.56149999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 488, + "mcp_result_bytes": 569, + "wire_bytes": 604, + "reported_used_tokens": 569, + "working_set_bytes": 639717376, + "peak_working_set_bytes": 684953600 + }, + { + "query": "¿Qué comando revierte Willow a la versión anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74ABABAVG84KTNSXNRMFG", + "id": "01M1X74EXYK9PF14Z7BPCJ45ZY", + "kind": "memory", + "score": 0.98951655626297, + "summary": "project:fact - To roll back Willow to the previous release, run `willowctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 350.4021, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 497, + "mcp_result_bytes": 578, + "wire_bytes": 614, + "reported_used_tokens": 578, + "working_set_bytes": 639905792, + "peak_working_set_bytes": 684953600 + }, + { + "query": "¿En qué región está desplegado Willow en producción?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74ABPP0X4QYY4CYR1CNZT", + "id": "01M1X74F8ZHS7MRAZRPTJ2SGGQ", + "kind": "memory", + "score": 0.9999579191207886, + "summary": "project:fact - Willow production runs in region us-west-2. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 351.3888, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 639926272, + "peak_working_set_bytes": 684953600 + }, + { + "query": "¿A qué hora UTC empiezan las copias diarias de la base de datos de Willow?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74AC110P4ZQRZHH6M5RDK", + "id": "01M1X74FM515R2P13D2VA4BCBC", + "kind": "memory", + "score": 0.99997878074646, + "summary": "project:fact - Willow daily database backups start at 04:15 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 364.3312, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 469, + "mcp_result_bytes": 550, + "wire_bytes": 586, + "reported_used_tokens": 550, + "working_set_bytes": 640262144, + "peak_working_set_bytes": 684953600 + }, + { + "query": "¿Qué base de datos y modo de registro usa Willow para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74ACDYT6BP5K98X3TTYF4", + "id": "01M1X74FZC3ZB6WF75B9NV678Z", + "kind": "memory", + "score": 0.999204695224762, + "summary": "project:fact - Willow stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 395.9871, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 491, + "mcp_result_bytes": 572, + "wire_bytes": 608, + "reported_used_tokens": 572, + "working_set_bytes": 640331776, + "peak_working_set_bytes": 684953600 + }, + { + "query": "What authentication password is configured for the Willow staging listener?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 361.6691, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 640503808, + "peak_working_set_bytes": 684953600 + }, + { + "query": "What encryption key protects the Willow database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 363.5083, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 646361088, + "peak_working_set_bytes": 684953600 + }, + { + "query": "How many production replicas run in the Willow deployment region?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 376.35020000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 646516736, + "peak_working_set_bytes": 684953600 + }, + { + "query": "¿Qué contraseña exige el servidor de staging de Willow?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 372.259, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 646598656, + "peak_working_set_bytes": 684953600 + }, + { + "query": "¿Cuántos días se conservan las copias de seguridad de Willow?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 358.5102, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 646852608, + "peak_working_set_bytes": 684953600 + }, + { + "query": "¿Qué versión de SQLite requiere Willow?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 352.434, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 646914048, + "peak_working_set_bytes": 684953600 + } + ], + "id": "willow-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.000 (n=6) positive-n=12 negative-n=6 (18 queries)" + }, + { + "observations": [ + { + "query": "Which TCP port should I connect to for Marble staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74KGAVVH202MFDYYGDNH7", + "id": "01M1X74NABYXVFQBFS8VF19RMR", + "kind": "memory", + "score": 0.9996737241744996, + "summary": "project:fact - Marble staging HTTP listener binds TCP port 8533. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1798.0515, + "first_query": true, + "server_startup_ms": 74.2368, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 629190656, + "peak_working_set_bytes": 684789760 + }, + { + "query": "Where should Marble diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74KGVC5QW3FPTV1RDDZ1P", + "id": "01M1X74NNSGX4YMG5JQ6K6BRTP", + "kind": "memory", + "score": 0.9971815347671508, + "summary": "project:fact - Marble diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X74KJ9VG6PXF9GTMWAAVBF", + "id": "01M1X74NNT4YXP2GSHCZX446NE", + "kind": "memory", + "score": 0.6012999415397644, + "summary": "project:fact - Marble stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 354.2907, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 754, + "mcp_result_bytes": 853, + "wire_bytes": 888, + "reported_used_tokens": 853, + "working_set_bytes": 629678080, + "peak_working_set_bytes": 684789760 + }, + { + "query": "Which command rolls back Marble to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74KH6F8EC1RYSZFF50DPF", + "id": "01M1X74P0ZPSC38GWNA6JY52RX", + "kind": "memory", + "score": 0.9997585415840148, + "summary": "project:fact - To roll back Marble to the previous release, run `marblectl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 389.2509, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 632147968, + "peak_working_set_bytes": 684789760 + }, + { + "query": "Which region hosts Marble production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74KHJ5C1KZ61SCS4G982K", + "id": "01M1X74PCQMQG6BTFHZ68316MJ", + "kind": "memory", + "score": 0.999954104423523, + "summary": "project:fact - Marble production runs in region ap-south-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 346.4203, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 632340480, + "peak_working_set_bytes": 684789760 + }, + { + "query": "At what UTC time do daily Marble database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74KHXBV6MQ4J0QWN0DE2Q", + "id": "01M1X74PQNH3YXZSW2STQKSW90", + "kind": "memory", + "score": 0.999979853630066, + "summary": "project:fact - Marble daily database backups start at 01:25 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 353.64709999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 587, + "reported_used_tokens": 552, + "working_set_bytes": 632512512, + "peak_working_set_bytes": 684789760 + }, + { + "query": "Which database and journal mode does Marble use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74KJ9VG6PXF9GTMWAAVBF", + "id": "01M1X74Q40H3HFSJ36YQC6M0Y5", + "kind": "memory", + "score": 0.9999568462371826, + "summary": "project:fact - Marble stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 407.0996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 632619008, + "peak_working_set_bytes": 684789760 + }, + { + "query": "¿A qué puerto TCP debo conectarme para staging de Marble?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74KGAVVH202MFDYYGDNH7", + "id": "01M1X74QGASZ4ZJZCPGFZBWKP3", + "kind": "memory", + "score": 0.9998871088027954, + "summary": "project:fact - Marble staging HTTP listener binds TCP port 8533. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 392.1516, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 498, + "mcp_result_bytes": 579, + "wire_bytes": 614, + "reported_used_tokens": 579, + "working_set_bytes": 634892288, + "peak_working_set_bytes": 684789760 + }, + { + "query": "¿Dónde deben escribirse los mensajes de diagnóstico de Marble?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74KGVC5QW3FPTV1RDDZ1P", + "id": "01M1X74QWDHZEXW3T5H11E0HVD", + "kind": "memory", + "score": 0.9992142915725708, + "summary": "project:fact - Marble diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 389.017, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 488, + "mcp_result_bytes": 569, + "wire_bytes": 604, + "reported_used_tokens": 569, + "working_set_bytes": 635305984, + "peak_working_set_bytes": 684789760 + }, + { + "query": "¿Qué comando revierte Marble a la versión anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74KH6F8EC1RYSZFF50DPF", + "id": "01M1X74R7WKKGZZQM1DDJCKMEF", + "kind": "memory", + "score": 0.976457178592682, + "summary": "project:fact - To roll back Marble to the previous release, run `marblectl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.88239999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 616, + "reported_used_tokens": 580, + "working_set_bytes": 635351040, + "peak_working_set_bytes": 684789760 + }, + { + "query": "¿En qué región está desplegado Marble en producción?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74KHJ5C1KZ61SCS4G982K", + "id": "01M1X74RK9N4ARHK3232KT21DX", + "kind": "memory", + "score": 0.9999542236328124, + "summary": "project:fact - Marble production runs in region ap-south-1. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 362.1453, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 635432960, + "peak_working_set_bytes": 684789760 + }, + { + "query": "¿A qué hora UTC empiezan las copias diarias de la base de datos de Marble?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74KHXBV6MQ4J0QWN0DE2Q", + "id": "01M1X74RYF0RE6RFNA78ZGDHQ9", + "kind": "memory", + "score": 0.9999665021896362, + "summary": "project:fact - Marble daily database backups start at 01:25 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 369.9992, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 471, + "mcp_result_bytes": 552, + "wire_bytes": 588, + "reported_used_tokens": 552, + "working_set_bytes": 635744256, + "peak_working_set_bytes": 684789760 + }, + { + "query": "¿Qué base de datos y modo de registro usa Marble para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74KJ9VG6PXF9GTMWAAVBF", + "id": "01M1X74SA9SCYZJAEXAVKTEKHZ", + "kind": "memory", + "score": 0.9953057169914246, + "summary": "project:fact - Marble stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 383.40180000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 635809792, + "peak_working_set_bytes": 684789760 + }, + { + "query": "What authentication password is configured for the Marble staging listener?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 369.97999999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 635899904, + "peak_working_set_bytes": 684789760 + }, + { + "query": "What encryption key protects the Marble database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 353.71029999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 641761280, + "peak_working_set_bytes": 684789760 + }, + { + "query": "How many production replicas run in the Marble deployment region?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 356.7172, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 641781760, + "peak_working_set_bytes": 684789760 + }, + { + "query": "¿Qué contraseña exige el servidor de staging de Marble?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 356.9958, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 641904640, + "peak_working_set_bytes": 684789760 + }, + { + "query": "¿Cuántos días se conservan las copias de seguridad de Marble?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 367.3115, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 642134016, + "peak_working_set_bytes": 684789760 + }, + { + "query": "¿Qué versión de SQLite requiere Marble?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 352.53679999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 642207744, + "peak_working_set_bytes": 684789760 + } + ], + "id": "marble-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.000 (n=6) positive-n=12 negative-n=6 (18 queries)" + }, + { + "observations": [ + { + "query": "Which TCP port should I connect to for Kestrel staging?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74WT3G9E8X615FY1VQF2F", + "id": "01M1X74YN1MM2R48HPDYS8MMXA", + "kind": "memory", + "score": 0.9999393224716188, + "summary": "project:fact - Kestrel staging HTTP listener binds TCP port 9647. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 1799.1631, + "first_query": true, + "server_startup_ms": 87.9836, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 634990592, + "peak_working_set_bytes": 685191168 + }, + { + "query": "Where should Kestrel diagnostic logs be written?", + "ranked": [ + "logs", + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74WTMDHA14XF6ZJP0SZ6A", + "id": "01M1X74Z05N8TCMQ2X5T4T7S5N", + "kind": "memory", + "score": 0.9987480640411376, + "summary": "project:fact - Kestrel diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + }, + { + "expansion_handle": "memory:01M1X74WW4RWSB49A09HKGNN8F", + "id": "01M1X74Z05D0SS9PWPY0ZC1967", + "kind": "memory", + "score": 0.9422296285629272, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 348.8137, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 756, + "mcp_result_bytes": 855, + "wire_bytes": 890, + "reported_used_tokens": 855, + "working_set_bytes": 637157376, + "peak_working_set_bytes": 685191168 + }, + { + "query": "Which command rolls back Kestrel to its previous release?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74WV0WHHXHYF9K582YARS", + "id": "01M1X74ZB416TPEYMPYKKC8F1G", + "kind": "memory", + "score": 0.9997856020927428, + "summary": "project:fact - To roll back Kestrel to the previous release, run `kestrelctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.0393, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 501, + "mcp_result_bytes": 582, + "wire_bytes": 617, + "reported_used_tokens": 582, + "working_set_bytes": 639414272, + "peak_working_set_bytes": 685191168 + }, + { + "query": "Which region hosts Kestrel production?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74WVCSS6SA5FTTNZRRWT9", + "id": "01M1X74ZP5RFMJ4XTRA2ZKC06R", + "kind": "memory", + "score": 0.9999779462814332, + "summary": "project:fact - Kestrel production runs in region eu-west-3. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 346.9871, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 609, + "reported_used_tokens": 574, + "working_set_bytes": 639672320, + "peak_working_set_bytes": 685191168 + }, + { + "query": "At what UTC time do daily Kestrel database backups start?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74WVS4D1527GT7WDEA13K", + "id": "01M1X7501922Q3WDK6AGBR8C58", + "kind": "memory", + "score": 0.999980330467224, + "summary": "project:fact - Kestrel daily database backups start at 03:50 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 362.404, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 472, + "mcp_result_bytes": 553, + "wire_bytes": 588, + "reported_used_tokens": 553, + "working_set_bytes": 640102400, + "peak_working_set_bytes": 685191168 + }, + { + "query": "Which database and journal mode does Kestrel use for local state?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74WW4RWSB49A09HKGNN8F", + "id": "01M1X750CF7Q5G0HVQ91F0Q5JQ", + "kind": "memory", + "score": 0.999975323677063, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 358.2096, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 608, + "reported_used_tokens": 573, + "working_set_bytes": 640221184, + "peak_working_set_bytes": 685191168 + }, + { + "query": "¿A qué puerto TCP debo conectarme para staging de Kestrel?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74WT3G9E8X615FY1VQF2F", + "id": "01M1X750QPH89W9W2A5KXXJSB2", + "kind": "memory", + "score": 0.9999423027038574, + "summary": "project:fact - Kestrel staging HTTP listener binds TCP port 9647. This is the current configured port for its staging service." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": false, + "latency_ms": 369.2017, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 499, + "mcp_result_bytes": 580, + "wire_bytes": 615, + "reported_used_tokens": 580, + "working_set_bytes": 642719744, + "peak_working_set_bytes": 685191168 + }, + { + "query": "¿Dónde deben escribirse los mensajes de diagnóstico de Kestrel?", + "ranked": [ + "logs" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74WTMDHA14XF6ZJP0SZ6A", + "id": "01M1X7513BFC7BRHFCVFD3AS9J", + "kind": "memory", + "score": 0.9997678399086, + "summary": "project:fact - Kestrel diagnostic messages are written to stderr. Stdout is reserved for JSON-RPC protocol messages." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 370.25590000000005, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 489, + "mcp_result_bytes": 570, + "wire_bytes": 605, + "reported_used_tokens": 570, + "working_set_bytes": 642830336, + "peak_working_set_bytes": 685191168 + }, + { + "query": "¿Qué comando revierte Kestrel a la versión anterior?", + "ranked": [ + "rollback" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74WV0WHHXHYF9K582YARS", + "id": "01M1X751F0H9MKZG03M2W97Q88", + "kind": "memory", + "score": 0.9766082763671876, + "summary": "project:fact - To roll back Kestrel to the previous release, run `kestrelctl rollback --previous` from the deployment workspace." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 384.2406, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 501, + "mcp_result_bytes": 582, + "wire_bytes": 618, + "reported_used_tokens": 582, + "working_set_bytes": 642985984, + "peak_working_set_bytes": 685191168 + }, + { + "query": "¿En qué región está desplegado Kestrel en producción?", + "ranked": [ + "region" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74WVCSS6SA5FTTNZRRWT9", + "id": "01M1X751TRPXKT395TGB0BXATF", + "kind": "memory", + "score": 0.999974250793457, + "summary": "project:fact - Kestrel production runs in region eu-west-3. Its deployment configuration selects this region explicitly." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 357.7899, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 492, + "mcp_result_bytes": 573, + "wire_bytes": 609, + "reported_used_tokens": 573, + "working_set_bytes": 643039232, + "peak_working_set_bytes": 685191168 + }, + { + "query": "¿A qué hora UTC empiezan las copias diarias de la base de datos de Kestrel?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74WVS4D1527GT7WDEA13K", + "id": "01M1X7525Y9WT6EBJ6WBMB65P5", + "kind": "memory", + "score": 0.9999799728393556, + "summary": "project:fact - Kestrel daily database backups start at 03:50 UTC. The backup job runs once per day." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.3876, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 472, + "mcp_result_bytes": 553, + "wire_bytes": 589, + "reported_used_tokens": 553, + "working_set_bytes": 643321856, + "peak_working_set_bytes": 685191168 + }, + { + "query": "¿Qué base de datos y modo de registro usa Kestrel para guardar el estado local?", + "ranked": [ + "storage" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X74WW4RWSB49A09HKGNN8F", + "id": "01M1X752H5VX2RWE9BAHMV4H10", + "kind": "memory", + "score": 0.9993937015533448, + "summary": "project:fact - Kestrel stores its local state in SQLite with write-ahead logging enabled. Use WAL mode for its database." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 362.36249999999995, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 493, + "mcp_result_bytes": 574, + "wire_bytes": 610, + "reported_used_tokens": 574, + "working_set_bytes": 643420160, + "peak_working_set_bytes": 685191168 + }, + { + "query": "What authentication password is configured for the Kestrel staging listener?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 358.2715, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643592192, + "peak_working_set_bytes": 685191168 + }, + { + "query": "What encryption key protects the Kestrel database backups?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 364.0852, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 647688192, + "peak_working_set_bytes": 685191168 + }, + { + "query": "How many production replicas run in the Kestrel deployment region?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 357.2308, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 647901184, + "peak_working_set_bytes": 685191168 + }, + { + "query": "¿Qué contraseña exige el servidor de staging de Kestrel?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 371.31399999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 647966720, + "peak_working_set_bytes": 685191168 + }, + { + "query": "¿Cuántos días se conservan las copias de seguridad de Kestrel?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 370.6778, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 648126464, + "peak_working_set_bytes": 685191168 + }, + { + "query": "¿Qué versión de SQLite requiere Kestrel?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 377.43080000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 648179712, + "peak_working_set_bytes": 685191168 + } + ], + "id": "kestrel-unseen-project", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=0.000 (n=2) resolution=1.000 (n=2) false-injection=0.000 (n=6) positive-n=12 negative-n=6 (18 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 4.0, + 4 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 1.0, + "n": 4, + "ci95": 0.0 + } + }, + "overall_index": 1.0, + "scenario_weighted_index": 1.0 +} diff --git a/docs/audits/2026-09-07-answerability/results/validation/1-baseline.stderr.log b/docs/audits/2026-09-07-answerability/results/validation/1-baseline.stderr.log new file mode 100644 index 0000000..f541b2e --- /dev/null +++ b/docs/audits/2026-09-07-answerability/results/validation/1-baseline.stderr.log @@ -0,0 +1,6 @@ +brainbench: 2 scenario(s) to run + [1/2] orchid-answerability-frozen | dim=retrieval tier=hard ... + -> score=0.73 | positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.600 (n=10) positive-n=12 negative-n=10 (22 queries) + [2/2] quartz-answerability-frozen | dim=retrieval tier=hard ... + -> score=0.73 | positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.600 (n=10) positive-n=12 negative-n=10 (22 queries) +kbench brainbench: report saved -> E:\tmp\kimetsu-brain-hardening\bench\local\runs\brainbench\2026-09-07T05-55-24.9741256Z.json diff --git a/docs/audits/2026-09-07-answerability/results/validation/1-baseline.stdout.log b/docs/audits/2026-09-07-answerability/results/validation/1-baseline.stdout.log new file mode 100644 index 0000000..4d8b4fb --- /dev/null +++ b/docs/audits/2026-09-07-answerability/results/validation/1-baseline.stdout.log @@ -0,0 +1,1241 @@ +{ + "generated_at": "2026-09-07T05:55:24.9738733Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-answerability\\validation-frozen.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "What is the Orchid gateway timeout?", + "ranked": [ + "timeout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YDS6AM46ADZA20HCA3N7", + "id": "01M1X6YFKQJ4X437CDB542ZS2S", + "kind": "memory", + "score": 0.999908208847046, + "summary": "project:fact - Orchid gateway timeout is 45 seconds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1784.9897, + "first_query": true, + "server_startup_ms": 73.9532, + "model_text_bytes": 426, + "mcp_result_bytes": 507, + "wire_bytes": 542, + "reported_used_tokens": 507, + "working_set_bytes": 634707968, + "peak_working_set_bytes": 684695552 + }, + { + "query": "How many retries does the Orchid client use?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YDSPXE2KBVGTTFZPDRYZ", + "id": "01M1X6YFZD5FJ4WJAT50D6E5RR", + "kind": "memory", + "score": 0.9976400136947632, + "summary": "project:fact - Orchid client retry count is 5." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 365.2181, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 420, + "mcp_result_bytes": 501, + "wire_bytes": 536, + "reported_used_tokens": 501, + "working_set_bytes": 636993536, + "peak_working_set_bytes": 684695552 + }, + { + "query": "What is the Orchid worker memory limit?", + "ranked": [ + "memory" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YDSYVMWAYEKR8WC3A4CV", + "id": "01M1X6YGAT8X08SYBMZV5Z1S7K", + "kind": "memory", + "score": 0.9999785423278807, + "summary": "project:fact - Orchid worker memory limit is 768 MiB." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 359.4508, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 543, + "reported_used_tokens": 508, + "working_set_bytes": 642109440, + "peak_working_set_bytes": 684695552 + }, + { + "query": "What version does the Orchid worker run?", + "ranked": [ + "version" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YDT8N1XW0HMY7EPRHDAM", + "id": "01M1X6YGNNEPAMWHGE6Y0W7MX0", + "kind": "memory", + "score": 0.9998592138290404, + "summary": "project:fact - Orchid worker version 8.2 is installed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 348.2773, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 543, + "reported_used_tokens": 508, + "working_set_bytes": 642129920, + "peak_working_set_bytes": 684695552 + }, + { + "query": "What is `storage.page_bytes` in Orchid?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YDTKXBSR6WHV1VEVKQ92", + "id": "01M1X6YH0JB7ZV8J0AKZ5BX7TE", + "kind": "memory", + "score": 0.9999752044677734, + "summary": "project:fact - Orchid storage.page_bytes = 8192." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 358.5264, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 422, + "mcp_result_bytes": 503, + "wire_bytes": 538, + "reported_used_tokens": 503, + "working_set_bytes": 642551808, + "peak_working_set_bytes": 684695552 + }, + { + "query": "What password does the Orchid gateway require?", + "ranked": [ + "password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YDTWGPBD1DYZ2CN4XGWB", + "id": "01M1X6YHBY3NFCERWP7P1D81EQ", + "kind": "memory", + "score": 0.999871015548706, + "summary": "project:fact - No password is required for the Orchid gateway." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 358.1663, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 435, + "mcp_result_bytes": 516, + "wire_bytes": 551, + "reported_used_tokens": 516, + "working_set_bytes": 642576384, + "peak_working_set_bytes": 684695552 + }, + { + "query": "How long are Orchid backups retained?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YDV5N0NSTYAN792AB1CP", + "id": "01M1X6YHQ4488DJS38CTP3PPDH", + "kind": "memory", + "score": 0.9999786615371704, + "summary": "project:fact - Orchid backups are retained for 36 hours." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 353.8764, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 429, + "mcp_result_bytes": 510, + "wire_bytes": 545, + "reported_used_tokens": 510, + "working_set_bytes": 642613248, + "peak_working_set_bytes": 684695552 + }, + { + "query": "Which files configure the port for Orchid?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YDVEMNRTMRDVCEYDRC1B", + "id": "01M1X6YJ21Q2Q5M11QJBAZJF8Y", + "kind": "memory", + "score": 0.9955846667289734, + "summary": "project:fact - Orchid listener binds TCP port 7321. Configure its port in listener.toml." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 355.8541, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 462, + "mcp_result_bytes": 543, + "wire_bytes": 578, + "reported_used_tokens": 543, + "working_set_bytes": 642633728, + "peak_working_set_bytes": 684695552 + }, + { + "query": "What causes a version conflict in Orchid?", + "ranked": [ + "advice" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YDVT5V9PY7APWPRTZM1Z", + "id": "01M1X6YJD20YXX0Y87VBNXVXMK", + "kind": "memory", + "score": 0.9998210072517396, + "summary": "project:fact - Orchid version conflicts occur when lockfiles disagree. Regenerate the lockfile and check dependency constraints." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 369.6207, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 501, + "mcp_result_bytes": 582, + "wire_bytes": 618, + "reported_used_tokens": 582, + "working_set_bytes": 642654208, + "peak_working_set_bytes": 684695552 + }, + { + "query": "¿Qué versión usa el worker de Orchid?", + "ranked": [ + "version" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YDT8N1XW0HMY7EPRHDAM", + "id": "01M1X6YJRN1Y2PW82GX89YMMMT", + "kind": "memory", + "score": 0.999970316886902, + "summary": "project:fact - Orchid worker version 8.2 is installed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 350.5338, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 544, + "reported_used_tokens": 508, + "working_set_bytes": 642723840, + "peak_working_set_bytes": 684695552 + }, + { + "query": "¿Cuánto tiempo se conservan las copias de seguridad de Orchid?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YDV5N0NSTYAN792AB1CP", + "id": "01M1X6YK43NWETW90N0DG5YJ3J", + "kind": "memory", + "score": 0.9995601773262024, + "summary": "project:fact - Orchid backups are retained for 36 hours." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 377.40439999999995, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 429, + "mcp_result_bytes": 510, + "wire_bytes": 546, + "reported_used_tokens": 510, + "working_set_bytes": 643182592, + "peak_working_set_bytes": 684695552 + }, + { + "query": "What is the timeout in seconds for the gateway in Orchid?", + "ranked": [ + "timeout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YDS6AM46ADZA20HCA3N7", + "id": "01M1X6YKG2EBKMJ6G9DHEAE7D0", + "kind": "memory", + "score": 0.9999822378158568, + "summary": "project:fact - Orchid gateway timeout is 45 seconds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 374.3332, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 426, + "mcp_result_bytes": 507, + "wire_bytes": 543, + "reported_used_tokens": 507, + "working_set_bytes": 643219456, + "peak_working_set_bytes": 684695552 + }, + { + "query": "What is the database timeout for Orchid?", + "ranked": [ + "timeout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YDS6AM46ADZA20HCA3N7", + "id": "01M1X6YKV41NABVJSVM3B2D85M", + "kind": "memory", + "score": 0.969832181930542, + "summary": "project:fact - Orchid gateway timeout is 45 seconds." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 348.1424, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 424, + "mcp_result_bytes": 505, + "wire_bytes": 541, + "reported_used_tokens": 505, + "working_set_bytes": 643235840, + "peak_working_set_bytes": 684695552 + }, + { + "query": "What encryption key does Orchid use?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 349.8092, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643244032, + "peak_working_set_bytes": 684695552 + }, + { + "query": "What is `storage.cache_bytes` in Orchid?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YDTKXBSR6WHV1VEVKQ92", + "id": "01M1X6YMH4ER27R12PV5VF5PA6", + "kind": "memory", + "score": 0.9991620779037476, + "summary": "project:fact - Orchid storage.page_bytes = 8192." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 364.9801, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 421, + "mcp_result_bytes": 502, + "wire_bytes": 538, + "reported_used_tokens": 502, + "working_set_bytes": 643248128, + "peak_working_set_bytes": 684695552 + }, + { + "query": "How many production replicas does Orchid run?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 374.0399, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643260416, + "peak_working_set_bytes": 684695552 + }, + { + "query": "What is the OpenSSL version for Orchid?", + "ranked": [ + "version" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YDT8N1XW0HMY7EPRHDAM", + "id": "01M1X6YN874S0AGNTPM3ZJR071", + "kind": "memory", + "score": 0.7756274342536926, + "summary": "project:fact - Orchid worker version 8.2 is installed." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 369.2296, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 544, + "reported_used_tokens": 508, + "working_set_bytes": 643264512, + "peak_working_set_bytes": 684695552 + }, + { + "query": "What is the database password for Orchid?", + "ranked": [ + "password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YDTWGPBD1DYZ2CN4XGWB", + "id": "01M1X6YNKM0JC8QNZKE6HSAB16", + "kind": "memory", + "score": 0.6111225485801697, + "summary": "project:fact - No password is required for the Orchid gateway." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 351.32039999999995, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 435, + "mcp_result_bytes": 516, + "wire_bytes": 552, + "reported_used_tokens": 516, + "working_set_bytes": 643330048, + "peak_working_set_bytes": 684695552 + }, + { + "query": "How long are logs retained for Orchid?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YDV5N0NSTYAN792AB1CP", + "id": "01M1X6YNYQCK6JR2HZHABC6SSH", + "kind": "memory", + "score": 0.9884384274482728, + "summary": "project:fact - Orchid backups are retained for 36 hours." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 363.88890000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 429, + "mcp_result_bytes": 510, + "wire_bytes": 546, + "reported_used_tokens": 510, + "working_set_bytes": 643416064, + "peak_working_set_bytes": 684695552 + }, + { + "query": "¿Qué contraseña usa la base de datos de Orchid?", + "ranked": [ + "password", + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YDTWGPBD1DYZ2CN4XGWB", + "id": "01M1X6YPA6438KH3FXYK0GQTTY", + "kind": "memory", + "score": 0.8813819885253906, + "summary": "project:fact - No password is required for the Orchid gateway." + }, + { + "expansion_handle": "memory:01M1X6YDVEMNRTMRDVCEYDRC1B", + "id": "01M1X6YPA64W26MGG7PDFKGDE3", + "kind": "memory", + "score": 0.8048929572105408, + "summary": "project:fact - Orchid listener binds TCP port 7321. Configure its port in listener.toml." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 379.0547, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 670, + "mcp_result_bytes": 769, + "wire_bytes": 805, + "reported_used_tokens": 769, + "working_set_bytes": 643489792, + "peak_working_set_bytes": 684695552 + }, + { + "query": "¿Cuántas replicas de producción tiene Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 396.481, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643555328, + "peak_working_set_bytes": 684695552 + }, + { + "query": "Which region hosts Orchid production?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 682.9003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643670016, + "peak_working_set_bytes": 684695552 + } + ], + "id": "orchid-answerability-frozen", + "dimension": "retrieval", + "tier": "hard", + "score": 0.7272727272727273, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.600 (n=10) positive-n=12 negative-n=10 (22 queries)" + }, + { + "observations": [ + { + "query": "What is the Quartz gateway timeout?", + "ranked": [ + "timeout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YRW0EC8XYXJDJBVJCNX6", + "id": "01M1X6YTP6GFT865MD8XX1YBN7", + "kind": "memory", + "score": 0.9999661445617676, + "summary": "project:fact - Quartz gateway timeout is 45 seconds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1776.3401999999999, + "first_query": true, + "server_startup_ms": 72.87599999999999, + "model_text_bytes": 426, + "mcp_result_bytes": 507, + "wire_bytes": 542, + "reported_used_tokens": 507, + "working_set_bytes": 630677504, + "peak_working_set_bytes": 684937216 + }, + { + "query": "How many retries does the Quartz client use?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YRWEP5V7T2DRR5W191RY", + "id": "01M1X6YV16TQTXB1KFMFAPHGBA", + "kind": "memory", + "score": 0.9913354516029358, + "summary": "project:fact - Quartz client retry count is 5." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 350.932, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 420, + "mcp_result_bytes": 501, + "wire_bytes": 536, + "reported_used_tokens": 501, + "working_set_bytes": 630988800, + "peak_working_set_bytes": 684937216 + }, + { + "query": "What is the Quartz worker memory limit?", + "ranked": [ + "memory" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YRWQYM61QB54QCXY5M8S", + "id": "01M1X6YVCDBJHFR3V4CF38Q9DP", + "kind": "memory", + "score": 0.9999793767929076, + "summary": "project:fact - Quartz worker memory limit is 768 MiB." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 352.15049999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 543, + "reported_used_tokens": 508, + "working_set_bytes": 635928576, + "peak_working_set_bytes": 684937216 + }, + { + "query": "What version does the Quartz worker run?", + "ranked": [ + "version" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YRX0N2VZWG624EM98933", + "id": "01M1X6YVQBH48NYRBWS2GCWHCE", + "kind": "memory", + "score": 0.9998953342437744, + "summary": "project:fact - Quartz worker version 8.2 is installed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 355.2699, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 543, + "reported_used_tokens": 508, + "working_set_bytes": 636006400, + "peak_working_set_bytes": 684937216 + }, + { + "query": "What is `storage.page_bytes` in Quartz?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YRX9144J4G8J165CF3WK", + "id": "01M1X6YW30JX5Q1TAZZHQA4RQA", + "kind": "memory", + "score": 0.9999722242355348, + "summary": "project:fact - Quartz storage.page_bytes = 8192." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 374.8146, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 422, + "mcp_result_bytes": 503, + "wire_bytes": 538, + "reported_used_tokens": 503, + "working_set_bytes": 638234624, + "peak_working_set_bytes": 684937216 + }, + { + "query": "What password does the Quartz gateway require?", + "ranked": [ + "password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YRXJ8E92QTCNBSVRM7RQ", + "id": "01M1X6YWDZNFG1H7C4T71J6HD1", + "kind": "memory", + "score": 0.9998206496238708, + "summary": "project:fact - No password is required for the Quartz gateway." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 351.1381, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 436, + "mcp_result_bytes": 517, + "wire_bytes": 552, + "reported_used_tokens": 517, + "working_set_bytes": 638275584, + "peak_working_set_bytes": 684937216 + }, + { + "query": "How long are Quartz backups retained?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YRXWS06KM9HEA391XTW1", + "id": "01M1X6YWS1YRP5SRBBQVBXHFMM", + "kind": "memory", + "score": 0.999981164932251, + "summary": "project:fact - Quartz backups are retained for 36 hours." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 346.8805, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 428, + "mcp_result_bytes": 509, + "wire_bytes": 544, + "reported_used_tokens": 509, + "working_set_bytes": 638300160, + "peak_working_set_bytes": 684937216 + }, + { + "query": "Which files configure the port for Quartz?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YRY6PW9FEZA7EK8NW7B2", + "id": "01M1X6YX3XJ56PPBFMPSXGC6H7", + "kind": "memory", + "score": 0.9969274401664734, + "summary": "project:fact - Quartz listener binds TCP port 8452. Configure its port in listener.toml." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 349.4505, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 462, + "mcp_result_bytes": 543, + "wire_bytes": 578, + "reported_used_tokens": 543, + "working_set_bytes": 638337024, + "peak_working_set_bytes": 684937216 + }, + { + "query": "What causes a version conflict in Quartz?", + "ranked": [ + "advice" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YRYJJ4SGYR06FREHSYQP", + "id": "01M1X6YXESJ7CG1XRMA1Z2AM7Q", + "kind": "memory", + "score": 0.9998512268066406, + "summary": "project:fact - Quartz version conflicts occur when lockfiles disagree. Regenerate the lockfile and check dependency constraints." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 357.88160000000005, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 501, + "mcp_result_bytes": 582, + "wire_bytes": 618, + "reported_used_tokens": 582, + "working_set_bytes": 638468096, + "peak_working_set_bytes": 684937216 + }, + { + "query": "¿Qué versión usa el worker de Quartz?", + "ranked": [ + "version" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YRX0N2VZWG624EM98933", + "id": "01M1X6YXSX9NJ1A4F1Y2Q30CVQ", + "kind": "memory", + "score": 0.9999598264694214, + "summary": "project:fact - Quartz worker version 8.2 is installed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 349.2778, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 544, + "reported_used_tokens": 508, + "working_set_bytes": 638562304, + "peak_working_set_bytes": 684937216 + }, + { + "query": "¿Cuánto tiempo se conservan las copias de seguridad de Quartz?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YRXWS06KM9HEA391XTW1", + "id": "01M1X6YY4XTGDFXBEJJ29M6QDW", + "kind": "memory", + "score": 0.9996949434280396, + "summary": "project:fact - Quartz backups are retained for 36 hours." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 353.0143, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 429, + "mcp_result_bytes": 510, + "wire_bytes": 546, + "reported_used_tokens": 510, + "working_set_bytes": 638988288, + "peak_working_set_bytes": 684937216 + }, + { + "query": "What is the timeout in seconds for the gateway in Quartz?", + "ranked": [ + "timeout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YRW0EC8XYXJDJBVJCNX6", + "id": "01M1X6YYG92D4YHB9NM73ADAFD", + "kind": "memory", + "score": 0.9999818801879884, + "summary": "project:fact - Quartz gateway timeout is 45 seconds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 363.98190000000005, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 426, + "mcp_result_bytes": 507, + "wire_bytes": 543, + "reported_used_tokens": 507, + "working_set_bytes": 639008768, + "peak_working_set_bytes": 684937216 + }, + { + "query": "What is the database timeout for Quartz?", + "ranked": [ + "timeout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YRW0EC8XYXJDJBVJCNX6", + "id": "01M1X6YYVF4SHBSEKPK7NNBCEH", + "kind": "memory", + "score": 0.9865899085998536, + "summary": "project:fact - Quartz gateway timeout is 45 seconds." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 365.71340000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 425, + "mcp_result_bytes": 506, + "wire_bytes": 542, + "reported_used_tokens": 506, + "working_set_bytes": 639062016, + "peak_working_set_bytes": 684937216 + }, + { + "query": "What encryption key does Quartz use?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 373.8856, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 639082496, + "peak_working_set_bytes": 684937216 + }, + { + "query": "What is `storage.cache_bytes` in Quartz?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YRX9144J4G8J165CF3WK", + "id": "01M1X6YZJFNMY4TQRY724AHQR8", + "kind": "memory", + "score": 0.998980700969696, + "summary": "project:fact - Quartz storage.page_bytes = 8192." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 353.7404, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 420, + "mcp_result_bytes": 501, + "wire_bytes": 537, + "reported_used_tokens": 501, + "working_set_bytes": 639082496, + "peak_working_set_bytes": 684937216 + }, + { + "query": "How many production replicas does Quartz run?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 349.1059, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 639143936, + "peak_working_set_bytes": 684937216 + }, + { + "query": "What is the OpenSSL version for Quartz?", + "ranked": [ + "version" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YRX0N2VZWG624EM98933", + "id": "01M1X6Z08FFM5T6EDF1RT3SE3X", + "kind": "memory", + "score": 0.7910260558128357, + "summary": "project:fact - Quartz worker version 8.2 is installed." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 356.2742, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 544, + "reported_used_tokens": 508, + "working_set_bytes": 639148032, + "peak_working_set_bytes": 684937216 + }, + { + "query": "What is the database password for Quartz?", + "ranked": [ + "password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YRXJ8E92QTCNBSVRM7RQ", + "id": "01M1X6Z0KDY5CADXF1A7GKK0Z1", + "kind": "memory", + "score": 0.5618340969085693, + "summary": "project:fact - No password is required for the Quartz gateway." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 346.1132, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 435, + "mcp_result_bytes": 516, + "wire_bytes": 552, + "reported_used_tokens": 516, + "working_set_bytes": 639148032, + "peak_working_set_bytes": 684937216 + }, + { + "query": "How long are logs retained for Quartz?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YRXWS06KM9HEA391XTW1", + "id": "01M1X6Z0YFD0R9SQGMGTM4YYVM", + "kind": "memory", + "score": 0.9967792630195618, + "summary": "project:fact - Quartz backups are retained for 36 hours." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 352.7833, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 429, + "mcp_result_bytes": 510, + "wire_bytes": 546, + "reported_used_tokens": 510, + "working_set_bytes": 639160320, + "peak_working_set_bytes": 684937216 + }, + { + "query": "¿Qué contraseña usa la base de datos de Quartz?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6YRY6PW9FEZA7EK8NW7B2", + "id": "01M1X6Z19EXZRA3MPY84DWRXAK", + "kind": "memory", + "score": 0.8378869891166687, + "summary": "project:fact - Quartz listener binds TCP port 8452. Configure its port in listener.toml." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 353.2962, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 461, + "mcp_result_bytes": 542, + "wire_bytes": 578, + "reported_used_tokens": 542, + "working_set_bytes": 639213568, + "peak_working_set_bytes": 684937216 + }, + { + "query": "¿Cuántas replicas de producción tiene Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 363.99350000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 639266816, + "peak_working_set_bytes": 684937216 + }, + { + "query": "Which region hosts Quartz production?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 348.8584, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 639365120, + "peak_working_set_bytes": 684937216 + } + ], + "id": "quartz-answerability-frozen", + "dimension": "retrieval", + "tier": "hard", + "score": 0.7272727272727273, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.600 (n=10) positive-n=12 negative-n=10 (22 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 1.4545454545454546, + 2 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 0.7272727272727273, + "n": 2, + "ci95": 0.0 + } + }, + "overall_index": 0.7272727272727273, + "scenario_weighted_index": 0.7272727272727273 +} diff --git a/docs/audits/2026-09-07-answerability/results/validation/1-candidate.stderr.log b/docs/audits/2026-09-07-answerability/results/validation/1-candidate.stderr.log new file mode 100644 index 0000000..60b2ab4 --- /dev/null +++ b/docs/audits/2026-09-07-answerability/results/validation/1-candidate.stderr.log @@ -0,0 +1,6 @@ +brainbench: 2 scenario(s) to run + [1/2] orchid-answerability-frozen | dim=retrieval tier=hard ... + -> score=1.00 | positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.000 (n=10) positive-n=12 negative-n=10 (22 queries) + [2/2] quartz-answerability-frozen | dim=retrieval tier=hard ... + -> score=1.00 | positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.000 (n=10) positive-n=12 negative-n=10 (22 queries) +kbench brainbench: report saved -> E:\tmp\kimetsu-brain-hardening\bench\local\runs\brainbench\2026-09-07T05-55-47.2821349Z.json diff --git a/docs/audits/2026-09-07-answerability/results/validation/1-candidate.stdout.log b/docs/audits/2026-09-07-answerability/results/validation/1-candidate.stdout.log new file mode 100644 index 0000000..19a00a0 --- /dev/null +++ b/docs/audits/2026-09-07-answerability/results/validation/1-candidate.stdout.log @@ -0,0 +1,1101 @@ +{ + "generated_at": "2026-09-07T05:55:47.2818094Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-answerability\\validation-frozen.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "What is the Orchid gateway timeout?", + "ranked": [ + "timeout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Z3F20JFPE952W3SBHNMM", + "id": "01M1X6Z5AKAJM6PC09XA6N6J8G", + "kind": "memory", + "score": 0.999908208847046, + "summary": "project:fact - Orchid gateway timeout is 45 seconds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1791.2393, + "first_query": true, + "server_startup_ms": 72.32669999999999, + "model_text_bytes": 426, + "mcp_result_bytes": 507, + "wire_bytes": 542, + "reported_used_tokens": 507, + "working_set_bytes": 632143872, + "peak_working_set_bytes": 685117440 + }, + { + "query": "How many retries does the Orchid client use?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Z3FHE516M190ES87Y9TB", + "id": "01M1X6Z5P5YB53B577DWC7B7HK", + "kind": "memory", + "score": 0.9976400136947632, + "summary": "project:fact - Orchid client retry count is 5." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 352.8681, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 420, + "mcp_result_bytes": 501, + "wire_bytes": 536, + "reported_used_tokens": 501, + "working_set_bytes": 634351616, + "peak_working_set_bytes": 685117440 + }, + { + "query": "What is the Orchid worker memory limit?", + "ranked": [ + "memory" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Z3FSY9PHQ7X50YEXP864", + "id": "01M1X6Z61GGR4ZMQJR3Y8HE5ND", + "kind": "memory", + "score": 0.9999785423278807, + "summary": "project:fact - Orchid worker memory limit is 768 MiB." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 362.1267, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 543, + "reported_used_tokens": 508, + "working_set_bytes": 639639552, + "peak_working_set_bytes": 685117440 + }, + { + "query": "What version does the Orchid worker run?", + "ranked": [ + "version" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Z3G208YWQXYFF8T42A96", + "id": "01M1X6Z6CFQ9EN9A97J0MNWERX", + "kind": "memory", + "score": 0.9998592138290404, + "summary": "project:fact - Orchid worker version 8.2 is installed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 360.7787, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 543, + "reported_used_tokens": 508, + "working_set_bytes": 639807488, + "peak_working_set_bytes": 685117440 + }, + { + "query": "What is `storage.page_bytes` in Orchid?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Z3GCCQ19YWCBQEN82V0W", + "id": "01M1X6Z6QZ9KD66JK8485335SM", + "kind": "memory", + "score": 0.9999752044677734, + "summary": "project:fact - Orchid storage.page_bytes = 8192." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 363.8144, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 422, + "mcp_result_bytes": 503, + "wire_bytes": 538, + "reported_used_tokens": 503, + "working_set_bytes": 640425984, + "peak_working_set_bytes": 685117440 + }, + { + "query": "What password does the Orchid gateway require?", + "ranked": [ + "password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Z3GNFA7389JQSPX5DJHS", + "id": "01M1X6Z738D3PK3Y419X4B2D1Q", + "kind": "memory", + "score": 0.999871015548706, + "summary": "project:fact - No password is required for the Orchid gateway." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 397.2996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 435, + "mcp_result_bytes": 516, + "wire_bytes": 551, + "reported_used_tokens": 516, + "working_set_bytes": 640483328, + "peak_working_set_bytes": 685117440 + }, + { + "query": "How long are Orchid backups retained?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Z3GYMK45Y974HY2F9E6M", + "id": "01M1X6Z7G3N0BMHM48XD2NC87B", + "kind": "memory", + "score": 0.9999786615371704, + "summary": "project:fact - Orchid backups are retained for 36 hours." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 380.8821, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 429, + "mcp_result_bytes": 510, + "wire_bytes": 545, + "reported_used_tokens": 510, + "working_set_bytes": 640827392, + "peak_working_set_bytes": 685117440 + }, + { + "query": "Which files configure the port for Orchid?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Z3H6NYDEZMFYZV1QS0EW", + "id": "01M1X6Z7W3NEZ3BEHTZAPMVHEG", + "kind": "memory", + "score": 0.9955846667289734, + "summary": "project:fact - Orchid listener binds TCP port 7321. Configure its port in listener.toml." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 379.6163, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 462, + "mcp_result_bytes": 543, + "wire_bytes": 578, + "reported_used_tokens": 543, + "working_set_bytes": 641015808, + "peak_working_set_bytes": 685117440 + }, + { + "query": "What causes a version conflict in Orchid?", + "ranked": [ + "advice" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Z3HJKHAVKPZRJ96TYHHW", + "id": "01M1X6Z87NQ6KVM955VNAD7N5E", + "kind": "memory", + "score": 0.9998210072517396, + "summary": "project:fact - Orchid version conflicts occur when lockfiles disagree. Regenerate the lockfile and check dependency constraints." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 361.979, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 501, + "mcp_result_bytes": 582, + "wire_bytes": 618, + "reported_used_tokens": 582, + "working_set_bytes": 641089536, + "peak_working_set_bytes": 685117440 + }, + { + "query": "¿Qué versión usa el worker de Orchid?", + "ranked": [ + "version" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Z3G208YWQXYFF8T42A96", + "id": "01M1X6Z8JH22NMQVT6AE2A8MC0", + "kind": "memory", + "score": 0.999970316886902, + "summary": "project:fact - Orchid worker version 8.2 is installed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 351.7429, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 544, + "reported_used_tokens": 508, + "working_set_bytes": 641204224, + "peak_working_set_bytes": 685117440 + }, + { + "query": "¿Cuánto tiempo se conservan las copias de seguridad de Orchid?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Z3GYMK45Y974HY2F9E6M", + "id": "01M1X6Z8XVCWZ3VGRXJCKDF4E7", + "kind": "memory", + "score": 0.9995601773262024, + "summary": "project:fact - Orchid backups are retained for 36 hours." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 366.82, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 429, + "mcp_result_bytes": 510, + "wire_bytes": 546, + "reported_used_tokens": 510, + "working_set_bytes": 641654784, + "peak_working_set_bytes": 685117440 + }, + { + "query": "What is the timeout in seconds for the gateway in Orchid?", + "ranked": [ + "timeout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6Z3F20JFPE952W3SBHNMM", + "id": "01M1X6Z995F1KWS65M169P9FA5", + "kind": "memory", + "score": 0.9999822378158568, + "summary": "project:fact - Orchid gateway timeout is 45 seconds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 360.3417, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 426, + "mcp_result_bytes": 507, + "wire_bytes": 543, + "reported_used_tokens": 507, + "working_set_bytes": 641716224, + "peak_working_set_bytes": 685117440 + }, + { + "query": "What is the database timeout for Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 355.3289, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 641748992, + "peak_working_set_bytes": 685117440 + }, + { + "query": "What encryption key does Orchid use?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 350.5031, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 641785856, + "peak_working_set_bytes": 685117440 + }, + { + "query": "What is `storage.cache_bytes` in Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 370.5801, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 641847296, + "peak_working_set_bytes": 685117440 + }, + { + "query": "How many production replicas does Orchid run?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 377.8243, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 641867776, + "peak_working_set_bytes": 685117440 + }, + { + "query": "What is the OpenSSL version for Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 350.1353, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 641867776, + "peak_working_set_bytes": 685117440 + }, + { + "query": "What is the database password for Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 350.3474, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 641867776, + "peak_working_set_bytes": 685117440 + }, + { + "query": "How long are logs retained for Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 349.6879, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 641900544, + "peak_working_set_bytes": 685117440 + }, + { + "query": "¿Qué contraseña usa la base de datos de Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 359.72900000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 642015232, + "peak_working_set_bytes": 685117440 + }, + { + "query": "¿Cuántas replicas de producción tiene Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 360.4404, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 642039808, + "peak_working_set_bytes": 685117440 + }, + { + "query": "Which region hosts Orchid production?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 348.4604, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 642105344, + "peak_working_set_bytes": 685117440 + } + ], + "id": "orchid-answerability-frozen", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.000 (n=10) positive-n=12 negative-n=10 (22 queries)" + }, + { + "observations": [ + { + "query": "What is the Quartz gateway timeout?", + "ranked": [ + "timeout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6ZE6VFD30HKMB332K594Q", + "id": "01M1X6ZG1H73DTGWB37HQKM9K8", + "kind": "memory", + "score": 0.9999661445617676, + "summary": "project:fact - Quartz gateway timeout is 45 seconds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1781.2711000000002, + "first_query": true, + "server_startup_ms": 76.8679, + "model_text_bytes": 426, + "mcp_result_bytes": 507, + "wire_bytes": 542, + "reported_used_tokens": 507, + "working_set_bytes": 636243968, + "peak_working_set_bytes": 684883968 + }, + { + "query": "How many retries does the Quartz client use?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6ZE7AN6KPGQGVSNQJF234", + "id": "01M1X6ZGCTYXKJFGQBMSBED164", + "kind": "memory", + "score": 0.9913354516029358, + "summary": "project:fact - Quartz client retry count is 5." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 345.8044, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 420, + "mcp_result_bytes": 501, + "wire_bytes": 536, + "reported_used_tokens": 501, + "working_set_bytes": 636764160, + "peak_working_set_bytes": 684883968 + }, + { + "query": "What is the Quartz worker memory limit?", + "ranked": [ + "memory" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6ZE7JWXQPWNRX3X85AAWB", + "id": "01M1X6ZGQKB4KBYY3EDJ3FQ53N", + "kind": "memory", + "score": 0.9999793767929076, + "summary": "project:fact - Quartz worker memory limit is 768 MiB." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 350.2877, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 543, + "reported_used_tokens": 508, + "working_set_bytes": 641699840, + "peak_working_set_bytes": 684883968 + }, + { + "query": "What version does the Quartz worker run?", + "ranked": [ + "version" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6ZE7V6VNTTEC8TA972H3N", + "id": "01M1X6ZH2NHFM0ESWFE1WAZ5Y5", + "kind": "memory", + "score": 0.9998953342437744, + "summary": "project:fact - Quartz worker version 8.2 is installed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.8625, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 543, + "reported_used_tokens": 508, + "working_set_bytes": 641925120, + "peak_working_set_bytes": 684883968 + }, + { + "query": "What is `storage.page_bytes` in Quartz?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6ZE85JSAKW61ZZMAY2M8Q", + "id": "01M1X6ZHDTGECWBEANASXWH4R3", + "kind": "memory", + "score": 0.9999722242355348, + "summary": "project:fact - Quartz storage.page_bytes = 8192." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 353.9498, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 422, + "mcp_result_bytes": 503, + "wire_bytes": 538, + "reported_used_tokens": 503, + "working_set_bytes": 644141056, + "peak_working_set_bytes": 684883968 + }, + { + "query": "What password does the Quartz gateway require?", + "ranked": [ + "password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6ZE8EBNQW8YRKA45WT0NC", + "id": "01M1X6ZHRSHMCM1YKSFYEGQC3J", + "kind": "memory", + "score": 0.9998206496238708, + "summary": "project:fact - No password is required for the Quartz gateway." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 349.2876, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 436, + "mcp_result_bytes": 517, + "wire_bytes": 552, + "reported_used_tokens": 517, + "working_set_bytes": 644337664, + "peak_working_set_bytes": 684883968 + }, + { + "query": "How long are Quartz backups retained?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6ZE8RV9GGEDFWRMZGAF79", + "id": "01M1X6ZJ3XC2S2NK34S193SRGJ", + "kind": "memory", + "score": 0.999981164932251, + "summary": "project:fact - Quartz backups are retained for 36 hours." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 358.7388, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 428, + "mcp_result_bytes": 509, + "wire_bytes": 544, + "reported_used_tokens": 509, + "working_set_bytes": 644538368, + "peak_working_set_bytes": 684883968 + }, + { + "query": "Which files configure the port for Quartz?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6ZE92HRG2G06CQRM5852W", + "id": "01M1X6ZJF58ZSMC79HZSM1TTPJ", + "kind": "memory", + "score": 0.9969274401664734, + "summary": "project:fact - Quartz listener binds TCP port 8452. Configure its port in listener.toml." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 363.7395, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 462, + "mcp_result_bytes": 543, + "wire_bytes": 578, + "reported_used_tokens": 543, + "working_set_bytes": 644767744, + "peak_working_set_bytes": 684883968 + }, + { + "query": "What causes a version conflict in Quartz?", + "ranked": [ + "advice" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6ZE9EP1KC8GDQ35GKNH3T", + "id": "01M1X6ZJTWABMWKYKFC84RZEV9", + "kind": "memory", + "score": 0.9998512268066406, + "summary": "project:fact - Quartz version conflicts occur when lockfiles disagree. Regenerate the lockfile and check dependency constraints." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 368.7004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 501, + "mcp_result_bytes": 582, + "wire_bytes": 618, + "reported_used_tokens": 582, + "working_set_bytes": 644907008, + "peak_working_set_bytes": 684883968 + }, + { + "query": "¿Qué versión usa el worker de Quartz?", + "ranked": [ + "version" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6ZE7V6VNTTEC8TA972H3N", + "id": "01M1X6ZK5VPNB3ZTBPTY5JZJJV", + "kind": "memory", + "score": 0.9999598264694214, + "summary": "project:fact - Quartz worker version 8.2 is installed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 352.2954, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 544, + "reported_used_tokens": 508, + "working_set_bytes": 645013504, + "peak_working_set_bytes": 684883968 + }, + { + "query": "¿Cuánto tiempo se conservan las copias de seguridad de Quartz?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6ZE8RV9GGEDFWRMZGAF79", + "id": "01M1X6ZKGZ3REYC1F4XXBT51SJ", + "kind": "memory", + "score": 0.9996949434280396, + "summary": "project:fact - Quartz backups are retained for 36 hours." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 363.1407, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 429, + "mcp_result_bytes": 510, + "wire_bytes": 546, + "reported_used_tokens": 510, + "working_set_bytes": 645574656, + "peak_working_set_bytes": 684883968 + }, + { + "query": "What is the timeout in seconds for the gateway in Quartz?", + "ranked": [ + "timeout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1X6ZE6VFD30HKMB332K594Q", + "id": "01M1X6ZKWB20RB0JWVWD74Z46J", + "kind": "memory", + "score": 0.9999818801879884, + "summary": "project:fact - Quartz gateway timeout is 45 seconds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 359.199, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 426, + "mcp_result_bytes": 507, + "wire_bytes": 543, + "reported_used_tokens": 507, + "working_set_bytes": 645701632, + "peak_working_set_bytes": 684883968 + }, + { + "query": "What is the database timeout for Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 363.9108, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645775360, + "peak_working_set_bytes": 684883968 + }, + { + "query": "What encryption key does Quartz use?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 351.41040000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645779456, + "peak_working_set_bytes": 684883968 + }, + { + "query": "What is `storage.cache_bytes` in Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 359.0007, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645783552, + "peak_working_set_bytes": 684883968 + }, + { + "query": "How many production replicas does Quartz run?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 357.4308, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645799936, + "peak_working_set_bytes": 684883968 + }, + { + "query": "What is the OpenSSL version for Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 754.8161, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645804032, + "peak_working_set_bytes": 684883968 + }, + { + "query": "What is the database password for Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 372.7692, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645820416, + "peak_working_set_bytes": 684883968 + }, + { + "query": "How long are logs retained for Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 347.4746, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645861376, + "peak_working_set_bytes": 684883968 + }, + { + "query": "¿Qué contraseña usa la base de datos de Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 353.0142, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645922816, + "peak_working_set_bytes": 684883968 + }, + { + "query": "¿Cuántas replicas de producción tiene Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 357.3481, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 646070272, + "peak_working_set_bytes": 684883968 + }, + { + "query": "Which region hosts Quartz production?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 359.3071, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 646152192, + "peak_working_set_bytes": 684883968 + } + ], + "id": "quartz-answerability-frozen", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.000 (n=10) positive-n=12 negative-n=10 (22 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 2.0, + 2 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 1.0, + "n": 2, + "ci95": 0.0 + } + }, + "overall_index": 1.0, + "scenario_weighted_index": 1.0 +} diff --git a/docs/audits/2026-09-07-answerability/verification/benchmark-python.log b/docs/audits/2026-09-07-answerability/verification/benchmark-python.log new file mode 100644 index 0000000..0c85360 --- /dev/null +++ b/docs/audits/2026-09-07-answerability/verification/benchmark-python.log @@ -0,0 +1,15 @@ +repeat 1/1: baseline +repeat 1/1: candidate +repeat 1/1: baseline +repeat 1/1: candidate +repeat 1/1: baseline +repeat 1/1: baseline +repeat 1/1: baseline +repeat 1/1: baseline +repeat 1/1: baseline +repeat 1/1: baseline +.................. +---------------------------------------------------------------------- +Ran 18 tests in 2.541s + +OK diff --git a/docs/audits/2026-09-07-answerability/verification/benchmark-rust.log b/docs/audits/2026-09-07-answerability/verification/benchmark-rust.log new file mode 100644 index 0000000..57bdeee --- /dev/null +++ b/docs/audits/2026-09-07-answerability/verification/benchmark-rust.log @@ -0,0 +1,148 @@ + Compiling kimetsu-core v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-core) + Compiling kimetsu-brain v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-brain) + Compiling kimetsu-agent v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-agent) + Compiling kimetsu-bench v0.2.0 (E:\tmp\kimetsu-brain-hardening\bench) + Finished `test` profile [unoptimized + debuginfo] target(s) in 44.08s + Running unittests src\main.rs (E:/Kimetsu/bench/target\debug\deps\kbench-a3892fa7ea342860.exe) + +running 131 tests +test drivers::brain_mcp::tests::windows_process_memory_reports_live_working_set_and_peak ... ok +test drivers::beam::tests::synthetic_fixture_is_well_formed ... ok +test drivers::brain_mcp::tests::malformed_or_failed_protocol_output_is_not_abstention ... ok +test drivers::brain_mcp::tests::measures_serialized_result_and_utf8_text_without_losing_escapes ... ok +test drivers::brainbench::tests::ci95_half_width_undefined_for_small_n ... ok +test drivers::brainbench::tests::ci95_half_width_shrinks_with_n ... ok +test drivers::brainbench::tests::ci95_half_width_zero_for_constant_scores ... ok +test drivers::brainbench::tests::build_report_excludes_skipped_from_index ... ok +test drivers::brainbench::tests::dimension_from_str ... ok +test drivers::brainbench::tests::expand_eval_fixtures_empty_refs_is_empty ... ok +test drivers::brainbench::tests::dated_compressed_capsule_matches_visible_fixture_text ... ok +test drivers::brainbench::tests::filter_by_dimension ... ok +test drivers::brainbench::tests::filter_by_limit ... ok +test drivers::brainbench::tests::filter_by_tier ... ok +test drivers::brainbench::tests::forget_f1_partial_wrong_and_overproposed ... ok +test drivers::brainbench::tests::forget_f1_perfect_and_empty ... ok +test drivers::brainbench::tests::filter_empty_returns_all ... ok +test drivers::brainbench::tests::irrelevant_memory_is_not_perfect_no_answer_retrieval ... ok +test drivers::brainbench::tests::malformed_capsule_summary_is_an_error ... ok +test drivers::brainbench::tests::model_override_survives_new_project_retrieval_preset ... ok +test drivers::brainbench::tests::mrr_first_position ... ok +test drivers::brainbench::tests::mrr_no_relevant_is_zero ... ok +test drivers::brainbench::tests::mrr_second_position ... ok +test drivers::brainbench::tests::pairwise_one_swapped_pair_is_two_thirds ... ok +test drivers::brainbench::tests::pairwise_missing_keys_are_skipped ... ok +test drivers::brainbench::tests::pairwise_perfect_order_is_one ... ok +test drivers::brainbench::tests::normalize_collapses_whitespace_and_case ... ok +test drivers::brainbench::tests::pairwise_tie_is_half ... ok +test drivers::brainbench::tests::pairwise_reversed_order_is_zero ... ok +test drivers::brainbench::tests::recall_empty_relevant_is_one ... ok +test drivers::brainbench::tests::recall_full_and_partial ... ok +test drivers::brainbench::tests::recall_zero_k_or_empty_ranked_is_zero ... ok +test drivers::brainbench::tests::resolution_relevant_absent_is_false ... ok +test drivers::brainbench::tests::report_does_not_let_calibration_volume_hide_failed_retrieval ... ok +test drivers::brainbench::tests::resolution_relevant_outranks_stale ... ok +test drivers::brainbench::tests::resolution_stale_absent_is_true ... ok +test drivers::brainbench::tests::expand_calibration_gen_rejects_tiny_pool ... ok +test drivers::brainbench::tests::retrieval_measurements_disable_unscored_warm_start_for_every_model ... ok +test drivers::brainbench::tests::retrieval_rejects_stale_context_even_below_correct_answer ... ok +test drivers::brainbench::tests::score_dedup_balances_precision_and_recall ... ok +test drivers::brainbench::tests::score_dedup_detects_true_positive ... ok +test drivers::brainbench::tests::score_dedup_no_groups_falls_back_to_zero ... ok +test drivers::beam::tests::load_dataset_accepts_envelope_and_bare_array ... ok +test drivers::brainbench::tests::score_dedup_perfect_precision_no_false_positive ... ok +test drivers::brainbench::tests::stale_empty_is_zero ... ok +test drivers::brainbench::tests::stale_hit_within_and_beyond_k ... ok +test drivers::brainbench::tests::expand_eval_fixtures_one_scenario_per_kind ... ok +test drivers::brainbench::tests::strip_prefix_summary_strips_scope_kind ... ok +test drivers::brainbench::tests::tier_from_str ... ok +test drivers::brainbench::tests::config_base_strips_storage_table_for_clean_backend_switch ... ok +test drivers::brainbench::tests::unknown_capsules_keep_their_rank_and_count_as_injection ... ok +test drivers::brainbench::tests::workflow_abstention_episode ... ok +test drivers::brainbench::tests::workflow_gold_episode_scores_recall ... ok +test drivers::brainbench::tests::workflow_learning_curve_halves ... ok +test drivers::brainbench::tests::expand_calibration_gen_is_deterministic_and_well_formed ... ok +test drivers::brainbench::tests::load_dataset_roundtrips_synthetic_fixture ... ok +test drivers::brainbench::tests::workflow_spec_parses_from_json ... ok +test drivers::brainbench::tests::workflow_stale_gates_gold_score ... ok +test drivers::brainbench::tests::workflow_trap_gate ... ok +test drivers::brainbench::tests::expand_workflow_gen_background_grows_the_haystack ... ok +test drivers::brainbench::tests::write_precision_all_captured_all_on_target ... ok +test drivers::brainbench::tests::write_precision_empty_distilled_precision_one ... ok +test drivers::brainbench::tests::write_precision_empty_gold_recall_one ... ok +test drivers::brainbench::tests::write_precision_offtarget_lesson_lowers_precision ... ok +test drivers::brainbench::tests::write_precision_partial_recall ... ok +test drivers::locomo::tests::category_names_cover_paper_taxonomy ... ok +test drivers::locomo::tests::render_markdown_reports_non_adversarial_slice ... ok +test drivers::longmemeval::tests::build_report_computes_accuracy_correctly ... ok +test drivers::longmemeval::tests::codex_argv_construction ... ok +test drivers::longmemeval::tests::codex_argv_no_model_omits_m_flag ... ok +test drivers::longmemeval::tests::codex_backend_validate_config_ok ... ok +test drivers::longmemeval::tests::codex_judge_prompt_abstention_note ... ok +test drivers::longmemeval::tests::codex_judge_prompt_correct_incorrect_instruction ... ok +test drivers::longmemeval::tests::codex_judge_prompt_preference_rubric ... ok +test drivers::longmemeval::tests::codex_reader_prompt_contains_key_instructions ... ok +test drivers::longmemeval::tests::codex_reader_prompt_includes_date_when_present ... ok +test drivers::longmemeval::tests::filter_by_limit ... ok +test drivers::longmemeval::tests::filter_by_question_type ... ok +test drivers::brainbench::tests::expand_workflow_gen_is_deterministic_and_well_formed ... ok +test drivers::longmemeval::tests::filter_limit_stratifies_across_types ... ok +test drivers::longmemeval::tests::filter_zero_limit_returns_all ... ok +test drivers::longmemeval::tests::heuristic_judge_abstention_correct ... ok +test drivers::longmemeval::tests::heuristic_judge_abstention_incorrect ... ok +test drivers::longmemeval::tests::heuristic_judge_mismatch ... ok +test drivers::longmemeval::tests::heuristic_judge_substring_match ... ok +test drivers::longmemeval::tests::ingest_plan_no_dates_for_single_session ... ok +test drivers::longmemeval::tests::ingest_plan_uses_dates_for_temporal_types ... ok +test drivers::longmemeval::tests::is_abstention_detects_abs_suffix ... ok +test drivers::locomo::tests::limit_samples_round_robin_across_categories ... ok +test drivers::longmemeval::tests::is_abstention_false_for_normal_types ... ok +test drivers::longmemeval::tests::llm_backend_default_is_http ... ok +test drivers::locomo::tests::parses_sessions_turns_and_qa ... ok +test drivers::longmemeval::tests::llm_backend_from_str_roundtrip ... ok +test drivers::longmemeval::tests::no_model_configured_error_message_is_actionable ... ok +test drivers::longmemeval::tests::parse_instance_missing_optional_fields ... ok +test drivers::longmemeval::tests::parse_instance_without_has_answer_defaults_to_false ... ok +test drivers::longmemeval::tests::dry_run_smoke_test_with_synthetic_fixture ... ok +test drivers::longmemeval::tests::parse_minimal_instance_from_json ... ok +test drivers::terminal_bench::tests::capture_wrapper_maps_every_agent_we_actually_run ... ok +test drivers::longmemeval::tests::dry_run_smoke_test_with_codex_backend ... ok +test drivers::terminal_bench::tests::capture_wrapper_refuses_unknown_agent_rather_than_running_uncaptured ... ok +test drivers::terminal_bench::tests::deepswe_named_metrics_are_scored_not_silently_zeroed ... ok +test drivers::terminal_bench::tests::deepswe_zero_reward_stays_a_loss ... ok +test drivers::terminal_bench::tests::merge_mounts_combines_driver_and_extra_args ... ok +test drivers::terminal_bench::tests::merge_mounts_returns_none_when_nothing_to_mount ... ok +test drivers::terminal_bench::tests::merge_mounts_uses_only_extra_when_driver_none ... ok +test drivers::terminal_bench::tests::parse_cost_from_stdout_finds_embedded_cost_line ... ok +test drivers::terminal_bench::tests::parse_cost_from_stdout_returns_none_when_absent ... ok +test drivers::terminal_bench::tests::parse_harbor_result_averages_multiple_evals ... ok +test drivers::terminal_bench::tests::parse_harbor_result_errored_task_surfaces_exception_kinds ... ok +test drivers::terminal_bench::tests::parse_harbor_result_missing_stats_grades_zero ... ok +test drivers::terminal_bench::tests::parse_harbor_result_partial_credit_passes_through ... ok +test drivers::terminal_bench::tests::parse_harbor_result_pass_surfaces_clean_grade ... ok +test drivers::terminal_bench::tests::parse_harbor_result_surfaces_useful_error_on_garbage ... ok +test drivers::terminal_bench::tests::parse_list_tasks_accepts_id_alias ... ok +test drivers::terminal_bench::tests::parse_list_tasks_accepts_task_id_field ... ok +test drivers::terminal_bench::tests::parse_list_tasks_surfaces_useful_error_on_garbage ... ok +test drivers::terminal_bench::tests::parse_tasks_from_dataset_path_errors_on_non_directory ... ok +test drivers::terminal_bench::tests::tail_lossy_returns_whole_string_when_under_limit ... ok +test drivers::terminal_bench::tests::tail_lossy_trims_to_max_with_ellipsis ... ok +test drivers::terminal_bench::tests::terminal_bench_mean_still_takes_precedence ... ok +test setup::auth::tests::to_harbor_args_emits_ae_for_env_tokens ... ok +test setup::auth::tests::to_harbor_args_emits_mounts_for_codex_dir ... ok +test setup::binary::tests::cached_returns_none_when_missing ... ok +test drivers::terminal_bench::tests::missing_or_empty_model_patch_is_distinguishable_from_a_real_patch ... ok +test setup::auth::tests::dotenv_returns_quoted_value_unquoted ... ok +test setup::auth::tests::dotenv_skips_comments_and_blank_lines ... ok +test drivers::terminal_bench::tests::parse_tasks_from_dataset_path_returns_registry_prefixed_ids ... ok +test setup::binary::tests::cached_fresh_when_newer_than_sources ... ok +test setup::binary::tests::cached_stale_when_older_than_sources ... ok +test drivers::beam::tests::dry_run_counts_probes_without_calls ... ok + +test result: ok. 131 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.05s + + Running unittests src\bin\kstress\main.rs (E:/Kimetsu/bench/target\debug\deps\kstress-2bc51bf0390e122d.exe) + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + diff --git a/docs/audits/2026-09-07-answerability/verification/hook.log b/docs/audits/2026-09-07-answerability/verification/hook.log new file mode 100644 index 0000000..b5c147c --- /dev/null +++ b/docs/audits/2026-09-07-answerability/verification/hook.log @@ -0,0 +1,3 @@ +PASS: guard=True, expected=None +PASS: guard=False, expected='managed' +PASS: guard=True, expected='6319' diff --git a/docs/audits/2026-09-07-answerability/verification/release-build.log b/docs/audits/2026-09-07-answerability/verification/release-build.log new file mode 100644 index 0000000..9e9ab71 --- /dev/null +++ b/docs/audits/2026-09-07-answerability/verification/release-build.log @@ -0,0 +1,11 @@ + Compiling kimetsu-core v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-core) + Compiling kimetsu-brain v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-brain) + Compiling kimetsu-agent v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-agent) + Compiling kimetsu-chat v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-chat) + Compiling kimetsu-cli v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-cli) +warning: linker stdout: Creando biblioteca E:\Kimetsu\target\release\deps\kimetsu.lib y objeto E:\Kimetsu\target\release\deps\kimetsu.exp + | + = note: `#[warn(linker_messages)]` on by default + +warning: `kimetsu-cli` (bin "kimetsu") generated 1 warning + Finished `release` profile [optimized] target(s) in 3m 54s diff --git a/docs/audits/2026-09-07-answerability/verification/review-regressions-red.log b/docs/audits/2026-09-07-answerability/verification/review-regressions-red.log new file mode 100644 index 0000000..21feb86 --- /dev/null +++ b/docs/audits/2026-09-07-answerability/verification/review-regressions-red.log @@ -0,0 +1,43 @@ + Compiling kimetsu-core v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-core) + Compiling kimetsu-brain v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-brain) + Finished `test` profile [unoptimized + debuginfo] target(s) in 14.35s + Running unittests src\lib.rs (E:/Kimetsu/target\debug\deps\kimetsu_brain-aca789751d649d80.exe) + +running 3 tests +test answerability::tests::review_scope_regressions ... FAILED +test answerability::tests::review_competing_clause ... FAILED +test answerability::tests::review_negated_values ... FAILED + +failures: + +---- answerability::tests::review_scope_regressions stdout ---- + +thread 'answerability::tests::review_scope_regressions' (29556) panicked at crates\kimetsu-brain\src\answerability.rs:398:13: +assertion `left == right` failed: What causes a version conflict? + left: MissingValue + right: Unrecognized +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + +---- answerability::tests::review_competing_clause stdout ---- + +thread 'answerability::tests::review_competing_clause' (30272) panicked at crates\kimetsu-brain\src\answerability.rs:409:9: +assertion `left == right` failed + left: ValuePresent + right: MissingValue + +---- answerability::tests::review_negated_values stdout ---- + +thread 'answerability::tests::review_negated_values' (12544) panicked at crates\kimetsu-brain\src\answerability.rs:404:13: +assertion `left == right` failed: We do not use SQLite version 3.45. + left: ValuePresent + right: MissingValue + + +failures: + answerability::tests::review_competing_clause + answerability::tests::review_negated_values + answerability::tests::review_scope_regressions + +test result: FAILED. 0 passed; 3 failed; 0 ignored; 0 measured; 700 filtered out; finished in 0.05s + +error: test failed, to rerun pass `-p kimetsu-brain --lib` diff --git a/docs/audits/2026-09-07-answerability/verification/scope-green.log b/docs/audits/2026-09-07-answerability/verification/scope-green.log new file mode 100644 index 0000000..9abd00a --- /dev/null +++ b/docs/audits/2026-09-07-answerability/verification/scope-green.log @@ -0,0 +1,18 @@ + Compiling kimetsu-core v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-core) + Compiling kimetsu-brain v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-brain) + Finished `test` profile [unoptimized + debuginfo] target(s) in 16.11s + Running unittests src\lib.rs (E:/Kimetsu/target\debug\deps\kimetsu_brain-aca789751d649d80.exe) + +running 9 tests +test answerability::tests::broad_tasks_remain_outside_this_bounded_guard ... ok +test answerability::tests::review_scope_regressions ... ok +test answerability::tests::mentions_and_unknown_values_are_not_answers ... ok +test answerability::tests::review_negated_values ... ok +test answerability::tests::review_competing_clause ... ok +test answerability::tests::related_topics_do_not_supply_missing_configuration_values ... ok +test answerability::tests::explicit_absence_and_short_retention_are_useful_answers ... ok +test answerability::tests::a_value_for_another_component_is_not_an_answer ... ok +test answerability::tests::explicit_values_survive_in_both_languages ... ok + +test result: ok. 9 passed; 0 failed; 0 ignored; 0 measured; 694 filtered out; finished in 0.07s + diff --git a/docs/audits/2026-09-07-answerability/verification/workspace.log b/docs/audits/2026-09-07-answerability/verification/workspace.log new file mode 100644 index 0000000..20c5f1c --- /dev/null +++ b/docs/audits/2026-09-07-answerability/verification/workspace.log @@ -0,0 +1,1552 @@ + Compiling kimetsu-core v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-core) + Compiling kimetsu-brain v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-brain) + Compiling kimetsu-agent v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-agent) + Compiling kimetsu-chat v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-chat) + Compiling kimetsu-e2e v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-e2e) + Compiling kimetsu-remote v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-remote) + Compiling kimetsu-cli v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-cli) + Finished `test` profile [unoptimized + debuginfo] target(s) in 2m 02s + Running unittests src\lib.rs (E:/Kimetsu/target\debug\deps\kimetsu_agent-204c5868f79bbaf2.exe) + +running 135 tests +test agent_loop::tests::structured_json_parser_extracts_object_from_text ... ok +test anthropic::tests::a_region_prefixed_frontier_id_still_omits_temperature ... ok +test anthropic::tests::messages_url_uses_base_when_set ... ok +test anthropic::tests::an_unknown_model_omits_temperature ... ok +test anthropic::tests::temperature_is_omitted_on_models_that_reject_it ... ok +test anthropic::tests::temperature_is_kept_on_models_that_accept_it ... ok +test bedrock::tests::bedrock_live_invoke ... ignored, requires real AWS credentials and Bedrock model access +test bedrock::tests::bedrock_body_has_anthropic_version_and_no_model_key ... ok +test anthropic::tests::the_request_body_drops_temperature_for_a_frontier_model ... ok +test bedrock::tests::direct_anthropic_body_regression ... ok +test anthropic::tests::response_maps_text_tool_use_and_usage ... ok +test bedrock::tests::bedrock_body_includes_system_and_tools ... ok +test anthropic::tests::request_maps_system_and_tool_blocks_to_anthropic_shape ... ok +test bedrock::tests::parse_bedrock_response_shape ... ok +test claude_code::tests::cache_stats_default_to_zero_when_absent ... ok +test claude_code::tests::cache_stats_round_trip_through_parser ... ok +test bedrock::tests::from_config_returns_none_when_not_bedrock_provider ... ok +test claude_code::tests::fingerprint_distinguishes_system_prompt_and_model ... ok +test bench::tests::auto_accept_policy_matches_documented_thresholds ... ok +test bench::tests::auto_accept_shadowed_by_low_usefulness_memory_is_rejected ... ok +test claude_code::tests::fingerprint_is_collision_resistant_for_close_strings ... ok +test bedrock::tests::sigv4_with_session_token_adds_security_token_header ... ok +test bedrock::tests::sigv4_headers_contain_expected_structure ... ok +test claude_code::tests::parses_finish_envelope_as_end_turn ... ok +test anthropic::tests::debug_format_does_not_leak_api_key ... ok +test anthropic::tests::for_distiller_builds_provider_with_base_url ... ok +test claude_code::tests::parses_success_json ... ok +test bedrock::tests::from_config_returns_none_when_access_key_missing ... ok +test claude_code::tests::parses_tool_call_envelope_from_response_text ... ok +test claude_code::tests::renders_text_only_request ... ok +test claude_code::tests::renders_tool_call_message_as_text_for_history ... ok +test claude_code::tests::renders_tool_protocol_when_tools_present ... ok +test claude_code::tests::stream_json_parser_errors_on_empty_output ... ok +test claude_code::tests::debug_format_does_not_leak_api_key ... ok +test claude_code::tests::stream_json_parser_errors_when_no_result_event ... ok +test claude_code::tests::stream_json_parser_falls_back_to_single_blob ... ok +test claude_code::tests::stream_json_parser_picks_last_result_when_multiple ... ok +test bedrock::tests::from_config_returns_none_when_region_missing ... ok +test claude_code::tests::stream_json_parser_returns_last_result_event ... ok +test claude_code::tests::stream_json_parser_skips_non_result_events ... ok +test claude_code::tests::stream_json_parser_tolerates_malformed_lines ... ok +test harness::tests::check_workspace_path_rejects_escape_attempts ... ok +test harness::tests::codex_patch_translator_handles_add_file ... ok +test harness::tests::codex_patch_translator_handles_delete_file ... ok +test harness::tests::codex_patch_translator_handles_update_file ... ok +test harness::tests::codex_patch_translator_rejects_unmarked_input ... ok +test bedrock::tests::from_config_builds_provider_when_all_present ... ok +test harness::tests::codex_patch_translator_synthesizes_hunk_when_missing ... ok +test harness::tests::diff_target_escapes_detects_traversal ... ok +test harness::tests::extract_between_pulls_section ... ok +test harness::tests::extract_diff_path_strips_ab_prefix ... ok +test harness::tests::extract_marker_finds_first_match ... ok +test harness::tests::expand_capsule_appears_in_full_tool_loadout ... ok +test harness::tests::is_useful_tool_excludes_deliberation_tools ... ok +test harness::tests::is_useful_tool_recognises_workspace_actions ... ok +test harness::tests::parse_bg_status_line_exited_state ... ok +test harness::tests::parse_bg_status_line_running_state ... ok +test harness::tests::parse_unified_diff_detects_delete_file ... ok +test harness::tests::parse_unified_diff_detects_new_file ... ok +test harness::tests::parse_unified_diff_multi_file ... ok +test harness::tests::parse_unified_diff_rejects_empty ... ok +test harness::tests::parse_unified_diff_rejects_missing_plus_header ... ok +test harness::tests::parse_unified_diff_single_file_single_hunk ... ok +test harness::tests::parse_wh_handles_well_formed_and_garbage ... ok +test harness::tests::plan_tool_rejects_invalid_status ... ok +test harness::tests::file_tools_reject_workspace_escape ... ok +test harness::tests::plan_tool_rejects_missing_fields ... ok +test harness::tests::plan_tool_validates_and_normalizes_todos ... ok +test harness::tests::record_deviation_accepts_complete_input ... ok +test harness::tests::record_deviation_rejects_empty_strings ... ok +test harness::tests::record_deviation_rejects_missing_fields ... ok +test harness::tests::render_verify_nudge_includes_task_recap ... ok +test harness::tests::render_verify_nudge_renders_plan_recap_when_present ... ok +test harness::tests::render_verify_nudge_second_iteration_is_firmer ... ok +test harness::tests::render_verify_nudge_strict_mode_requires_record_deviation ... ok +test harness::tests::strip_path_components_mimics_patch_p ... ok +test harness::tests::task_signals_verification_matches_specific_triggers ... ok +test harness::tests::expand_capsule_dispatches_via_resolver_and_emits_event ... ok +test harness::tests::task_signals_verification_skips_generic_language ... ok +test harness::tests::think_tool_acknowledges_and_reports_length ... ok +test harness::tests::validate_bg_handle_accepts_well_formed ... ok +test harness::tests::expand_capsule_no_resolver_returns_not_configured_error ... ok +test harness::tests::validate_bg_handle_rejects_garbage ... ok +test harness::tests::dynamic_loadout_starts_small_and_loads_profiles_on_request ... ok +test harness::tests::expand_capsule_unknown_handle_returns_error_not_crash ... ok +test openai::tests::request_maps_system_and_user_to_responses_body ... ok +test openai::tests::response_maps_incomplete_max_tokens ... ok +test harness::tests::read_only_loadout_does_not_advertise_edit_or_shell_tools ... ok +test openai::tests::response_maps_output_text_and_usage ... ok +test openai::tests::debug_format_does_not_leak_api_key ... ok +test openai::tests::for_distiller_builds_provider_with_base_url ... ok +test openai::tests::response_maps_top_level_output_text ... ok +test openai::tests::responses_url_uses_base_when_set ... ok +test pipeline::tests::build_memory_proposal_request_includes_existing_memories ... ok +test pipeline::tests::e1_ledger_suppresses_pitfall_on_retry ... ok +test pipeline::tests::e1_no_match_returns_none ... ok +test pipeline::tests::e1_pitfall_request_uses_correct_parameters ... ok +test pipeline::tests::e1_pitfall_surfaces_in_first_attempt ... ok +test pipeline::tests::e1_surfaced_and_injected_are_independent ... ok +test pipeline::tests::f1_back_ref_is_cheaper_than_full_render ... ok +test pipeline::tests::f1_dedup_across_two_renders_same_bundle ... ok +test pipeline::tests::f1_partial_overlap_second_bundle ... ok +test pipeline::tests::f2_already_injected_capsule_is_back_referenced_not_charged_again ... ok +test pipeline::tests::f2_headline_does_not_double_charge_after_tool_expansion ... ok +test pipeline::tests::f2_headline_tier_charges_small_cost ... ok +test pipeline::tests::f2_top_tier_full_rest_headline ... ok +test pipeline::tests::f3_estimate_task_tokens_matches_pipeline_heuristic ... ok +test pipeline::tests::f3_overhead_ratio_falls_on_larger_task ... ok +test pipeline::tests::f3_per_run_global_cap_limits_total_brain_tokens ... ok +test pipeline::tests::filter_memory_proposals_drops_duplicates_by_normalized_text ... ok +test pipeline::tests::filter_memory_proposals_drops_invalid_scopes_and_kinds ... ok +test pipeline::tests::filter_memory_proposals_drops_run_specific_text ... ok +test pipeline::tests::filter_memory_proposals_keeps_long_guidance_even_with_path_mention ... ok +test pipeline::tests::render_memory_proposal_section_renders_accepted_proposals ... ok +test pipeline::tests::render_retry_context_includes_command_and_stderr_excerpt ... ok +test pipeline::tests::verification_section_renders_pass_and_fail_results ... ok +test pipeline::tests::verification_section_reports_no_commands ... ok +test pipeline::tests::verification_section_skipped_in_dry_run ... ok +test recall_ledger::tests::fresh_ledger_is_empty ... ok +test recall_ledger::tests::mark_injected_tracks_membership_and_tokens ... ok +test recall_ledger::tests::reinjection_counts_tokens_once_keeping_original_charge ... ok +test recall_ledger::tests::surfaced_dedup_for_proactive_recall ... ok +test swe_bench::tests::formats_task_brief_with_hints ... ok +test swe_bench::tests::parses_minimal_swe_bench_task ... ok +test pipeline::tests::fingerprint_strips_ansi_escapes ... ok +test pipeline::tests::fingerprint_differs_for_different_errors ... ok +test pipeline::tests::fingerprint_is_stable_across_paths_and_line_numbers ... ok +test agent_loop::tests::loop_enforces_tool_budget ... ok +test tools::tests::apply_patch_rejects_symlink_targets ... ok +test tools::tests::shell_policy_blocks_network_and_allows_direct_programs ... ok +test agent_loop::tests::loop_executes_tool_calls_and_writes_model_artifacts ... ok +test agent_loop::tests::loop_applies_patch_under_active_plan ... ok +test tools::tests::file_tools_and_apply_patch_emit_trace_events ... ok +test pipeline::tests::dry_run_pipeline_writes_trace_patch_plan_and_report ... ok +test bench::tests::benchmark_reports_warm_memory_reuse has been running for over 60 seconds +test bench::tests::benchmark_reports_warm_memory_reuse ... ok + +test result: ok. 134 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 61.23s + + Running unittests src\lib.rs (E:/Kimetsu/target\debug\deps\kimetsu_brain-ee2c0f7fc7f73527.exe) + +running 707 tests +test ambient::tests::parse_git_status_handles_typical_lines ... ok +test ambient::tests::parse_git_status_respects_limit ... ok +test ambient::tests::augment_query_appends_suffix_when_nonempty ... ok +test ambient::tests::render_omits_empty_fields ... ok +test ambient::tests::render_includes_branch_only_when_present ... ok +test ambient::tests::ambient_enabled_respects_env ... ok +test ambient::tests::w3_ambient_enabled_with_config_false_when_env_unset ... ok +test ambient::tests::render_collapses_multiple_fields_with_separator ... ok +test ambient::tests::w3_ambient_env_disable_overrides_config_true ... ok +test ambient::tests::render_normalizes_windows_path_separators ... ok +test ambient::tests::w3_ambient_env_enable_overrides_config_false ... ok +test answerability::tests::broad_tasks_remain_outside_this_bounded_guard ... ok +test ambient::tests::collect_recent_files_skips_dotkimetsu ... ok +test answerability::tests::mentions_and_unknown_values_are_not_answers ... ok +test answerability::tests::related_topics_do_not_supply_missing_configuration_values ... ok +test answerability::tests::review_competing_clause ... ok +test answerability::tests::review_negated_values ... ok +test answerability::tests::review_scope_regressions ... ok +test backend::tests::backend_for_all_known_variants_no_panic ... ok +test backend::tests::backend_for_flat_resolves ... ok +test answerability::tests::explicit_absence_and_short_retention_are_useful_answers ... ok +test answerability::tests::a_value_for_another_component_is_not_an_answer ... ok +test backend::tests::backend_for_graph_lite_resolves_to_graph_lite_backend ... ok +test answerability::tests::explicit_values_survive_in_both_languages ... ok +test backend::tests::backend_for_graph_resolves_to_petgraph_backend ... ok +test backend::tests::graph_lite_1_hop_surfaces_edge_connected_memory ... ok +test backend::tests::graph_lite_no_edges_returns_flat_set ... ok +test backend::tests::graph_reached_candidates_are_hop_decayed_below_their_seed ... ok +test backend::tests::graph_lite_is_a_superset_of_flat_with_edges_present ... ok +test backend::tests::graph_rerank_retains_trust_and_decayed_usefulness ... ok +test backend::tests::hardening_graph_hydration_checks_future_and_offset_expiry ... ok +test backend::tests::petgraph_backend_from_conn_empty_db ... ok +test backend::tests::petgraph_backend_graph_algorithms_on_seeded_topology ... ok +test backend::tests::petgraph_backend_memory_candidates_superset_of_flat ... ok +test backend::tests::superseded_event_inserts_edge_and_edge_survives_rebuild ... ok +test backend_bench::tests::cross_backend_bench_backend_names ... ok +test backend_bench::tests::format_results_markdown_includes_headers ... ok +test backend_bench::tests::cross_backend_bench_runs_without_panic ... ok +test backend_bench::tests::v25_decision_criterion_documents_verdict ... ok +test benchmark::tests::cold_brain_excludes_memory_capsules ... ok +test benchmark::tests::detects_known_and_suffix_task_slugs ... ok +test benchmark::tests::ignores_generic_terminal_bench_tokens ... ok +test benchmark::tests::outcome_memory_text_marks_episodic ... ok +test benchmark::tests::playbook_prioritizes_generalizable_memory_over_exact_episodic ... ok +test benchmark::tests::playbook_prioritizes_task_memory ... ok +test benchmark::tests::proposal_memory_text_marks_generalizable_and_review_pending ... ok +test benchmark::tests::required_mode_accepts_generalizable_memory_without_exact_slug ... ok +test bitemporal::tests::a_memory_written_later_is_not_in_the_past_view ... ok +test bitemporal::tests::a_retracted_memory_is_still_visible_before_its_retraction ... ok +test bitemporal::tests::a_superseded_memory_still_counts_as_a_past_belief ... ok +test backend_bench::tests::graph_lite_candidate_count_gte_flat ... ok +test backend_bench::tests::graph_lite_recall_gte_flat ... ok +test backend_bench::tests::petgraph_candidate_count_equals_graph_lite ... ok +test bitemporal::tests::as_of_capsules_render_the_scope_and_kind_prefix ... ok +test bitemporal::tests::belief_delta_reports_what_was_learned_and_retired ... ok +test bitemporal::tests::an_expired_memory_drops_out_after_its_valid_to ... ok +test bitemporal::tests::the_limit_is_respected_and_zero_means_all ... ok +test conflict::tests::auto_resolution_stamps_loser_valid_to_when_new_wins ... ok +test bitemporal::tests::valid_time_is_independent_of_when_it_was_recorded ... ok +test conflict::tests::auto_resolution_survives_rebuild ... ok +test conflict::tests::auto_resolution_stamps_new_memory_when_existing_wins ... ok +test conflict::tests::cross_model_rows_are_skipped ... ok +test conflict::tests::detect_and_record_noop_writes_nothing ... ok +test conflict::tests::exclude_id_prevents_self_conflict ... ok +test conflict::tests::exact_match_is_not_flagged_as_conflict ... ok +test conflict::tests::high_similarity_and_score_gap_do_not_prove_contradiction ... ok +test conflict::tests::list_unresolved_excludes_resolved_rows ... ok +test conflict::tests::near_tie_goes_to_queue_not_auto_resolved ... ok +test conflict::tests::noop_embedder_returns_no_conflicts ... ok +test conflict::tests::record_conflict_is_idempotent ... ok +test conflict::tests::resolution_score_higher_confidence_wins_all_else_equal ... ok +test conflict::tests::resolution_score_newer_wins_all_else_equal ... ok +test conflict::tests::resolve_conflict_invalidates_loser_side ... ok +test conflict::tests::resolve_conflict_is_idempotent ... ok +test conflict::tests::resolve_conflict_rejects_invalid_resolution_strings ... ok +test analytics::tests::citation_stats_rate_correct ... ok +test conflict::tests::similar_but_different_text_is_flagged ... ok +test consolidate::tests::apply_merge_preserves_evidence_without_counting_copies_as_independent ... ok +test consolidate::tests::citations_reassigned_on_merge ... ok +test consolidate::tests::consolidation_is_rebuild_safe ... ok +test consolidate::tests::cosine_dim_mismatch_returns_zero ... ok +test consolidate::tests::cosine_empty_returns_zero ... ok +test consolidate::tests::cosine_opposite_is_minus_one ... ok +test consolidate::tests::cosine_orthogonal_is_zero ... ok +test consolidate::tests::cosine_same_vector_is_one ... ok +test consolidate::tests::find_distill_clusters_requires_shared_tags ... ok +test consolidate::tests::find_distill_clusters_shared_tag_and_band_clusters ... ok +test consolidate::tests::find_merge_clusters_different_models_do_not_cluster ... ok +test consolidate::tests::find_merge_clusters_identical_vectors_cluster ... ok +test consolidate::tests::find_merge_clusters_orthogonal_no_clusters ... ok +test consolidate::tests::merge_preserves_scope_kind_and_distinct_claims ... ok +test consolidate::tests::merge_similarity_chain_cannot_bridge_distant_members ... ok +test consolidate::tests::parse_tags_case_insensitive_key ... ok +test consolidate::tests::parse_tags_deduplicates ... ok +test consolidate::tests::parse_tags_extracts_tags ... ok +test consolidate::tests::parse_tags_no_block_returns_empty ... ok +test consolidate::tests::run_reflection_creates_proposal_from_cluster ... ok +test consolidate::tests::run_reflection_without_model_reports_only ... ok +test consolidate::tests::stale_merge_plan_does_not_retire_any_member ... ok +test consolidate::tests::superseded_row_excluded_from_latest_memory_candidates ... ok +test consolidate::tests::survivor_is_highest_usefulness_score ... ok +test consolidate::tests::v2_brain_migrates_to_v3_with_backup_and_superseded_by_column ... ok +test context::delivery::tests::final_serialization_bounds_unicode_identifiers_and_escaping ... ok +test context::delivery::tests::repeated_playbook_text_is_included_in_final_bound ... ok +test context::delivery::tests::tiny_budget_reports_actual_error_cost_and_no_exposure ... ok +test context::evidence_tests::a_complete_bundle_gets_no_notice ... ok +test context::evidence_tests::a_partial_bundle_names_what_is_missing_and_tells_the_reader_what_to_do ... ok +test context::evidence_tests::a_term_the_corpus_has_never_seen_counts_as_a_gap ... ok +test context::evidence_tests::a_ubiquitous_term_carries_no_weight ... ok +test context::evidence_tests::a_vowel_y_is_not_stripped ... ok +test context::evidence_tests::an_empty_or_skipped_bundle_gets_no_notice ... ok +test context::evidence_tests::an_empty_query_does_not_claim_a_gap ... ok +test context::evidence_tests::an_inflected_corpus_term_counts_as_covered ... ok +test context::evidence_tests::an_ordering_query_returns_a_dated_chronological_bundle ... ok +test context::evidence_tests::an_ordinary_query_is_untouched ... ok +test context::evidence_tests::an_unmeasurable_query_does_not_claim_a_gap ... ok +test context::evidence_tests::coverage_is_collective_not_per_capsule ... ok +test context::evidence_tests::full_coverage_names_nothing ... ok +test context::evidence_tests::ordering_changes_the_rendering_not_the_selection ... ok +test context::evidence_tests::partial_coverage_names_the_missing_terms ... ok +test context::evidence_tests::short_words_keep_their_ending ... ok +test context::evidence_tests::the_dates_are_counted_against_the_budget ... ok +test context::evidence_tests::the_notice_caps_how_many_terms_it_names ... ok +test context::evidence_tests::the_original_suffix_rules_still_hold ... ok +test context::evidence_tests::the_y_ies_pair_shares_a_stem ... ok +test context::hardening_tests::hardening_hydration_binds_text_revision_before_later_correction ... ok +test context::hardening_tests::hardening_idf_counts_prefix_documents_not_occurrences_or_substrings ... ok +test context::hardening_tests::hardening_live_lexical_and_recency_apply_both_time_bounds ... ok +test context::tests::abstain_evidence_gate_skips_on_weak_absolute_evidence ... ok +test context::tests::aged_cited_memory_does_not_decay_when_half_life_is_zero ... ok +test context::tests::aged_cited_memory_ranks_below_recently_cited_memory ... ok +test context::tests::band_arbitration_follows_the_cross_encoder ... ok +test context::tests::band_arbitration_never_converts_out_of_band_bundles ... ok +test context::tests::band_arbitration_uses_raw_evidence_before_policy_cap ... ok +test context::tests::band_arbitration_uses_raw_rerank_evidence ... ok +test context::tests::band_fails_closed_without_a_reranker ... ok +test context::tests::band_spares_bundles_with_repo_evidence ... ok +test context::tests::boost_gain_is_capped_so_cited_junk_cannot_beat_relevant_uncited ... ok +test context::tests::boost_still_reorders_within_a_relevance_band ... ok +test context::tests::capsule_matches_kind_reads_memory_summary_prefix ... ok +test context::tests::classify_task_maps_each_kind_deterministically ... ok +test context::tests::classify_task_respects_precedence_order ... ok +test context::tests::compress_for_render_caps_sentences ... ok +test context::tests::compress_for_render_empty_input_safe ... ok +test context::tests::compress_for_render_long_memory_reduces_tokens_by_25_percent ... ok +test context::tests::compress_for_render_preserves_scope_prefix ... ok +test context::tests::compress_for_render_short_text_unchanged ... ok +test context::tests::compress_for_render_strips_context_suffix ... ok +test context::tests::compress_for_render_strips_tags_prefix ... ok +test context::tests::compress_for_render_utf8_safe ... ok +test context::tests::compress_for_render_zero_max_sentences_returns_original ... ok +test context::tests::content_tokens_strips_stopwords_keeps_topical_words ... ok +test analytics::tests::corpus_health_counts_active_vs_invalidated ... ok +test context::tests::global_normalization_keeps_relevance_comparable_across_kinds ... ok +test context::tests::hardening_freshness_has_thirty_day_half_life ... ok +test context::tests::hardening_rerank_preserves_decayed_usefulness_and_trust ... ok +test context::tests::hardening_usefulness_cannot_dominate_relevance ... ok +test context::tests::hybrid_retrieval_skips_cosine_on_model_id_mismatch ... ok +test context::tests::debug_surfaces_more_failure_pattern_than_docs ... ok +test context::tests::hybrid_retrieval_uses_cosine_score_to_rerank ... ok +test context::tests::jaccard_is_one_for_identical_sets ... ok +test context::tests::jaccard_is_zero_for_disjoint_sets ... ok +test context::tests::jaccard_partial_overlap ... ok +test context::tests::hybrid_retrieval_with_noop_embedder_is_lexical_only ... ok +test context::tests::lean_noop_embedder_uses_fts_then_recency_unchanged ... ok +test context::tests::lexical_floor_drops_offtopic_memories_sharing_project_name ... ok +test context::tests::light_stem_strips_one_inflection_suffix ... ok +test context::tests::penalty_side_remains_multiplicative ... ok +test context::tests::per_kind_normalization_flatters_the_best_of_a_weak_kind ... ok +test context::tests::query_tokens_expands_build_class ... ok +test context::tests::query_tokens_expands_edit_class ... ok +test context::tests::query_tokens_expands_search_class ... ok +test context::tests::query_tokens_no_expansion_on_unrelated_query ... ok +test context::tests::rerank_capsules_cap_truncates ... ok +test context::tests::rerank_capsules_empty_input_returns_empty ... ok +test context::tests::rerank_capsules_fail_open_preserves_input_order ... ok +test context::tests::rerank_capsules_floor_drops_zero_overlap ... ok +test context::tests::rerank_capsules_reorders_by_query_overlap ... ok +test context::tests::rerank_reapplies_usefulness_but_not_to_superseded_capsules ... ok +test context::tests::resolve_capsule_file_caps_large_file ... ok +test context::tests::resolve_capsule_file_rejects_absolute_path ... ok +test context::tests::resolve_capsule_file_returns_bounded_content ... ok +test context::tests::lexical_floor_keeps_ontopic_memory ... ok +test context::tests::resolve_capsule_malformed_handle_returns_err ... ok +test context::tests::resolve_capsule_memory_returns_full_text ... ok +test context::tests::resolve_capsule_memory_missing_id_returns_err ... ok +test context::tests::resolve_capsule_run_handle_returns_deferred_err ... ok +test context::tests::resolve_capsule_unknown_handle_returns_err ... ok +test context::tests::summary_token_set_lowercases_and_filters_short ... ok +test context::tests::supersession_ignores_distinct_memories_and_applies_once ... ok +test context::tests::supersession_is_inert_without_embeddings_or_timestamps ... ok +test context::tests::supersession_penalizes_the_older_near_duplicate ... ok +test context::tests::supersession_penalty_survives_reranking ... ok +test context::tests::stemmed_query_matches_inflected_corpus_through_floor ... ok +test context::tests::unknown_normalization_falls_back_to_per_kind ... ok +test context::tests::usefulness_decay_disabled_when_half_life_is_zero_or_negative ... ok +test context::tests::usefulness_decay_falls_back_to_created_at_when_last_useful_is_none ... ok +test context::tests::usefulness_decay_follows_half_life_curve ... ok +test context::tests::usefulness_decay_full_at_zero_age ... ok +test context::tests::usefulness_decay_returns_one_on_unparseable_timestamps ... ok +test context::tests::usefulness_multiplier_blends_smoothly_in_transition ... ok +test context::tests::usefulness_multiplier_clamps_to_envelope ... ok +test context::tests::usefulness_multiplier_maps_ratio_onto_envelope ... ok +test context::tests::task_kind_feature_is_retrieval_neutral ... ok +test context::tests::usefulness_multiplier_neutral_at_zero_uses ... ok +test context::tests::weighted_coverage_ignores_zero_idf_tokens ... ok +test context::tests::weights_for_task_kind_debug_up_freshness_fraction ... ok +test context::tests::weights_for_task_kind_feature_is_unchanged ... ok +test context::tests::weights_for_task_kind_refactor_up_scope_fraction ... ok +test context::tests::weights_for_task_kind_renormalizes_to_unit_sum ... ok +test digest::tests::digest_size_within_400_token_budget ... ok +test analytics::tests::harvest_stats_by_source_and_yield ... ok +test analytics::tests::log_telemetry_event_writes_context_served_to_db ... ok +test analytics::tests::proposal_stats_acceptance_rate_and_pending ... ok +test analytics::tests::retrieval_stats_all_hits_skip_rate_zero ... ok +test analytics::tests::retrieval_stats_counts_hits_and_misses ... ok +test analytics::tests::retrieval_stats_no_context_served_events_returns_none ... ok +test analytics::tests::superseded_memory_excluded_from_active_count ... ok +test analytics::tests::token_economy_all_old_events_returns_none ... ok +test analytics::tests::token_economy_averages_new_events_and_tolerates_old_events ... ok +test digest::tests::record_warmstart_served_is_best_effort ... ok +test analytics::tests::usefulness_trend_gate_failure_excluded_from_window_net ... ok +test conflict::tests::conflict_detection_enabled_config_false_when_env_unset ... ok +test conflict::tests::conflict_detection_enabled_env_disable_overrides_config_true ... ok +test drift::tests::a_return_to_topic_resets_the_run ... ok +test drift::tests::a_session_that_holds_its_topic_does_not_drift ... ok +test conflict::tests::resolve_conflicts_enabled_env_disable_overrides_config_true ... ok +test drift::tests::a_slow_walk_away_from_the_opening_turn_is_still_drift ... ok +test drift::tests::a_sustained_run_marks_where_the_session_turned ... ok +test conflict::tests::off_switch_prevents_conflict_detection ... ok +test drift::tests::an_empty_session_reports_nothing ... ok +test drift::tests::one_off_anchor_turn_is_not_drift ... ok +test drift::tests::the_anchor_turn_is_never_the_drift_point ... ok +test dropped_capsule::tests::match_and_remove_finds_and_removes ... ok +test dropped_capsule::tests::match_and_remove_on_empty_is_safe ... ok +test dropped_capsule::tests::match_and_remove_returns_none_when_absent ... ok +test dropped_capsule::tests::prune_window_caps_at_max_entries ... ok +test dropped_capsule::tests::prune_window_empty_input_is_safe ... ok +test dropped_capsule::tests::prune_window_keeps_boundary_entry ... ok +test dropped_capsule::tests::prune_window_removes_old_entries ... ok +test dropped_capsule::tests::save_is_atomic_no_tmp_leftover ... ok +test embeddings::checked_serving_loader_tests::failed_requested_model_is_not_an_intentional_lexical_measurement ... ok +test embeddings::configured_reranker_tests::configured_cache_reuses_model_and_off_never_loads ... ok +test drift::tests::a_single_turn_session_is_not_reported ... ok +test embeddings::explicit_embedder_tests::aliases_and_disable_have_one_effective_model_identity ... ok +test drift::tests::turns_come_back_in_order_grouped_by_session ... ok +test drift::tests::sessions_without_stored_queries_are_absent_not_clean ... ok +test embeddings::tests::builtin_models_table_is_consistent ... ok +test embeddings::tests::cosine_similarity_handles_edge_cases ... ok +test embeddings::tests::cosine_similarity_is_symmetric ... ok +test embeddings::tests::decode_embedding_rejects_dim_mismatch ... ok +test drift::tests::turns_without_a_session_id_are_skipped ... ok +test embeddings::tests::decode_embedding_rejects_unaligned_blob ... ok +test embeddings::tests::embed_batch_empty_is_empty ... ok +test embeddings::tests::embed_batch_length_matches_input ... ok +test embeddings::tests::embed_batch_matches_per_row ... ok +test embeddings::tests::encode_decode_embedding_round_trip ... ok +test embeddings::tests::map_builtin_id_maps_aliases_and_defaults_unknown ... ok +test embeddings::tests::noop_embedder_returns_not_implemented_and_is_noop ... ok +test embeddings::tests::open_default_embedder_returns_noop_on_default_build ... ok +test embeddings::tests::resolve_embedder_id_uses_config_when_env_unset ... ok +test embeddings::tests::runtime_threads_are_explicit_bounded_and_invalid_values_are_errors ... ok +test embeddings::tests::stub_embedder_distinguishes_disjoint_inputs ... ok +test embeddings::tests::stub_embedder_handles_empty_input ... ok +test embeddings::tests::stub_embedder_is_deterministic ... ok +test embeddings::tests::stub_reranker_empty_query_returns_floor ... ok +test embeddings::tests::stub_reranker_higher_overlap_scores_higher ... ok +test embeddings::tests::stub_reranker_model_id ... ok +test embeddings::tests::stub_reranker_returns_doc_order_scores ... ok +test embeddings::correction_race_tests::slow_embedding_cannot_overwrite_a_newer_correction ... ok +test digest::tests::cache_is_reused_on_second_call ... ok +test digest::tests::digest_with_memories_is_bounded ... ok +test episode::tests::episode_inserts_lesson_from_edges ... ok +test episode::tests::project_episode_round_trip ... ok +test episode::tests::rebuild_in_place_reprojects_episodes ... ok +test episode::tests::render_resume_context_formats_episode ... ok +test episode::tests::reset_projection_clears_episodes ... ok +test episode::tests::rule_based_episode_parses_transcript ... ok +test episode::tests::second_episode_supersedes_first ... ok +test eval::tests::delivered_metrics_separate_fraction_hit_and_known_negative_accuracy ... ok +test eval::tests::mean_all_ones ... ok +test eval::tests::mean_empty_is_zero ... ok +test eval::tests::mean_normal ... ok +test eval::tests::mean_single ... ok +test eval::tests::mrr_absent_is_zero ... ok +test eval::tests::mrr_empty_ranked_is_zero ... ok +test eval::tests::mrr_empty_relevant_is_zero ... ok +test eval::tests::mrr_first_position_is_one ... ok +test eval::tests::mrr_second_position_is_half ... ok +test eval::tests::mrr_third_position_is_one_third ... ok +test eval::tests::mrr_uses_first_hit_when_multiple_relevant ... ok +test eval::tests::recall_at_k_duplicates_in_ranked_count_once ... ok +test eval::tests::recall_at_k_exact_hits ... ok +test eval::tests::recall_at_k_k_larger_than_ranked_uses_full_list ... ok +test eval::tests::recall_at_k_negative_has_no_vacuous_quality_credit ... ok +test eval::tests::recall_at_k_no_hits_is_zero ... ok +test eval::tests::recall_at_k_zero_k_is_zero ... ok +test eval::tests::resolution_correct_empty_relevant_is_false ... ok +test eval::tests::resolution_correct_relevant_above_stale_is_true ... ok +test eval::tests::resolution_correct_relevant_absent_is_false ... ok +test eval::tests::resolution_correct_stale_above_relevant_is_false ... ok +test eval::tests::resolution_correct_stale_absent_is_true ... ok +test eval::tests::stale_hit_rate_no_stale_is_zero ... ok +test eval::tests::stale_hit_rate_stale_absent_is_zero ... ok +test eval::tests::stale_hit_rate_stale_beyond_k_is_zero ... ok +test eval::tests::stale_hit_rate_stale_in_top_k_is_one ... ok +test framing::tests::every_framing_states_which_side_wins_a_conflict ... ok +test framing::tests::the_framing_does_not_hedge_the_memory_itself ... ok +test framing::tests::the_proactive_framing_stays_short ... ok +test fusion::tests::fuse_dispatches_on_the_mode ... ok +test fusion::tests::fusion_parses_and_falls_back_to_linear ... ok +test fusion::tests::rrf_is_deterministic_on_ties ... ok +test fusion::tests::rrf_normalizes_the_top_score_to_one ... ok +test fusion::tests::rrf_over_one_list_preserves_its_order ... ok +test fusion::tests::rrf_rewards_agreement_between_lists ... ok +test fusion::tests::union_max_keeps_the_best_instance_of_each_candidate ... ok +test graph::tests::build_edges_excludes_superseded ... ok +test graph::tests::build_edges_links_shared_entity_and_skips_unrelated ... ok +test graph::tests::build_edges_persist_roundtrip ... ok +test graph::tests::entity_source_prefers_the_author_supplied_tag ... ok +test graph::tests::extract_entities_is_sorted_and_deduped ... ok +test graph::tests::extract_entities_picks_tags_and_salient_terms ... ok +test graph::tests::incremental_edges_link_a_new_memory_to_its_neighbours ... ok +test graph::tests::incremental_edges_need_more_than_one_shared_entity ... ok +test digest::tests::empty_brain_returns_none ... ok +test graph::tests::incremental_edges_respect_the_fan_out_cap ... ok +test graph::tests::incremental_edges_skip_inactive_neighbours ... ok +test graph::tests::project_entities_replaces_rather_than_accumulates ... ok +test graph::tests::reproject_all_entities_backfills_an_existing_corpus ... ok +test hardening_evidence_tests::hardening_archive_restore_replay_preserves_expiry_and_invalidity ... ok +test hardening_evidence_tests::hardening_concurrent_episode_lanes_replay ... ok +test hardening_evidence_tests::hardening_explicit_revision_cannot_override_actual_delivery ... ok +test digest::tests::force_rebuild_bypasses_cache ... ok +test hardening_evidence_tests::hardening_identity_uses_first_usable_task_session_or_worktree ... ok +test hardening_evidence_tests::hardening_manual_conflict_rejection_cannot_restore_archived_loser ... ok +test hardening_evidence_tests::hardening_manual_conflict_replay_and_atomic_validation ... ok +test hardening_evidence_tests::hardening_mixed_revision_run_is_not_current_claim_evidence ... ok +test hardening_evidence_tests::hardening_partial_episode_preserves_explicit_lane ... ok +test hardening_evidence_tests::hardening_roi_labels_assumptions_and_keeps_delivery_units_separate ... ok +test ingest::tests::effective_ingest_limits_clamp_hostile_project_config ... ok +test ingest::tests::index_file_redacts_secrets_in_snippet ... ok +test ingest::tests::read_file_capped_rejects_oversized_content ... ok +test inject_policy::tests::a_policy_round_trips_through_json ... ok +test inject_policy::tests::a_small_dataset_does_not_move_the_policy ... ok +test inject_policy::tests::a_wrong_shaped_policy_is_invalid ... ok +test inject_policy::tests::an_unexercised_surface_is_omitted_rather_than_scored_zero ... ok +test inject_policy::tests::feature_names_line_up_with_the_vector ... ok +test inject_policy::tests::history_from_before_surfaces_is_dropped_not_defaulted ... ok +test inject_policy::tests::only_citations_after_the_injection_count ... ok +test inject_policy::tests::sigmoid_is_stable_at_the_extremes ... ok +test inject_policy::tests::single_class_data_does_not_move_the_policy ... ok +test inject_policy::tests::suppressed_injections_do_not_count_against_a_surface ... ok +test inject_policy::tests::surface_strings_round_trip ... ok +test inject_policy::tests::surfaces_are_scored_separately ... ok +test inject_policy::tests::the_prior_ignores_every_untrained_feature ... ok +test inject_policy::tests::the_prior_reproduces_the_legacy_threshold ... ok +test inject_policy::tests::training_can_also_make_the_policy_speak_sooner ... ok +test inject_policy::tests::training_moves_the_boundary_towards_the_evidence ... ok +test digest::tests::hardening_warm_digest_excludes_invalid_time_and_other_task_focus ... ok +test digest::tests::hardening_warm_digest_revalidates_corrected_and_retired_claims ... ok +test digest::tests::hardening_warm_profile_honors_user_brain_opt_out ... ok +test lifecycle::tests::forgetting_uses_meaningful_recency_and_not_harmful_popularity ... ok +test digest::tests::is_stale_false_after_build ... ok +test lifecycle::tests::hardening_archive_scan_revalidates_correction_and_recent_use ... ok +test lifecycle::tests::invalidation_reason_as_str_round_trips ... ok +test lifecycle::tests::invalidation_reason_legacy_strings_parse_correctly ... ok +test digest::tests::is_stale_true_when_no_cache ... ok +test digest::tests::load_cached_digest_serves_stale_text ... ok +test lock::tests::concurrent_acquire_serializes ... ok +test lock::tests::corrupt_lock_is_reclaimed ... ok +test embeddings::tests::env_disables_embedder_recognizes_off_values ... ok +test embeddings::tests::pick_builtin_model_from_env_handles_aliases ... ok +test lock::tests::process_alive_current_is_alive ... ok +test embeddings::tests::w3_embedder_enabled_for_config_false_when_env_unset ... ok +test embeddings::tests::w3_embedder_env_disable_overrides_config_true ... ok +test embeddings::tests::w3_embedder_env_model_id_overrides_config_false ... ok +test maintain::tests::a_backwards_clock_does_not_wedge_the_schedule ... ok +test maintain::tests::a_pass_becomes_due_again_after_its_interval ... ok +test maintain::tests::a_pass_that_just_ran_is_not_due ... ok +test maintain::tests::everything_is_due_on_a_brain_that_has_never_run_upkeep ... ok +test maintain::tests::pass_names_round_trip ... ok +test migrate::tests::applies_single_migration ... ok +test maintain::tests::state_round_trips_and_a_corrupt_file_makes_everything_due ... ok +test digest::tests::warm_start_block_respects_gate_and_renders_digest ... ok +test migrate::tests::backup_brain_custom_path ... ok +test migrate::tests::backup_brain_default_path_does_not_overwrite_existing_backup ... ok +test migrate::tests::idempotent_rerun ... ok +test migrate::tests::migrate_v7_forward_adds_origin_and_hlc ... ok +test migrate::tests::backup_brain_default_path_exists_and_valid ... ok +test migrate::tests::multi_step_chain ... ok +test migrate::tests::no_backup_for_in_memory_db ... ok +test migrate::tests::noop_when_at_target ... ok +test migrate::tests::rejects_newer_db ... ok +test migrate::tests::retention_keep_3 ... ok +test migrate::tests::rollback_on_failing_migration ... ok +test ordering::tests::a_prefixless_summary_is_dated_at_the_front ... ok +test ordering::tests::capsules_are_reordered_by_time_and_dated ... ok +test ordering::tests::equal_timestamps_keep_the_brokers_order ... ok +test ordering::tests::markers_match_whole_words_only ... ok +test ordering::tests::ordering_questions_are_recognised ... ok +test ordering::tests::ordinary_questions_are_not_ordering_questions ... ok +test ordering::tests::reordering_never_adds_or_drops_a_capsule ... ok +test ordering::tests::the_date_survives_the_hooks_summary_stripping ... ok +test ordering::tests::the_token_estimate_accounts_for_the_date_prefix ... ok +test ordering::tests::undated_capsules_are_kept_after_the_timeline ... ok +test migrate::tests::no_backup_for_noop ... ok +test migrate::tests::backup_created_for_file_db ... ok +test lock::tests::process_alive_dead_pid_is_dead ... ok +test maintain::tests::passes_are_best_effort_against_a_missing_brain ... ok +test episode::tests::capture_episode_end_to_end ... ok +test lock::tests::stale_lock_dead_pid_is_reclaimed ... ok +test graph::tests::recording_memories_links_them_without_a_manual_graph_build ... ok +test hardening_evidence_tests::hardening_exposure_no_invention_stale_feedback_and_unknown_outcome ... ok +test lifecycle::tests::forget_brain_apply_invalidates_noise_keeps_signal ... ok +test lock::tests::live_held_lock_times_out ... ok +test project::tests::apply_export_redaction_both_flags_strips_tags_and_context ... ok +test project::tests::apply_export_redaction_no_flags_is_passthrough ... ok +test project::tests::apply_export_redaction_redact_only_strips_context ... ok +test lifecycle::tests::forget_brain_dry_run_identifies_noise_keeps_signal ... ok +test lifecycle::tests::forget_protects_recently_retrieved_via_last_used_at ... ok +test lifecycle::tests::gc_proposals_expires_old_pending_keeps_fresh ... ok +test lifecycle::tests::invalidations_by_reason_groups_structured_reasons ... ok +test lifecycle::tests::regret_flagged_memories_flags_above_threshold ... ok +test project::tests::a_quarantined_pack_reaches_the_review_queue_and_not_retrieval ... ok +test project::tests::abort_run_already_finished_returns_err ... ok +test project::tests::abort_run_stamps_aborted_and_frees_lock ... ok +test project::tests::abort_run_unknown_id_returns_err ... ok +test project::tests::accepting_a_quarantined_proposal_admits_it ... ok +test project::tests::add_memories_batch_all_entries_same_embedding_model ... ok +test project::tests::add_memories_batch_deduplicates ... ok +test project::tests::add_memories_batch_present_retrievable_rebuild_safe ... ok +test project::tests::add_memory_distinct_texts_no_conflicts ... ok +test project::tests::add_memory_redacts_secrets_before_persist ... ok +test project::tests::at_root_init_and_round_trip_memory ... ok +test project::tests::at_root_init_is_idempotent ... ok +test project::tests::batch_review_accepts_filtered_subset_and_rejects_remainder ... ok +test project::tests::blame_run_separates_cited_from_silent_passengers ... ok +test project::tests::cite_outcome_survives_rebuild ... ok +test project::tests::compact_brain_default_preserves_everything ... ok +test project::tests::compact_brain_event_trim_keeps_materialized_memories ... ok +test project::tests::compact_brain_event_trim_then_rebuild_is_consistent ... ok +test project::tests::invalidate_memory_persists_invalidated_metadata_and_survives_rebuild ... ok +test project::tests::compact_brain_purge_invalidated_reclaims_space ... ok +test project::tests::detect_conflicts_env_off_writes_no_conflict_rows ... ok +test project::tests::edit_memory_changes_kind_only ... ok +test project::tests::list_memories_top_sorts_by_usefulness_ratio_and_drops_small_samples ... ok +test project::tests::list_proposals_filters_and_reject_records_reason ... ok +test project::tests::edit_memory_errors ... ok +test project::tests::edit_memory_updates_text_and_preserves_history ... ok +test project::tests::export_import_round_trip ... ok +test project::tests::export_redact_import_roundtrip_and_dedup ... ok +test project::tests::export_scope_kind_filter ... ok +test project::tests::fix2_search_excludes_superseded_rows ... ok +test project::tests::fix4_prune_excludes_superseded_rows ... ok +test project::tests::fix4_top_excludes_superseded_rows ... ok +test project::tests::import_dedup_on_second_import ... ok +test project::tests::import_pack_merge_replace_and_provenance ... ok +test project::tests::import_scope_override_global_user ... ok +test project::tests::import_skips_malformed_entries ... ok +test project::tests::invalidated_memory_is_excluded_from_broker_retrieval ... ok +test project::tests::load_project_rejects_future_config_version ... ok +test project::tests::redact_context_suffix_strips_trailing_context ... ok +test project::tests::redact_tags_prefix_strips_leading_tags ... ok +test project::tests::manual_regret_lowers_usefulness_and_confidence ... ok +test project::tests::memory_add_survives_projection_rebuild_from_trace ... ok +test project::tests::p0_global_user_add_memory_works_from_non_project_dir ... ok +test project::tests::repo_ingest_indexes_searchable_files_and_context_capsules ... ok +test project::tests::p0_global_user_honors_use_user_brain_false_when_start_is_project ... ok +test project::tests::run_aborted_does_not_update_usefulness ... ok +test project::tests::run_failed_decrements_usefulness_unless_gate ... ok +test project::tests::perf_tier1_structural_invariant_and_timing ... ok +test project::tests::prune_low_usefulness_dry_run_then_apply ... ok +test project::tests::quarantine_collapses_duplicates_within_a_pack ... ok +test project::tests::quarantine_does_not_re_propose_what_you_already_have ... ok +test project::tests::quarantine_does_not_reach_back_into_packs_already_installed ... ok +test project::tests::real_run_cite_does_not_bump_in_apply_memory_cited ... ok +test project::tests::rebuild_auto_fallback_imports_traces_when_events_table_empty ... ok +test project::tests::rebuild_from_events_table_restores_memories ... ok +test project::tests::rebuild_from_traces_flag_reimports_on_disk_traces ... ok +test project::tests::record_mcp_citation_writes_memory_citations_row ... ok +test project::tests::record_regret_writes_retrieval_regret_event ... ok +test project::tests::reindex_with_explicit_embedder_uses_that_model ... ok +test project::tests::retrieve_context_lexical_returns_fts_hits_without_embedder ... ok +test project::tests::w3_1_config_enabled_default_does_not_regress ... ok +test project::tests::retrieve_proactive_returns_actionable_kind_and_excludes_others ... ok +test project::tests::retrieve_with_injected_embedder_returns_fts_hits ... ok +test projector::correction_regressions::corpus_revision_observes_existing_embedding_updates_from_another_connection ... ok +test projector::correction_regressions::correction_history_separates_known_and_effective_time_and_replays ... ok +test projector::correction_regressions::correction_validation_rolls_back_events_text_and_fts ... ok +test projector::correction_regressions::delayed_run_evidence_stays_on_the_retiring_claim ... ok +test projector::correction_regressions::explicit_unbound_exposure_never_credits_a_claim ... ok +test projector::tests::add_memory_edges_writes_and_survives_rebuild ... ok +test project::tests::run_finished_gives_weak_signal_to_silent_passenger_memories ... ok +test projector::tests::confidence_calibration_rewards_success_and_survives_rebuild ... ok +test projector::tests::empty_payload_memory_accepted ... ok +test projector::tests::empty_payload_memory_cited ... ok +test projector::tests::empty_payload_memory_invalidated ... ok +test projector::tests::empty_payload_memory_proposed ... ok +test projector::tests::empty_payload_memory_rejected ... ok +test projector::tests::empty_payload_memory_temporal ... ok +test projector::tests::empty_payload_run_aborted ... ok +test projector::tests::empty_payload_run_failed ... ok +test projector::tests::empty_payload_run_finished ... ok +test projector::tests::concurrent_manual_regrets_lose_no_updates ... ok +test projector::tests::empty_payload_run_started ... ok +test projector::tests::empty_payload_work_episode ... ok +test projector::tests::event_carries_and_roundtrips_origin ... ok +test projector::tests::failure_penalty_scales_with_prior_citations ... ok +test projector::tests::initial_usefulness_seeds_score_and_survives_rebuild ... ok +test projector::tests::memory_cited_redacts_event_and_projection_rationale ... ok +test projector::tests::memory_proposed_redacts_event_and_projection_payloads ... ok +test projector::tests::memory_temporal_stamps_validity_and_survives_rebuild ... ok +test projector::tests::rebuild_import_failure_preserves_existing_projection ... ok +test projector::tests::rebuild_import_keeps_durable_events_missing_from_trace ... ok +test projector::tests::rebuild_in_place_no_dup_events ... ok +test projector::tests::rebuild_in_place_payload_fidelity ... ok +test projector::tests::rebuild_in_place_reconstructs_citations ... ok +test projector::tests::rebuild_refuses_to_erase_unlogged_legacy_user_memory ... ok +test projector::tests::reset_projection_keeps_events ... ok +test projector::tests::trace_import_binds_historical_exposure_before_later_correction ... ok +test projector::tests::trace_import_replays_missing_correction_before_later_invalidation ... ok +test projector::tests::upcast_is_identity_at_v1 ... ok +test projector::tests::well_formed_run_started_projects_correctly ... ok +test redact::tests::anthropic_oauth_token_is_redacted ... ok +test redact::tests::bearer_token_in_curl_log_is_redacted ... ok +test redact::tests::clean_text_round_trips_untouched ... ok +test redact::tests::generic_assignments_match_only_with_secret_looking_value ... ok +test redact::tests::github_pat_classic_and_fine_grained_redacted ... ok +test redact::tests::luhn_check ... ok +test redact::tests::match_offsets_point_into_original_text ... ok +test redact::tests::openai_key_is_redacted_without_shadowing_anthropic_prefix ... ok +test redact::tests::overlapping_matches_keep_first_only ... ok +test redact::tests::redaction_preserves_non_secret_surroundings ... ok +test redact::tests::scrub_for_export_leaves_technical_text_alone ... ok +test redact::tests::scrub_for_export_luhn_gates_credit_cards ... ok +test redact::tests::scrub_for_export_redacts_pii_and_credentials ... ok +test redact::tests::slack_aws_jwt_pem_google_all_redact ... ok +test redact::tests::summary_lists_unique_kinds ... ok +test redact::tests::url_embedded_credentials_are_redacted ... ok +test projector::tests::rebuild_reads_events_after_acquiring_writer_lock ... ok +test project::tests::run_finished_increments_usefulness_for_injected_memories ... ok +test project::tests::search_memories_paginates_and_filters_by_kind ... ok +test project::tests::set_age_backdates_created_at_and_survives_rebuild ... ok +test project::tests::standalone_cite_records_reliance_without_outcome_credit ... ok +test project::tests::undo_last_memory_invalidates_newest_first ... ok +test project::tests::undo_last_memory_on_empty_brain_returns_none ... ok +test reindex::tests::reindex_scope_parser_accepts_aliases ... ok +test project::tests::w1_4_add_memory_creates_no_run_dir_but_memory_and_runs_row_exist ... ok +test project::tests::w1_4_dedup_hit_creates_no_orphan_run_dir ... ok +test reinforce::tests::hardening_semantic_routes_ignore_unrelated_candidate_ids ... ok +test project::tests::w1_4_memory_ops_create_no_run_dirs ... ok +test project::tests::w1_4_memory_survives_rebuild_from_events_table_no_trace ... ok +test project::tests::w1_5_init_creates_kimetsu_dir_but_no_runs_dir ... ok +test roi::tests::estimate_output_tokens_quarter_ratio ... ok +test roi::tests::estimate_savings_all_kinds_covered ... ok +test roi::tests::estimate_savings_multi_kind ... ok +test roi::tests::estimate_savings_single_kind ... ok +test roi::tests::estimate_savings_zero_when_empty ... ok +test roi::tests::format_tokens_below_1000 ... ok +test roi::tests::format_tokens_thousands ... ok +test project::tests::w3_1_config_disabled_writes_null_embedding ... ok +test project::tests::w3_1_open_embedder_for_resolver ... ok +test roi::tests::resolve_price_known_model ... ok +test roi::tests::resolve_price_longest_prefix_wins ... ok +test roi::tests::resolve_price_override_wins ... ok +test roi::tests::resolve_price_unknown_model_none ... ok +test project::tests::w3_1_retrieval_fts_only_when_embedder_disabled ... ok +test reindex::tests::reindex_one_conn_backfills_null_embeddings ... ok +test reindex::tests::reindex_one_conn_batches_more_than_chunk_rows ... ok +test reindex::tests::reindex_one_conn_dry_run_does_not_mutate ... ok +test reindex::tests::reindex_one_conn_force_reembeds_current_model_rows ... ok +test reindex::tests::reindex_one_conn_limit_smaller_than_chunk_is_faithful ... ok +test reindex::tests::reindex_one_conn_skips_superseded_rows ... ok +test reindex::tests::reindex_one_conn_with_noop_embedder_returns_zero_candidates ... ok +test roi::tests::roi_window_parse ... ok +test roi::tests::savings_sentence_positive_no_usd ... ok +test roi::tests::savings_sentence_positive_with_usd ... ok +test reinforce::tests::co_cited_pair_staples_once_and_keeps_originals ... ok +test schema::tests::apply_pragmas_does_not_error_on_in_memory_conn ... ok +test schema::tests::apply_pragmas_sets_cache_size_on_rw_connection ... ok +test schema::tests::fresh_init_reaches_current_version_with_full_shape ... ok +test schema::tests::idempotent_initialize_twice ... ok +test schema::tests::idempotent_rerun_preserves_data ... ok +test schema::tests::v2_to_v3_migration_adds_superseded_by ... ok +test schema::tests::v3_to_v4_migration_adds_memory_edges ... ok +test schema::tests::v4_to_v5_migration_adds_work_episodes ... ok +test schema::tests::v5_to_v6_migration_adds_skill_proposals ... ok +test schema::tests::v6_to_v7_migration_adds_temporal_validity_columns ... ok +test schema::tests::validate_hard_errors_for_newer_db ... ok +test schema::tests::validate_ok_at_target ... ok +test schema::tests::validate_returns_needs_migration_for_older_db ... ok +test serving::tests::compression_keeps_the_value_that_justified_admission ... ok +test serving::tests::explicit_fact_guard_excludes_topic_match_before_output_cap ... ok +test reinforce::tests::grouped_citations_share_run_and_persist_query ... ok +test serving::tests::reranker_floor_and_final_serialization_reject_candidates_before_measurement ... ok +test skill_synthesis::tests::a_drafted_candidate_is_reported_as_pending_not_as_a_candidate ... ok +test skill_synthesis::tests::a_quiet_brain_gets_no_nudge ... ok +test skill_synthesis::tests::accept_already_decided_proposal_errors ... ok +test skill_synthesis::tests::accept_proposal_records_installed_path ... ok +test skill_synthesis::tests::an_accepted_proposal_ends_the_nudge ... ok +test skill_synthesis::tests::an_undrafted_candidate_is_surfaced_with_its_command ... ok +test skill_synthesis::tests::below_threshold_not_a_candidate ... ok +test skill_synthesis::tests::candidate_detected_at_citation_threshold ... ok +test skill_synthesis::tests::insert_and_list_pending_proposals ... ok +test skill_synthesis::tests::reject_proposal_marks_rejected ... ok +test skill_synthesis::tests::report_only_proposal_has_no_draft_content ... ok +test skill_synthesis::tests::staleness_check_flags_superseded_source ... ok +test skill_synthesis::tests::staleness_check_ok_for_live_source ... ok +test skill_synthesis::tests::superseded_memory_excluded_from_candidates ... ok +test sync::tests::cursor_advances_correctly ... ok +test sync::tests::directory_protocol_push_pull ... ok +test sync::tests::dry_run_import_does_not_write ... ok +test sync::tests::export_excludes_local_only_kinds ... ok +test sync::tests::export_redacts_secrets ... ok +test sync::tests::import_is_idempotent ... ok +test sync::tests::round_trip_export_import ... ok +test sync::tests::sync_archive_restore_round_trip ... ok +test sync::tests::sync_directory_failure_preserves_projection_and_pull_cursors ... ok +test sync::tests::sync_directory_merges_peer_dependencies_before_replay ... ok +test sync::tests::sync_import_counts_duplicate_lines_in_dry_run_and_commit ... ok +test sync::tests::sync_import_failure_rolls_back_entire_batch ... ok +test sync::tests::sync_import_refuses_to_erase_unlogged_memory ... ok +test reinforce::tests::route_below_min_support_does_not_fire ... ok +test sync::tests::sync_replays_historical_correction_before_local_retirement ... ok +test sync::tests::two_brains_converge_after_exchange ... ok +test trace::tests::gc_env_zero_disables_gc ... ok +test trace::tests::select_empty_returns_empty ... ok +test trace::tests::select_keep_larger_than_slice_selects_nothing ... ok +test trace::tests::select_keep_protects_newest ... ok +test trace::tests::select_mixed_age_and_keep ... ok +test trace::tests::select_newer_than_cutoff_not_selected ... ok +test trace::tests::select_older_than_cutoff_selected ... ok +test trace::tests::gc_fresh_dirs_all_survive ... ok +test trace::tests::trace_writer_create_env_zero_skips_gc ... ok +test trust::tests::audit_counts_the_unvetted_external_population ... ok +test trust::tests::audit_flags_a_write_burst_and_ignores_ordinary_writing ... ok +test trust::tests::audit_of_an_empty_brain_is_empty_not_an_error ... ok +test trust::tests::hardening_citation_retains_origin_penalty ... ok +test trust::tests::known_sources_classify ... ok +test trust::tests::the_enum_ordering_is_the_trust_ordering ... ok +test trust::tests::trust_never_exceeds_one ... ok +test trust::tests::unknown_provenance_reads_as_local ... ok +test tune::tests::all_combos_covers_the_full_grid ... ok +test tune::tests::compute_objective_formula ... ok +test tune::tests::compute_objective_with_regret_zero_rate_matches_base ... ok +test tune::tests::compute_objective_zero_cost_weight_is_just_mrr ... ok +test trace::tests::trace_writer_create_new_run_survives_gc ... ok +test tune::tests::even_full_historical_regret_is_diagnostic_only ... ok +test tune::tests::explicit_default_cost_policy_uses_budget_fraction_units ... ok +test tune::tests::historical_regret_cannot_change_candidate_objective ... ok +test tune::tests::model_advisor_no_recommendation_below_milestone ... ok +test tune::tests::model_advisor_recommends_at_milestone ... ok +test tune::tests::overlapping_aliases_and_task_families_never_leak_into_holdout ... ok +test reinforce::tests::routes_build_and_boost_is_bounded ... ok +test reinforce::tests::single_co_cite_does_not_staple ... ok +test roi::tests::per_memory_roi_respects_top_limit ... ok +test tune::tests::select_winner_picks_highest_objective ... ok +test tune::tests::single_family_cannot_supply_an_independent_holdout ... ok +test tune::tests::split_membership_is_independent_of_input_order ... ok +test tune::tests::train_holdout_split_80_20 ... ok +test tune::tests::train_holdout_split_empty ... ok +test tune::tests::tune_history_empty_when_no_file ... ok +test tune::tests::tune_history_entry_memory_count_roundtrip ... ok +test tune::tests::tune_history_roundtrip ... ok +test roi::tests::per_memory_roi_top_entries_sorted_by_savings ... ok +test roi::tests::roi_report_digest_served_adds_savings ... ok +test roi::tests::roi_report_empty_db_returns_zeros ... ok +test roi::tests::roi_report_negative_net_when_overhead_exceeds_savings ... ok +test roi::tests::roi_report_output_token_estimate_is_quarter_of_input ... ok +test roi::tests::roi_report_unknown_model_no_usd ... ok +test roi::tests::roi_report_usd_with_known_model ... ok +test roi::tests::roi_report_usd_with_override ... ok +test roi::tests::roi_report_with_citations_computes_savings ... ok +test roi::tests::session_roi_returns_none_when_no_citations ... ok +test serving::tests::production_and_eval_use_same_final_budget_and_arbitration_with_injected_models ... ok +test tune::tests::count_regret_events_zero_in_empty_db ... ok +test tune::tests::retune_trigger_corpus_milestone_when_enough_memories ... ok +test tune::tests::retune_trigger_drift_when_regret_rate_high ... ok +test tune::tests::retune_trigger_no_history_no_events ... ok +test tuneset::tests::build_personal_eval_deduplicates_same_query ... ok +test tuneset::tests::build_personal_eval_empty_when_no_queries_stored ... ok +test user_profile::tests::a_global_duplicate_is_not_repeated ... ok +test user_profile::tests::a_global_preference_is_marked_as_such ... ok +test user_profile::tests::an_empty_profile_renders_nothing ... ok +test user_profile::tests::hardening_profile_checks_numeric_start_and_expiry ... ok +test user_profile::tests::only_preference_memories_make_the_profile ... ok +test user_profile::tests::overlong_preferences_are_skipped_and_the_block_is_budgeted ... ok +test user_profile::tests::project_preferences_lead_and_globals_fill_the_remainder ... ok +test user_profile::tests::proven_preferences_come_first ... ok +test user_profile::tests::retired_preferences_are_excluded ... ok +test user_profile::tests::the_profile_is_capped ... ok +test user_profile::tests::the_profile_reads_as_instructions ... ok +test tuneset::tests::build_personal_eval_noise_when_no_citation_in_window ... ok +test tuneset::tests::build_personal_eval_positive_case_from_time_window ... ok +test tuneset::tests::exact_exposure_citation_labels_only_its_query_and_current_claim ... ok +test user_brain::tests::add_user_memory_persists_and_dedups ... ok +test user_brain::tests::fix5_dedup_does_not_collapse_onto_superseded_row ... ok +test user_brain::tests::migration_upgrades_user_brain_creates_backup_and_preserves_data ... ok +test user_brain::tests::open_user_brain_creates_db_on_first_call ... ok +test user_brain::tests::open_user_brain_readonly_returns_none_before_first_write ... ok +test user_brain::tests::open_user_brain_returns_none_when_disabled ... ok +test user_brain::tests::readonly_degrades_to_none_on_stale_schema ... ok +test user_brain::tests::user_brain_path_resolves_from_override_env ... ok +test user_brain::tests::w3_open_user_brain_env_disable_overrides_config_true ... ok +test user_brain::tests::w3_open_user_brain_env_enable_overrides_config_false ... ok +test user_brain::tests::w3_open_user_brain_for_config_false_returns_none ... ok +test user_brain::tests::w3_open_user_brain_for_config_true_opens_normally ... ok + +test result: ok. 707 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 106.12s + + Running unittests src\lib.rs (E:/Kimetsu/target\debug\deps\kimetsu_chat-ed412d525287af6c.exe) + +running 135 tests +test ask::tests::is_command_query_matches_how_do_i ... ok +test ask::tests::is_command_query_no_match_for_general_questions ... ok +test ask::tests::record_helpful_mark_no_panic_on_file_handles ... ok +test ask::tests::reorder_puts_command_first ... ok +test ask::tests::reorder_noop_for_non_command_query ... ok +test bridge::tests::aggregate_state_extension_counts_as_core ... ok +test ask::tests::verbatim_answer_labels_command_prominently ... ok +test ask::tests::compose_answer_refusal_when_no_brain ... ok +test bridge::tests::b1_claude_hooks_golden_shared_pretooluse_event ... ok +test bridge::tests::claude_hooks_install_session_end ... ok +test bridge::tests::b1_codex_hooks_golden_with_user_content ... ok +test bridge::tests::claude_hooks_merge_tolerates_utf8_bom ... ok +test bridge::tests::b1_mcp_config_golden_preserves_user_server ... ok +test bridge::tests::claude_hooks_include_sessionstart_warm ... ok +test bridge::tests::claude_hooks_merge_preserves_user_hooks ... ok +test bridge::tests::codex_hooks_merge_preserves_user_hooks ... ok +test bridge::tests::copy_dir_with_replace_refuses_symlink_destination ... ok +test bridge::tests::install_scope_parses_aliases ... ok +test bridge::tests::cursor_global_install_writes_to_home ... ok +test bridge::tests::b2_install_codex_workspace_preserves_user_content ... ok +test bridge::tests::merge_claude_md_fresh_file ... ok +test bridge::tests::install_preserves_existing_user_claude_md ... ok +test bridge::tests::cursor_workspace_install_writes_mcp_and_rules ... ok +test bridge::tests::b2_install_codex_global_preserves_user_content ... ok +test bridge::tests::cursor_uninstall_removes_mcp_entry ... ok +test bridge::tests::cursor_workspace_install_preserves_user_server ... ok +test bridge::tests::mcp_config_is_idempotent_and_scopes_keys ... ok +test bridge::tests::cursor_workspace_install_is_idempotent ... ok +test bridge::tests::cursor_status_detects_installed_workspace ... ok +test bridge::tests::merge_claude_md_preserves_user_content ... ok +test bridge::tests::merge_claude_md_idempotent ... ok +test bridge::tests::b2_install_claudecode_global_preserves_user_content ... ok +test bridge::tests::merge_claude_md_tolerates_bom ... ok +test bridge::tests::merge_claude_md_upgrades_in_place ... ok +test bridge::tests::b2_install_claudecode_workspace_preserves_user_content ... ok +test bridge::tests::merge_claude_md_repairs_begin_without_end ... ok +test bridge::tests::b3_upgrade_idempotency_claudecode_workspace ... ok +test bridge::tests::remote_install_rejects_unsupported_host ... ok +test bridge::tests::qq1_status_fresh_workspace_all_absent ... ok +test bridge::tests::u1_idempotent_on_clean_host ... ok +test bridge::tests::plugin_install_no_proactive_skips_tool_hooks ... ok +test bridge::tests::remote_install_literal_token_is_written ... ok +test bridge::tests::qq1_status_partial_claude_code_workspace ... ok +test bridge::tests::upsert_kimetsu_hook_preserves_user_groups_and_is_idempotent ... ok +test bridge::tests::qq1_status_user_content_not_detected_as_kimetsu ... ok +test commands::tests::non_slash_input_is_not_a_command ... ok +test bridge::tests::plugin_install_refreshes_generated_files_without_force ... ok +test commands::tests::parses_argument_commands ... ok +test commands::tests::parses_known_commands ... ok +test commands::tests::parses_strict_truthy_and_falsy ... ok +test commands::tests::parses_memory_and_skills_arguments ... ok +test commands::tests::unknown_slash_falls_through_to_agent ... ok +test cost::tests::budget_clamp_prevents_negative_budget ... ok +test cost::tests::new_meter_is_zeroed ... ok +test cost::tests::over_budget_triggers_when_crossed ... ok +test cost::tests::record_turn_accumulates_and_tracks_max ... ok +test cost::tests::record_turn_clamps_negative_input ... ok +test bridge::tests::remote_install_claude_writes_http_mcp_entry ... ok +test bridge::tests::plugin_install_writes_optional_and_required_modes ... ok +test bridge::tests::write_cursor_mcp_config_fresh_and_idempotent ... ok +test bridge::tests::qq1_status_after_claude_code_workspace_install ... ok +test bridge::tests::qq1_status_codex_workspace_install_and_partial ... ok +test bridge::tests::imports_and_exports_skill_bundle ... ok +test mcp_server::tests::brain_insights_appears_in_tool_definitions ... ok +test bridge::tests::plugin_install_global_writes_to_home_not_workspace ... ok +test bridge::tests::u1_roundtrip_codex_workspace ... ok +test mcp_server::tests::cite_tool_is_write_gated ... ok +test bridge::tests::u1_preserves_user_hook_on_shared_event ... ok +test mcp_server::tests::cite_tool_listed_and_write_gated ... ok +test bridge::tests::u1_roundtrip_claude_global ... ok +test mcp_server::tests::context_tool_catalog_advertises_episode_identity_lanes ... ok +test mcp_server::tests::dispatch_allowlist_blocks_unlisted_tool_call ... ok +test bridge::tests::u1_roundtrip_claude_workspace ... ok +test mcp_server::tests::dispatch_allowlist_filters_tools_list ... ok +test bridge::tests::qq1_status_install_then_uninstall_flips_to_absent ... ok +test mcp_server::tests::dispatch_no_allowlist_returns_full_catalog ... ok +test mcp_server::tests::brain_insights_reports_missing_project_without_error ... ok +test mcp_server::tests::global_plugin_install_is_not_available_through_mcp_helper ... ok +test mcp_server::tests::brain_status_reports_missing_project_without_error ... ok +test mcp_server::tests::dispatch_allowlist_permits_listed_tool_call ... ok +test mcp_server::tests::dispatch_remote_ignores_config_for_write_tools ... ok +test mcp_server::tests::benchmark_record_outcome_writes_retrievable_memory ... ok +test mcp_server::tests::benchmark_context_required_reports_missing_task_memory ... ok +test mcp_server::tests::dispatch_blocks_writes_when_config_disables_them ... ok +test mcp_server::tests::initialize_explains_kimetsu_workflow ... ok +test mcp_server::tests::lists_tools ... ok +test mcp_server::tests::benchmark_record_outcome_creates_pending_generalized_memory_proposal ... ok +test mcp_server::tests::brain_context_returns_memory_capsules ... ok +test mcp_server::tests::tool_required_arguments_are_declared_in_their_schema ... ok +test mcp_server::tests::write_tools_decision_precedence ... ok +test repl::tests::agent_registry_loads_markdown_agent ... ok +test repl::tests::build_chat_brain_context_keeps_only_tail_when_history_is_long ... ok +test repl::tests::build_chat_brain_context_renders_transcript_alone ... ok +test repl::tests::build_chat_brain_context_returns_none_when_no_state ... ok +test repl::tests::build_chat_brain_context_truncates_long_turns ... ok +test repl::tests::chat_route_distinguishes_conversation_from_workspace_work ... ok +test repl::tests::file_mentions_expand_existing_files ... ok +test repl::tests::file_mentions_reject_paths_outside_workspace ... ok +test repl::tests::greeting_is_handled_without_provider_or_cost ... ok +test repl::tests::hook_registry_ignores_workspace_hooks_by_default ... ok +test repl::tests::image_generation_is_model_gated ... ok +test repl::tests::input_history_moves_backward_and_forward ... ok +test repl::tests::mcp_registry_loads_json_servers ... ok +test repl::tests::non_slash_input_attempts_agent_round_trip ... ok +test repl::tests::quit_command_ends_session_cleanly ... ok +test repl::tests::rich_ui_renders_dragon_banner ... ok +test repl::tests::run_command_request_parses_terminal_flags ... ok +test repl::tests::skill_prefix_parses_name_and_prompt ... ok +test repl::tests::slash_clear_redraws_banner_and_keeps_session_alive ... ok +test repl::tests::slash_cost_shows_zero_at_start ... ok +test repl::tests::slash_goal_sets_and_recalls ... ok +test repl::tests::slash_help_prints_command_list ... ok +test repl::tests::slash_palette_filters_and_completes_commands ... ok +test repl::tests::slash_skills_lists_and_loads_workspace_skill ... ok +test repl::tests::slash_strict_toggles ... ok +test repl::tests::terminal_run_requires_interactive_terminal ... ok +test skills::tests::installs_external_skill_as_kimetsu_bundle ... ok +test skills::tests::loads_selected_skill_and_renders_context ... ok +test mcp_server::tests::benchmark_context_returns_playbook_and_enforces_task_memory ... ok +test skills::tests::resolve_contained_rejects_paths_outside_workspace_and_roots ... ok +test skills::tests::parses_codex_and_claude_skill_frontmatter ... ok +test mcp_server::tests::brain_context_tool_with_stub_reranker_reorders_and_caps ... ok +test mcp_server::tests::brain_insights_returns_well_formed_report ... ok +test mcp_server::tests::cite_tool_writes_memory_citations_row ... ok +test mcp_server::tests::configured_rerank_cutoff_controls_actual_mcp_admission ... ok +test ask::tests::compose_answer_refusal_with_empty_brain ... ok +test ask::tests::compose_answer_verbatim_or_composed_with_memory ... ok +test mcp_server::tests::hardening_benchmark_budget_recomputes_required_evidence ... ok +test mcp_server::tests::hardening_context_final_payload_excludes_rejected_and_is_bounded ... ok +test mcp_server::tests::hardening_failed_embedder_load_is_bounded_and_has_no_success_exposure ... ok +test mcp_server::tests::hardening_mcp_warm_identity_and_retry_after_budget_omission ... ok +test mcp_server::tests::hardening_normal_output_does_not_grow_with_rejected_summary_size ... ok +test mcp_server::tests::hardening_served_ids_and_revisions_match_final_mcp_payload ... ok +test mcp_server::tests::mcp_fact_guard_rejects_high_scoring_topic_and_respects_opt_out ... ok +test mcp_server::tests::stdio_uses_configured_reranker_off_and_initialization_error_explicitly ... ok + +test result: ok. 135 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 14.53s + + Running unittests src\main.rs (E:/Kimetsu/target\debug\deps\kimetsu-8414ed62c053df23.exe) + +running 268 tests +test ask::tests::is_command_query_delegated_correctly ... ok +test distiller::tests::normalize_distiller_provider_aws_alias ... ok +test distiller::tests::normalize_distiller_provider_bedrock_alias ... ok +test distiller::tests::distill_lessons_empty_on_model_error ... ok +test distiller::tests::normalize_distiller_provider_existing_aliases_unchanged ... ok +test distiller::tests::normalize_distiller_provider_ollama ... ok +test distiller::tests::distill_lessons_uses_model_text ... ok +test distiller::tests::parse_lessons_extracts_array_and_defaults ... ok +test distiller::tests::parse_lessons_handles_brackets_inside_strings ... ok +test distiller::tests::parse_lessons_ignores_trailing_prose_and_brackets ... ok +test distiller::tests::parse_lessons_temporal_does_not_break_caps ... ok +test distiller::tests::parse_lessons_tolerates_garbage ... ok +test distiller::tests::parse_lessons_with_temporal_tags ... ok +test distiller::tests::quality_gate_drops_too_long ... ok +test distiller::tests::quality_gate_drops_too_short ... ok +test distiller::tests::quality_gate_drops_transient_phrasing ... ok +test distiller::tests::quality_gate_passes_durable_lesson_without_embedder ... ok +test ask::tests::compose_answer_graceful_for_missing_workspace ... ok +test doctor::tests::doctor_report_aggregates_counts ... ok +test doctor::tests::outcome_glyphs_distinct ... ok +test doctor::tests::schema_mismatch_detection ... ok +test distiller::tests::build_transcript_view_streams_text_and_bounds ... ok +test doctor::tests::skew_fresh_server_is_pass ... ok +test doctor::tests::skew_mixed_stale_and_fresh_is_warn ... ok +test doctor::tests::skew_multiple_servers_no_start_time_is_warn ... ok +test doctor::tests::skew_no_binary_mtime_single_server_is_pass ... ok +test doctor::tests::skew_no_servers_is_pass ... ok +test doctor::tests::skew_single_server_no_start_time_is_pass ... ok +test doctor::tests::skew_stale_server_is_warn_with_restart_guidance ... ok +test doctor::tests::skew_wrong_exe_path_is_warn ... ok +test doctor::tests::doctor_detects_current_codex_hooks_json ... ok +test doctor::tests::doctor_rejects_legacy_pre_turn_scripts ... ok +test harvest_setup::tests::upsert_env_var_replaces_existing ... ok +test harvest_setup::tests::wizard_declined_writes_nothing ... ok +test harvest_setup::tests::wizard_unrecognized_harness_aborts ... ok +test harvest_setup::tests::wizard_unrecognized_provider_aborts ... ok +test harvest_setup::tests::wizard_accepts_codex_harness ... ok +test proactive_state::tests::dedupe_filter_mixed_keeps_new_only ... ok +test proactive_state::tests::dedupe_filter_never_empties_injection ... ok +test proactive_state::tests::dedupe_filter_passes_empty_handle_always ... ok +test harvest_setup::tests::wizard_accepts_openai_custom_model ... ok +test proactive_state::tests::dedupe_filter_passes_unsurfaced_handles ... ok +test proactive_state::tests::dedupe_filter_removes_surfaced_handles ... ok +test proactive_state::tests::dedupe_marks_once ... ok +test proactive_state::tests::error_signature_picks_the_error_line ... ok +test proactive_state::tests::failure_then_resolution_is_detectable ... ok +test proactive_state::tests::harvest_cue_throttle ... ok +test proactive_state::tests::loop_counter_reaches_threshold ... ok +test proactive_state::tests::dedupe_filter_survives_state_roundtrip ... ok +test proactive_state::tests::refractory_window ... ok +test harvest_setup::tests::wizard_writes_env_and_config ... ok +test proactive_state::tests::ring_buffer_is_bounded ... ok +test distiller::tests::quality_gate_drops_near_duplicate_passes_novel ... ok +test proactive_state::tests::session_path_sanitizes ... ok +test process::tests::classify_chat ... ok +test process::tests::classify_cli_fallback ... ok +test process::tests::classify_hook_variants ... ok +test proactive_state::tests::proactive_state_lands_in_cache_not_in_kimetsu ... ok +test process::tests::classify_mcp_serve_various_forms ... ok +test process::tests::csv_row_escaped_quote ... ok +test distiller::tests::quality_gate_preserves_similar_corrections_and_bounded_workarounds ... ok +test process::tests::csv_row_quoted_comma ... ok +test process::tests::locking_filter_empty_list_returns_empty ... ok +test process::tests::locking_filter_case_insensitive ... ok +test process::tests::locking_filter_excludes_procs_with_no_exe ... ok +test process::tests::locking_filter_no_match_returns_empty ... ok +test process::tests::stop_processes_never_kills_self ... ok +test process::tests::locking_filter_returns_matching_procs ... ok +test process::tests::unix_ps_empty_input ... ok +test process::tests::unix_ps_count_excludes_current_and_non_kimetsu ... ok +test process::tests::unix_ps_kinds ... ok +test process::tests::unix_ps_with_etimes_parses_started_at ... ok +test process::tests::unix_ps_without_etimes_falls_back_gracefully ... ok +test process::tests::unix_ps_workspace_extraction ... ok +test process::tests::windows_csv_count_excludes_current_pid ... ok +test process::tests::windows_csv_empty_input ... ok +test process::tests::windows_csv_header_only ... ok +test process::tests::windows_csv_kinds ... ok +test process::tests::windows_csv_malformed_rows_skipped ... ok +test process::tests::windows_csv_with_creation_date_parses_timestamp ... ok +test process::tests::windows_csv_workspace_extraction ... ok +test process::tests::wmi_datetime_epoch ... ok +test process::tests::wmi_datetime_handles_no_offset_suffix ... ok +test process::tests::wmi_datetime_negative_offset ... ok +test proactive_state::tests::save_is_atomic_no_tmp_leftover ... ok +test process::tests::wmi_datetime_positive_offset ... ok +test process::tests::wmi_datetime_returns_none_for_empty ... ok +test process::tests::wmi_datetime_returns_none_for_malformed ... ok +test process::tests::wmi_datetime_utc_zero_offset ... ok +test process::tests::workspace_flag_at_end_no_value ... ok +test process::tests::workspace_no_flag ... ok +test process::tests::workspace_quoted_with_spaces ... ok +test process::tests::workspace_simple ... ok +test remote_client::tests::render_result_extracts_text_content ... ok +test remote_client::tests::resolve_token_prefers_explicit ... ok +test skill_synth::tests::derive_skill_meta_fallback_when_no_draft ... ok +test skill_synth::tests::derive_skill_meta_uses_frontmatter_when_present ... ok +test skill_synth::tests::extract_frontmatter_parses_name_and_description ... ok +test skill_synth::tests::extract_frontmatter_returns_none_for_no_frontmatter ... ok +test skill_synth::tests::slugify_normalizes_whitespace_and_special_chars ... ok +test skill_synth::tests::write_skill_provenance_creates_valid_json ... ok +test tests::cli_smoke_config_get_parses_key ... ok +test tests::cli_smoke_config_set_help ... ok +test tests::cli_smoke_config_get_help ... ok +test tests::cli_smoke_config_set_parses_key_value ... ok +test tests::cli_smoke_runs_prune_parses_flags ... ok +test tests::cli_smoke_runs_prune_help ... ok +test tests::cli_smoke_setup_flags_parse ... ok +test tests::cli_smoke_setup_help_parses ... ok +test tests::cli_version_flag_contains_flavor ... ok +test doctor::tests::redact_smoke_passes_against_known_secret_string ... ok +test tests::context_hook_output_is_user_prompt_submit_json ... ok +test tests::count_brain_record_calls_handles_both_shapes ... ok +test tests::count_transcript_jsonl_streams_counts ... ok +test tests::fmt_bytes_kb ... ok +test tests::fmt_bytes_mb ... ok +test tests::fmt_bytes_sub_kb ... ok +test tests::get_toml_path_missing_returns_none ... ok +test tests::get_toml_path_nested_bool ... ok +test tests::get_toml_path_nested_string ... ok +test tests::get_toml_path_returns_table ... ok +test tests::hardening_free_never_requests_host_harvesting ... ok +test tests::config_set_text_drops_to_custom_only_for_managed_keys_under_a_preset ... ok +test distiller::tests::resolve_distiller_global_when_no_workspace ... ok +test tests::kimetsu_on_path_with_returns_false_for_empty_path ... ok +test tests::kimetsu_on_path_with_returns_false_for_none ... ok +test tests::kimetsu_on_path_with_returns_true_when_exe_dir_on_path ... ok +test tests::normalize_repo_id_handles_url_forms ... ok +test tests::parse_duration_bad_number ... ok +test tests::parse_duration_bad_unit ... ok +test tests::parse_duration_days ... ok +test tests::parse_duration_empty ... ok +test tests::parse_duration_hours ... ok +test tests::parse_duration_minutes ... ok +test tests::parse_duration_seconds ... ok +test tests::parse_openclaw_without_feature_returns_helpful_error ... ok +test tests::parse_pi_without_feature_returns_helpful_error ... ok +test tests::parse_scalar_coerce_to_integer_fails_on_non_numeric ... ok +test tests::parse_scalar_coerces_to_existing_bool ... ok +test tests::parse_scalar_coerces_to_existing_integer ... ok +test tests::parse_scalar_false_infers_bool ... ok +test tests::parse_scalar_float_infers_float ... ok +test tests::parse_scalar_integer_infers_integer ... ok +test tests::parse_scalar_negative_integer ... ok +test tests::parse_scalar_plain_string ... ok +test tests::parse_scalar_string_when_existing_is_string ... ok +test tests::parse_scalar_true_infers_bool ... ok +test tests::resolve_setup_hosts_auto_both_present ... ok +test tests::resolve_setup_hosts_auto_only_claude_present ... ok +test tests::resolve_setup_hosts_auto_only_codex_present ... ok +test tests::resolve_setup_hosts_bad_host_arg_returns_error ... ok +test tests::resolve_setup_hosts_explicit_both ... ok +test tests::resolve_setup_hosts_explicit_claude_code ... ok +test tests::resolve_setup_hosts_neither_present_non_tty_defaults_claude ... ok +test tests::resolve_setup_hosts_neither_present_tty_scripted_both ... ok +test tests::resolve_setup_hosts_neither_present_tty_scripted_codex ... ok +test tests::roundtrip_invalid_type_rejected_by_validation ... ok +test tests::roundtrip_set_embedder_enabled_false ... ok +test doctor::tests::ambient_collect_handles_non_git_dir_gracefully ... ok +test tests::select_both_keep_protects_even_old_runs ... ok +test tests::select_both_older_than_and_keep ... ok +test tests::select_empty_runs_list ... ok +test tests::select_keep_all_protected ... ok +test tests::select_keep_only ... ok +test tests::select_neither_flag_selects_nothing ... ok +test tests::select_older_than_exact_boundary ... ok +test tests::select_older_than_only ... ok +test distiller::tests::resolve_distiller_workspace_wins ... ok +test tests::served_event_payload_always_includes_query_hash ... ok +test tests::served_event_payload_has_required_fields ... ok +test tests::served_event_payload_hash_is_stable_for_same_query ... ok +test tests::served_event_payload_includes_raw_query_when_store_queries_true ... ok +test tests::served_event_payload_includes_session_id_when_present ... ok +test tests::served_event_payload_omits_session_id_when_absent ... ok +test tests::set_toml_edit_path_preserves_comments_and_unknown_keys ... ok +test tests::set_toml_path_creates_intermediate_tables ... ok +test tests::set_toml_path_replaces_existing_bool ... ok +test tests::set_toml_path_replaces_existing_integer ... ok +test distiller::tests::resolve_distiller_openai_workspace ... ok +test tests::stop_cue_suppressed_when_distiller_enabled ... ok +test tests::stop_harvest_cue_blocks_so_it_reaches_the_model ... ok +test tests::stop_hook_outputs_are_valid_json_objects ... ok +test tests::stop_hook_with_savings_outputs_are_valid_json_objects ... ok +test tests::stop_lessons_recorded_pluralizes ... ok +test tests::stop_lessons_recorded_with_savings_appends_sentence ... ok +test tests::stop_lessons_recorded_without_savings_unchanged ... ok +test tests::stop_no_lessons_with_savings_appends_sentence ... ok +test tests::stop_no_lessons_without_savings_unchanged ... ok +test tests::ulid_timestamp_ms_known_ulid ... ok +test tests::self_check_sees_installed_after_plugin_install ... ok +test tests::ulid_timestamp_ms_non_ulid ... ok +test tests::ulid_timestamp_ms_roundtrip ... ok +test tests::update_current_version_is_bare_semver ... ok +test tests::version_constant_contains_known_flavor ... ok +test tests::version_constant_starts_with_cargo_pkg_version ... ok +test tool_outcome::tests::a_cargo_compile_error_is_a_failure_and_reports_the_diagnostic ... ok +test tool_outcome::tests::a_failing_jest_run_is_a_failure ... ok +test tool_outcome::tests::a_failing_pytest_run_is_a_failure ... ok +test tool_outcome::tests::a_failing_rust_test_run_is_a_failure_with_a_signature ... ok +test tool_outcome::tests::a_nonzero_exit_still_borrows_the_toolchain_signature ... ok +test tool_outcome::tests::a_passing_jest_run_is_not_a_failure ... ok +test tool_outcome::tests::a_passing_pytest_run_is_not_a_failure ... ok +test tool_outcome::tests::a_passing_rust_test_run_is_not_a_failure ... ok +test tool_outcome::tests::a_test_named_error_handling_passing_is_not_a_failure ... ok +test tool_outcome::tests::an_explicit_success_marker_vetoes_the_substring_scan ... ok +test tool_outcome::tests::compiling_a_crate_named_error_chain_is_not_a_failure ... ok +test tool_outcome::tests::empty_output_without_an_exit_code_is_not_a_failure ... ok +test tool_outcome::tests::evidence_is_ordered_weakest_to_strongest ... ok +test tool_outcome::tests::exit_zero_beats_any_amount_of_scary_output ... ok +test tool_outcome::tests::go_test_failures_and_successes_are_distinguished ... ok +test tool_outcome::tests::nonzero_exit_is_a_failure_even_with_silent_output ... ok +test tool_outcome::tests::npm_and_make_failures_are_recognised ... ok +test tool_outcome::tests::tsc_diagnostics_are_recognised ... ok +test tool_outcome::tests::tsc_reporting_zero_errors_is_not_a_failure ... ok +test tool_outcome::tests::unstructured_failures_still_fall_through_to_the_substring_scan ... ok +test distiller::tests::distill_and_record_global_writes_to_user_brain ... ok +test tests::setup_init_and_install_claude_code_workspace ... ok +test update::tests::auto_flavor_falls_back_to_lean_for_intel_macos ... ok +test update::tests::checksum_manifest_parses_common_formats ... ok +test update::tests::error_message_does_not_say_elevated_shell ... ok +test update::tests::parse_locking_pids_excludes_current_pid ... ok +test update::tests::parse_locking_pids_extracts_matching_pid ... ok +test update::tests::parse_locking_pids_filters_different_path ... ok +test update::tests::parse_locking_pids_handles_multiple_rows ... ok +test update::tests::parse_locking_pids_returns_empty_on_empty_output ... ok +test update::tests::parse_locking_pids_returns_empty_on_header_only ... ok +test update::tests::preflight_empty_locking_list_returns_defer ... ok +test update::tests::preflight_force_flag_returns_defer_not_silent_stop ... ok +test update::tests::preflight_interactive_empty_line_returns_stop_default ... ok +test update::tests::preflight_interactive_n_returns_defer ... ok +test update::tests::preflight_interactive_no_returns_defer ... ok +test update::tests::preflight_interactive_yes_capital_returns_stop ... ok +test update::tests::preflight_interactive_yes_full_word_returns_stop ... ok +test update::tests::preflight_interactive_yes_returns_stop ... ok +test update::tests::preflight_multiple_procs_all_listed ... ok +test update::tests::preflight_non_tty_returns_defer ... ok +test update::tests::select_asset_matches_target_and_flavor ... ok +test update::tests::select_asset_requires_exact_project_release_asset ... ok +test update::tests::tier_interactive_choice_1_is_binary_only ... ok +test update::tests::tier_interactive_choice_2_is_with_plugins ... ok +test update::tests::tier_interactive_choice_3_empty_confirm_falls_back_to_with_plugins ... ok +test update::tests::tier_interactive_choice_3_with_correct_confirm_is_with_brains ... ok +test update::tests::tier_interactive_choice_3_wrong_confirm_falls_back_to_with_plugins ... ok +test update::tests::tier_interactive_delete_user_data_flag_preselects_3_and_needs_confirm ... ok +test update::tests::tier_interactive_empty_line_is_with_plugins ... ok +test update::tests::tier_interactive_keep_plugins_flag_preselects_1_on_empty_input ... ok +test update::tests::tier_interactive_unknown_choice_defaults_to_with_plugins ... ok +test update::tests::tier_non_interactive_default_is_with_plugins ... ok +test update::tests::tier_non_interactive_delete_user_data_is_with_brains ... ok +test update::tests::tier_non_interactive_keep_plugins_is_binary_only ... ok +test update::tests::tier_non_tty_without_yes_uses_flags ... ok +test update::tests::tier_ordering_is_correct ... ok +test update::tests::version_compare_handles_multi_digit_minor ... ok +test distiller::tests::temporary_global_fallback_keeps_expiry_and_duplicates_do_not_renew_it ... ok +test distiller::tests::temporary_proposal_keeps_expiry_on_acceptance_and_rebuild ... ok +test distiller::tests::temporary_user_brain_duplicates_do_not_renew_expiry ... ok +test tests::config_edit_with_broken_toml_returns_err ... ok +test tests::config_edit_with_valid_edit_is_accepted ... ok +test tests::config_set_and_get_integration ... ok +test tests::interactive_loop_accepts_rejects_and_skips_from_scripted_input ... ok +test tests::interactive_loop_quit_preserves_partial_decisions ... ok +test tests::run_abort_cli_stamps_terminal_kind ... ok +test skill_synth::tests::cited_ge3_candidate_to_install_provenance_round_trip ... ok +test distiller::tests::distill_and_record_writes_to_a_temp_brain ... ok +test doctor::tests::selftest_passes_on_healthy_setup ... ok +test tune_tests::brain_tune_dry_run_does_not_modify_config ... ok +test tune_tests::brain_tune_status_shows_zero_cases_when_empty ... ok +test tune_tests::fix3_apply_in_fixture_mode_leaves_config_untouched ... ok + +test result: ok. 268 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 10.67s + + Running tests\cli_smoke.rs (E:/Kimetsu/target\debug\deps\cli_smoke-d8296eece41d1b55.exe) + +running 30 tests +test concurrent_processes_lose_no_cites ... ignored, spawns many processes; run on demand +test brain_status_reports_the_free_tier_by_default ... ok +test a_passing_test_run_does_not_trigger_a_proactive_interruption ... ok +test a_real_failure_surfaces_a_matching_memory ... ok +test kimetsu_brain_help_lists_brain_subcommands ... ok +test brain_import_quarantines_by_source_and_honours_the_overrides ... ok +test kimetsu_brain_insights_help_lists_args ... ok +test kimetsu_brain_memory_help_lists_v05_subcommands ... ok +test kimetsu_help_lists_top_level_subcommands ... ok +test kimetsu_uninstall_help_lists_confirmation_flags ... ok +test kimetsu_unknown_subcommand_exits_nonzero_with_helpful_message ... ok +test kimetsu_update_help_lists_check_mode ... ok +test kimetsu_version_prints_a_version_string_and_exits_clean ... ok +test configuring_a_cheap_model_resolves_to_the_deep_tier ... ok +test explicit_free_tier_overrides_a_configured_model ... ok +test deep_without_a_model_downgrades_and_is_reported ... ok +test as_of_reports_what_the_brain_believed_at_a_point_in_time ... ok +test context_hook_warm_starts_even_on_a_short_prompt ... ok +test context_hook_suppressed_when_env_var_zero ... ok +test context_hook_without_the_flag_never_warm_starts ... ok +test context_hook_frames_memory_as_a_prior_conclusion_not_ground_truth ... ok +test context_hook_miss_logs_context_served_event ... ok +test context_hook_dates_and_orders_capsules_for_an_ordering_question ... ok +test the_injection_policy_starts_as_the_legacy_rule_and_records_its_decisions ... ok +test fusion_mode_is_wired_and_is_a_no_op_on_the_lean_path ... ok +test context_hook_warm_starts_once_per_session ... ok +test standing_preferences_reach_the_agent_without_being_retrieved ... ok +test maintenance_runs_what_is_due_and_then_stops ... ok +test hardening_free_hooks_never_cue_host_after_resolution_or_stop ... ok +test hardening_episode_cli_identity_and_archive_restore ... ok + +test result: ok. 29 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 15.48s + + Running unittests src\lib.rs (E:/Kimetsu/target\debug\deps\kimetsu_core-9cdb70ad5f2f08b7.exe) + +running 57 tests +test clock::tests::canonical_sorts_chronologically_and_breaks_ties_by_node ... ok +test clock::tests::observe_advances_past_far_future_remote ... ok +test clock::tests::now_is_strictly_increasing ... ok +test clock::tests::parse_roundtrips_including_dotted_node ... ok +test config::tests::deep_without_a_model_downgrades_and_is_flagged ... ok +test config::tests::default_for_project_uses_the_benchmarked_backend ... ok +test config::tests::default_config_uses_config_version_not_schema_version ... ok +test config::tests::explicit_free_overrides_a_configured_model ... ok +test config::tests::f3_adaptive_budget_huge_size_clamped_to_run_cap ... ok +test config::tests::f3_adaptive_budget_is_sublinear ... ok +test config::tests::f3_adaptive_budget_respects_floor ... ok +test config::tests::f3_adaptive_budget_respects_run_cap ... ok +test config::tests::f3_adaptive_budget_typical_task_near_historical_default ... ok +test config::tests::f3_adaptive_budget_zero_size_returns_floor ... ok +test config::tests::f3b_default_for_project_uses_conservative_defaults ... ok +test config::tests::explicit_fact_guard_is_opt_in_and_round_trips ... ok +test config::tests::hardening_automatic_harvest_policy_matrix ... ok +test config::tests::embedder_survives_toml_round_trip ... ok +test config::tests::broker_v1_5_fields_round_trip_as_false ... ok +test config::tests::pre_s1_2_config_without_cheap_model_loads_cleanly ... ok +test config::tests::pre_v0_8_config_without_embedder_loads_with_default ... ok +test config::tests::f3b_new_broker_fields_round_trip ... ok +test config::tests::pre_v1_5_config_without_price_per_mtok_loads_with_none ... ok +test config::tests::retrieval_level_never_overrides_embedder_off_switch ... ok +test config::tests::retrieval_level_never_reenables_explicit_reranker_off ... ok +test config::tests::f3b_proactive_prefetch_default_false_round_trips ... ok +test config::tests::retrieval_level_resolves_embedder_and_reranker ... ok +test config::tests::s1_2_a_learning_distiller_back_compat ... ok +test config::tests::s1_2_b_cheap_model_takes_precedence ... ok +test config::tests::rerank_cutoff_survives_configuration_roundtrip_and_rejects_invalid_values ... ok +test config::tests::missing_tier_field_loads_cleanly ... ok +test config::tests::s1_2_d_absent_disabled_returns_none ... ok +test config::tests::price_per_mtok_round_trips ... ok +test config::tests::s3_default_for_project_sync_unconfigured ... ok +test config::tests::s3_pre_s3_config_without_sync_loads_cleanly ... ok +test config::tests::tier_auto_follows_the_legacy_distiller_alias ... ok +test config::tests::tier_auto_resolves_to_deep_when_a_model_is_configured ... ok +test config::tests::s1_2_c_ollama_default_base_url ... ok +test config::tests::tier_defaults_to_free_without_a_model ... ok +test event::tests::origin_scope_empty_is_no_override ... ok +test config::tests::s3_sync_section_round_trips ... ok +test paths::tests::display_path_strips_extended_prefix ... ok +test event::tests::origin_scope_overrides_and_restores ... ok +test paths::tests::slug_is_filesystem_safe ... ok +test paths::tests::user_cache_dir_for_falls_back_to_temp_when_no_home ... ok +test paths::tests::user_cache_dir_for_lands_under_user_home ... ok +test secret::tests::debug_format_never_includes_inner_value ... ok +test secret::tests::display_emits_redaction_marker ... ok +test secret::tests::empty_and_len_helpers ... ok +test config::tests::w3_off_switch_fields_round_trip_as_false ... ok +test secret::tests::expose_secret_returns_cleartext ... ok +test config::tests::tier_round_trips_and_auto_stays_unwritten ... ok +test secret::tests::parent_struct_derive_debug_does_not_leak ... ok +test paths::tests::pin_discover_to_root_skips_git_climb ... ok +test secret::tests::serialize_emits_redaction_marker ... ok +test config::tests::s5_1_storage_backend_round_trips ... ok +test paths::tests::validate_state_dir_rejects_symlinked_kimetsu_dir ... ok + +test result: ok. 57 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s + + Running unittests src\lib.rs (E:/Kimetsu/target\debug\deps\kimetsu_e2e-ab969a891eb85052.exe) + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + + Running tests\citations.rs (E:/Kimetsu/target\debug\deps\citations-53f33a9c85a773d5.exe) + +running 2 tests +test cite_memory_tool_call_lands_in_report_context_with_turn_index ... ok +test cited_memory_earns_strong_signal_after_run_finished_projection ... ok + +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 9.29s + + Running tests\conflicts.rs (E:/Kimetsu/target\debug\deps\conflicts-9cbe3979db2b564a.exe) + +running 2 tests +test list_and_resolve_conflict_wrappers_compose_against_a_real_project ... ok +test re_resolving_same_conflict_is_idempotent_through_project_wrapper ... ok + +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 8.99s + + Running tests\decay.rs (E:/Kimetsu/target\debug\deps\decay-c87b88ef2ac1de68.exe) + +running 2 tests +test aged_cited_memory_ranks_below_recently_cited_under_default_half_life ... ok +test decay_can_be_disabled_via_broker_weights ... ok + +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.14s + + Running tests\golden_path.rs (E:/Kimetsu/target\debug\deps\golden_path-ad66c546184c51e1.exe) + +running 2 tests +test agent_loop_produces_structured_context_field ... ok +test agent_loop_completes_a_simple_scripted_run ... ok + +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.29s + + Running tests\insights.rs (E:/Kimetsu/target\debug\deps\insights-99f3749e6675f367.exe) + +running 4 tests +test insights_all_hits_gives_full_hit_rate_and_zero_skip_rate ... ok +test insights_all_skips_gives_zero_hit_rate_and_full_skip_rate ... ok +test insights_hit_rate_reflects_seeded_context_served_events ... ok +test insights_no_context_served_returns_zero_served_and_none_rates ... ok + +test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.60s + + Running tests\migration.rs (E:/Kimetsu/target\debug\deps\migration-d2d70dcb7a4e56f3.exe) + +running 1 test +test project_brain_v1_to_v2_migration_creates_backup_and_preserves_data ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.00s + + Running tests\pipeline_events_survive_rebuild.rs (E:/Kimetsu/target\debug\deps\pipeline_events_survive_rebuild-b3a5dec2811db10f.exe) + +running 1 test +test agent_run_events_survive_rebuild_from_events_table ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.62s + + Running unittests src\lib.rs (E:/Kimetsu/target\debug\deps\kimetsu_remote-2de35176e1ae8d5d.exe) + +running 33 tests +test auth::tests::debug_does_not_expose_tokens ... ok +test auth::tests::missing_or_unknown_is_unauthorized ... ok +test auth::tests::per_repo_token_scoped ... ok +test auth::tests::global_token_works_for_any_repo ... ok +test auth::tests::global_token_detection_distinguishes_per_repo_tokens ... ok +test auth::tests::user_for_token_uses_name_then_stable_anon_fingerprint ... ok +test git::tests::leaves_plain_urls_unchanged ... ok +test git::tests::redacts_credentials_in_git_urls ... ok +test ingest::tests::empty_file_is_ok ... ok +test ingest::tests::rejects_invalid_repo_keys ... ok +test ingest::tests::rejects_duplicate_canonical_repo_keys ... ok +test ingest::tests::parses_both_forms ... ok +test metrics::tests::counts_and_renders ... ok +test ratelimit::tests::burst_then_block_then_refill ... ok +test ratelimit::tests::disabled_always_allows ... ok +test ratelimit::tests::tokens_are_independent ... ok +test repo::tests::accepts_reasonable_ids ... ok +test repo::tests::rejects_traversal_and_separators ... ok +test repo::tests::resolved_root_stays_in_data_dir ... ok +test app::tests::healthz_needs_no_auth ... ok +test app::tests::metrics_endpoint_counts_outcomes ... ok +test app::tests::per_repo_token_wrong_repo_is_403 ... ok +test config::tests::duplicate_canonical_per_repo_tokens_fail ... ok +test app::tests::missing_token_is_401 ... ok +test app::tests::per_repo_token_cannot_write_shared_user_memory ... ok +test config::tests::per_repo_tokens_are_canonicalized ... ok +test app::tests::initialize_advertises_protocol ... ok +test repo::tests::ensure_initialized_repairs_partial_state_dir ... ok +test app::tests::excluded_tool_call_errors ... ok +test app::tests::tools_list_filtered_to_remote_catalog ... ok +test app::tests::rate_limit_returns_429 ... ok +test app::tests::bearer_scheme_is_case_insensitive ... ok +test app::tests::remote_write_is_attributed_to_the_token_user ... ok + +test result: ok. 33 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.96s + + Running unittests src\main.rs (E:/Kimetsu/target\debug\deps\kimetsu_remote-45dbaaa399b635fc.exe) + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + + Running tests\http_roundtrip.rs (E:/Kimetsu/target\debug\deps\http_roundtrip-7d1dd6668f3c601e.exe) + +running 5 tests +test hardening_remote_reranker_empty_reply_obeys_final_budget ... ok +test hardening_remote_reranker_escaped_capsule_obeys_final_budget ... ok +test reranker_in_appstate_intercepts_brain_context ... ok +test record_then_context_round_trips ... ok +test two_repos_are_isolated ... ok + +test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.84s + + Running tests\org_brain.rs (E:/Kimetsu/target\debug\deps\org_brain-c1828c5d6c4dd821.exe) + +running 1 test +test org_brain_shares_global_user_but_not_project ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.92s + + Running tests\server_ingest.rs (E:/Kimetsu/target\debug\deps\server_ingest-dd53cc3f2d9908ac.exe) + +running 1 test +test registered_repo_ingests_and_files_are_retrievable ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.42s + + Doc-tests kimetsu_agent + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + + Doc-tests kimetsu_brain + +running 2 tests +test crates\kimetsu-brain\src\packs.rs - packs::redact_context_suffix (line 92) ... ok +test crates\kimetsu-brain\src\packs.rs - packs::redact_tags_prefix (line 126) ... ok + +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s + +all doctests ran in 1.12s; merged doctests compilation took 0.98s + Doc-tests kimetsu_chat + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + + Doc-tests kimetsu_core + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + + Doc-tests kimetsu_e2e + +running 3 tests +test crates\kimetsu-e2e\src\lib.rs - (line 27) ... ignored +test crates\kimetsu-e2e\src\lib.rs - prelude (line 50) ... ignored +test crates\kimetsu-e2e\src\scripted_provider.rs - scripted_provider (line 8) ... ignored + +test result: ok. 0 passed; 0 failed; 3 ignored; 0 measured; 0 filtered out; finished in 0.00s + +all doctests ran in 0.66s; merged doctests compilation took 0.44s + Doc-tests kimetsu_remote + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + From 3ae8329c43660ada6e9cce4502c4ca89002bbcc4 Mon Sep 17 00:00:00 2001 From: RodCor Date: Mon, 7 Sep 2026 10:15:51 -0300 Subject: [PATCH 28/34] Add scoped fact evidence and partial answer delivery --- crates/kimetsu-agent/src/pipeline.rs | 6 + crates/kimetsu-brain/src/answerability.rs | 6 +- crates/kimetsu-brain/src/backend.rs | 47 +- crates/kimetsu-brain/src/backend_bench.rs | 2 +- crates/kimetsu-brain/src/benchmark.rs | 5 + crates/kimetsu-brain/src/bitemporal.rs | 1 + crates/kimetsu-brain/src/context.rs | 269 ++++++++- crates/kimetsu-brain/src/fact_query.rs | 524 ++++++++++++++++++ crates/kimetsu-brain/src/fact_store.rs | 345 ++++++++++++ crates/kimetsu-brain/src/fact_values.rs | 193 +++++++ crates/kimetsu-brain/src/facts.rs | 470 ++++++++++++++++ crates/kimetsu-brain/src/fusion.rs | 1 + crates/kimetsu-brain/src/lib.rs | 4 + crates/kimetsu-brain/src/migrate.rs | 5 + crates/kimetsu-brain/src/ordering.rs | 1 + crates/kimetsu-brain/src/project.rs | 1 + crates/kimetsu-brain/src/projector.rs | 4 + crates/kimetsu-brain/src/reinforce.rs | 1 + crates/kimetsu-brain/src/schema.rs | 20 + crates/kimetsu-brain/src/serving.rs | 133 ++++- crates/kimetsu-chat/src/ask.rs | 1 + crates/kimetsu-cli/src/commands/brain.rs | 83 ++- crates/kimetsu-cli/src/commands/hooks.rs | 93 +++- crates/kimetsu-cli/src/embed_daemon/proto.rs | 42 ++ crates/kimetsu-cli/src/embed_daemon/server.rs | 10 + crates/kimetsu-cli/src/main.rs | 5 +- crates/kimetsu-core/src/lib.rs | 2 +- crates/kimetsu-remote/src/rpc.rs | 10 +- crates/kimetsu-remote/tests/http_roundtrip.rs | 4 + 29 files changed, 2237 insertions(+), 51 deletions(-) create mode 100644 crates/kimetsu-brain/src/fact_query.rs create mode 100644 crates/kimetsu-brain/src/fact_store.rs create mode 100644 crates/kimetsu-brain/src/fact_values.rs create mode 100644 crates/kimetsu-brain/src/facts.rs diff --git a/crates/kimetsu-agent/src/pipeline.rs b/crates/kimetsu-agent/src/pipeline.rs index c8de2bf..16bb160 100644 --- a/crates/kimetsu-agent/src/pipeline.rs +++ b/crates/kimetsu-agent/src/pipeline.rs @@ -253,6 +253,7 @@ pub fn run_coding(options: CodingRunOptions) -> KimetsuResult { evidence_coverage: 1.0, uncovered_terms: Vec::new(), chronological: false, + known_fact_conflicts: vec![], }; let empty_plan = ContextBundle { stage: CodingStage::PatchPlan.as_str().to_string(), @@ -266,6 +267,7 @@ pub fn run_coding(options: CodingRunOptions) -> KimetsuResult { evidence_coverage: 1.0, uncovered_terms: Vec::new(), chronological: false, + known_fact_conflicts: vec![], }; (empty_loc, empty_plan, "Broker disabled (brain_off).") } else { @@ -3166,6 +3168,7 @@ mod tests { superseded_hint: false, rerank_policy_tier: 0, claim_revision: None, + facts: vec![], rerank_usefulness: None, rerank_trust: None, } @@ -3184,6 +3187,7 @@ mod tests { evidence_coverage: 1.0, uncovered_terms: Vec::new(), chronological: false, + known_fact_conflicts: vec![], } } @@ -3553,6 +3557,7 @@ mod tests { evidence_coverage: 1.0, uncovered_terms: Vec::new(), chronological: false, + known_fact_conflicts: vec![], }; let mut ledger = RunRecallLedger::new(); assert!( @@ -3573,6 +3578,7 @@ mod tests { evidence_coverage: 1.0, uncovered_terms: Vec::new(), chronological: false, + known_fact_conflicts: vec![], }; assert!( render_known_pitfalls(&empty_bundle, &mut ledger).is_none(), diff --git a/crates/kimetsu-brain/src/answerability.rs b/crates/kimetsu-brain/src/answerability.rs index bd8ffdb..75b80e1 100644 --- a/crates/kimetsu-brain/src/answerability.rs +++ b/crates/kimetsu-brain/src/answerability.rs @@ -335,8 +335,12 @@ pub fn assess(query: &str, text: &str) -> FactEvidence { /// Apply the same explicit-fact policy to MCP and the lightweight hook. pub fn filter_bundle(query: &str, bundle: &mut crate::context::ContextBundle) { + let request = crate::fact_query::parse(query); for capsule in std::mem::take(&mut bundle.capsules) { - if assess(query, &capsule.summary) == FactEvidence::MissingValue { + let rejected = if let Some(request) = request.as_ref().filter(|_| !capsule.facts.is_empty()) { + !capsule.facts.iter().any(|fact| crate::fact_query::visible(&capsule, fact) && crate::fact_query::matches(request, &fact.claim)) + } else { assess(query, &capsule.summary) == FactEvidence::MissingValue }; + if rejected { bundle.excluded.push(capsule); } else { bundle.capsules.push(capsule); diff --git a/crates/kimetsu-brain/src/backend.rs b/crates/kimetsu-brain/src/backend.rs index 7a868e0..78c434c 100644 --- a/crates/kimetsu-brain/src/backend.rs +++ b/crates/kimetsu-brain/src/backend.rs @@ -108,6 +108,7 @@ pub(crate) trait RetrievalBackend { query: &str, query_embedding: Option<&QueryEmbedding>, half_life_days: f32, + include_facts: bool, ) -> KimetsuResult>; } @@ -135,6 +136,7 @@ impl RetrievalBackend for FlatBackend { query: &str, query_embedding: Option<&QueryEmbedding>, half_life_days: f32, + include_facts: bool, ) -> KimetsuResult> { crate::context::memory_candidates_flat( conn, @@ -142,6 +144,7 @@ impl RetrievalBackend for FlatBackend { query_embedding, half_life_days, self.fusion, + include_facts, ) } } @@ -214,6 +217,7 @@ impl RetrievalBackend for GraphLiteBackend { query: &str, query_embedding: Option<&QueryEmbedding>, half_life_days: f32, + include_facts: bool, ) -> KimetsuResult> { // 1. Start with the flat candidate set (FTS + ANN / FTS + recency). let flat = crate::context::memory_candidates_flat( @@ -222,6 +226,7 @@ impl RetrievalBackend for GraphLiteBackend { query_embedding, half_life_days, self.fusion, + include_facts, )?; // 2. Collect the memory_ids already in the flat set. @@ -264,6 +269,7 @@ impl RetrievalBackend for GraphLiteBackend { &mut seen_ids, max_flat_relevance, half_life_days, + include_facts, )?; // 5. Concatenate: flat hits first (they have real relevance signals), @@ -387,6 +393,7 @@ fn fetch_graph_candidates( seen_ids: &mut HashSet, seed_relevance: f32, half_life_days: f32, + include_facts: bool, ) -> KimetsuResult> { if new_ids.is_empty() { return Ok(Vec::new()); @@ -462,7 +469,8 @@ fn fetch_graph_candidates( // Keep the graph's hop-derived query signal, but use exactly the same // usefulness decay and provenance policy as FTS/ANN hydration. - let claim_revision = Some(crate::projector::claim_revision_at(conn, &memory_id, None)?); + let revision = crate::projector::claim_revision_at(conn, &memory_id, None)?; + let claim_revision = Some(revision); if let Some(mut candidate) = crate::context::memory_row_to_candidate( &[], memory_id, @@ -481,6 +489,7 @@ fn fetch_graph_candidates( None, ) { candidate.capsule.claim_revision = claim_revision; + crate::context::hydrate_fact_evidence(conn, &mut candidate, include_facts)?; for source in &mut candidate.capsule.provenance { source.source = "graph".into(); } @@ -724,6 +733,7 @@ impl RetrievalBackend for PetgraphBackend { query: &str, query_embedding: Option<&QueryEmbedding>, half_life_days: f32, + include_facts: bool, ) -> KimetsuResult> { // 1. Flat candidate set (FTS + ANN or FTS + recency). let flat = crate::context::memory_candidates_flat( @@ -732,6 +742,7 @@ impl RetrievalBackend for PetgraphBackend { query_embedding, half_life_days, self.fusion, + include_facts, )?; // 2. Collect seen ids from the flat set. @@ -765,6 +776,7 @@ impl RetrievalBackend for PetgraphBackend { &mut seen_ids, max_flat_relevance, half_life_days, + include_facts, )?; // 5. Flat first (real relevance signals), graph-reached appended. @@ -858,6 +870,7 @@ impl RetrievalBackend for DeferredPetgraphBackend { query: &str, query_embedding: Option<&QueryEmbedding>, half_life_days: f32, + include_facts: bool, ) -> KimetsuResult> { // Fast path: already initialised — but we must hold the lock to read. // We delegate to the inner backend while the lock is held. The lock is @@ -874,6 +887,7 @@ impl RetrievalBackend for DeferredPetgraphBackend { query, query_embedding, half_life_days, + include_facts, ) } } @@ -912,7 +926,8 @@ mod tests { ("future".into(), 1), ("expired".into(), 1), ]; - let out = fetch_graph_candidates(&conn, &ids, &mut HashSet::new(), 1.0, 30.0).unwrap(); + let out = + fetch_graph_candidates(&conn, &ids, &mut HashSet::new(), 1.0, 30.0, false).unwrap(); assert_eq!(out.len(), 1); assert_eq!(out[0].capsule.expansion_handle, "memory:live"); assert_eq!( @@ -944,7 +959,8 @@ mod tests { insert_memory(&conn, "local", "fact", "local claim"); conn.execute("UPDATE memories SET provenance_snapshot_json='{\"source\":\"pack\"}' WHERE memory_id='pack'",[]).unwrap(); let ids = vec![("pack".into(), 1), ("local".into(), 1)]; - let out = fetch_graph_candidates(&conn, &ids, &mut HashSet::new(), 1.0, 30.0).unwrap(); + let out = + fetch_graph_candidates(&conn, &ids, &mut HashSet::new(), 1.0, 30.0, false).unwrap(); let ranked = crate::context::rerank_capsules( "q", out.into_iter().map(|c| c.capsule).collect(), @@ -960,7 +976,8 @@ mod tests { .format(&time::format_description::well_known::Rfc3339) .unwrap(); conn.execute("UPDATE memories SET use_count=5,usefulness_score=-5,last_useful_at=?1 WHERE memory_id='local'",[past]).unwrap(); - let out = fetch_graph_candidates(&conn, &ids, &mut HashSet::new(), 1.0, 30.0).unwrap(); + let out = + fetch_graph_candidates(&conn, &ids, &mut HashSet::new(), 1.0, 30.0, false).unwrap(); let local = out .iter() .find(|c| c.capsule.expansion_handle == "memory:local") @@ -1086,12 +1103,12 @@ mod tests { let flat = FlatBackend { fusion: crate::fusion::Fusion::Linear, } - .memory_candidates(&conn, query, None, 90.0) + .memory_candidates(&conn, query, None, 90.0, false) .expect("flat"); let graph = GraphLiteBackend { fusion: crate::fusion::Fusion::Linear, } - .memory_candidates(&conn, query, None, 90.0) + .memory_candidates(&conn, query, None, 90.0, false) .expect("graph-lite"); for candidate in &flat { @@ -1156,7 +1173,7 @@ mod tests { let graph = GraphLiteBackend { fusion: crate::fusion::Fusion::Linear, } - .memory_candidates(&conn, "checkpoint", None, 90.0) + .memory_candidates(&conn, "checkpoint", None, 90.0, false) .expect("graph-lite"); let reached = graph .iter() @@ -1203,10 +1220,10 @@ mod tests { }; let flat_candidates = flat_backend - .memory_candidates(&conn, "cargo rust", None, 90.0) + .memory_candidates(&conn, "cargo rust", None, 90.0, false) .expect("flat candidates"); let graph_candidates = graph_backend - .memory_candidates(&conn, "cargo rust", None, 90.0) + .memory_candidates(&conn, "cargo rust", None, 90.0, false) .expect("graph candidates"); // graph-lite ⊇ flat — so it must have at least as many candidates. @@ -1360,7 +1377,7 @@ mod tests { let flat = FlatBackend { fusion: crate::fusion::Fusion::Linear, } - .memory_candidates(&conn, "cargo fmt", None, 90.0) + .memory_candidates(&conn, "cargo fmt", None, 90.0, false) .expect("flat"); let flat_ids: HashSet = flat .iter() @@ -1384,7 +1401,7 @@ mod tests { let graph = GraphLiteBackend { fusion: crate::fusion::Fusion::Linear, } - .memory_candidates(&conn, "cargo fmt", None, 90.0) + .memory_candidates(&conn, "cargo fmt", None, 90.0, false) .expect("graph"); let graph_ids: HashSet = graph .iter() @@ -1437,7 +1454,7 @@ mod tests { let conn = make_conn(); let backend = backend_for("graph-lite", crate::fusion::Fusion::Linear); // Must not panic on an empty brain. - let result = backend.memory_candidates(&conn, "some query", None, 90.0); + let result = backend.memory_candidates(&conn, "some query", None, 90.0, false); assert!( result.is_ok(), "graph-lite backend must not error on empty brain" @@ -1555,7 +1572,7 @@ mod tests { PetgraphBackend::from_conn(&conn, crate::fusion::Fusion::Linear).expect("from_conn"); let candidates = backend - .memory_candidates(&conn, "cargo fmt", None, 90.0) + .memory_candidates(&conn, "cargo fmt", None, 90.0, false) .expect("memory_candidates"); let ids: std::collections::HashSet = candidates @@ -1586,7 +1603,7 @@ mod tests { let conn = make_conn(); let backend = backend_for("graph", crate::fusion::Fusion::Linear); // Must not panic on an empty brain. - let result = backend.memory_candidates(&conn, "some query", None, 90.0); + let result = backend.memory_candidates(&conn, "some query", None, 90.0, false); assert!( result.is_ok(), "petgraph backend must not error on empty brain" @@ -1601,7 +1618,7 @@ mod tests { let conn = make_conn(); let backend = backend_for("graph", crate::fusion::Fusion::Linear); // Must still work (graph-lite fallback). - let result = backend.memory_candidates(&conn, "some query", None, 90.0); + let result = backend.memory_candidates(&conn, "some query", None, 90.0, false); assert!( result.is_ok(), "graph fallback must not error on empty brain" diff --git a/crates/kimetsu-brain/src/backend_bench.rs b/crates/kimetsu-brain/src/backend_bench.rs index fc44601..0f37eda 100644 --- a/crates/kimetsu-brain/src/backend_bench.rs +++ b/crates/kimetsu-brain/src/backend_bench.rs @@ -220,7 +220,7 @@ fn run_backend( for (query, relevant) in cases { let t0 = Instant::now(); - let candidates = match backend.memory_candidates(conn, query, None, 90.0) { + let candidates = match backend.memory_candidates(conn, query, None, 90.0, false) { Ok(c) => c, Err(_) => { continue; diff --git a/crates/kimetsu-brain/src/benchmark.rs b/crates/kimetsu-brain/src/benchmark.rs index 528520b..9736fb5 100644 --- a/crates/kimetsu-brain/src/benchmark.rs +++ b/crates/kimetsu-brain/src/benchmark.rs @@ -691,6 +691,7 @@ mod tests { evidence_coverage: 1.0, uncovered_terms: Vec::new(), chronological: false, + known_fact_conflicts: vec![], }; let context = build_benchmark_context( @@ -787,6 +788,7 @@ mod tests { evidence_coverage: 1.0, uncovered_terms: Vec::new(), chronological: false, + known_fact_conflicts: vec![], }; let context = build_benchmark_context( @@ -828,6 +830,7 @@ mod tests { evidence_coverage: 1.0, uncovered_terms: Vec::new(), chronological: false, + known_fact_conflicts: vec![], }; let context = build_benchmark_context( @@ -867,6 +870,7 @@ mod tests { evidence_coverage: 1.0, uncovered_terms: Vec::new(), chronological: false, + known_fact_conflicts: vec![], }; let context = build_benchmark_context( @@ -911,6 +915,7 @@ mod tests { superseded_hint: false, rerank_policy_tier: 0, claim_revision: None, + facts: vec![], rerank_usefulness: None, rerank_trust: None, } diff --git a/crates/kimetsu-brain/src/bitemporal.rs b/crates/kimetsu-brain/src/bitemporal.rs index ec4e0a7..9f32b3b 100644 --- a/crates/kimetsu-brain/src/bitemporal.rs +++ b/crates/kimetsu-brain/src/bitemporal.rs @@ -177,6 +177,7 @@ pub fn as_of_capsules(memories: &[AsOfMemory]) -> Vec { superseded_hint: false, rerank_policy_tier: 0, claim_revision: None, + facts: vec![], rerank_usefulness: None, rerank_trust: None, }) diff --git a/crates/kimetsu-brain/src/context.rs b/crates/kimetsu-brain/src/context.rs index 4736b06..4cd35e5 100644 --- a/crates/kimetsu-brain/src/context.rs +++ b/crates/kimetsu-brain/src/context.rs @@ -261,6 +261,9 @@ pub struct ContextCapsule { /// Claim identity read in the same SQLite snapshot as the hydrated text. #[serde(default, skip_serializing_if = "Option::is_none")] pub claim_revision: Option, + /// Structured evidence read alongside this capsule text and revision. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub facts: Vec, /// Decayed usefulness multiplier and provenance discount carried to reranking. #[serde(default, skip_serializing_if = "Option::is_none")] pub rerank_usefulness: Option, @@ -312,6 +315,7 @@ impl ContextCapsule { superseded_hint: false, rerank_policy_tier: 0, claim_revision: None, + facts: vec![], rerank_usefulness: None, rerank_trust: None, } @@ -327,6 +331,10 @@ pub struct ProvenanceRef { #[derive(Debug, Clone, Default)] pub struct ContextRequest { + /// Hydrate structured evidence only for consumers that explicitly request it. + pub include_fact_evidence: bool, + /// Defer intermediate budgeting to a final evidence-aware renderer; requires a bounded pool. + pub defer_fact_budget: bool, pub stage: String, pub query: String, pub budget_tokens: u32, @@ -465,6 +473,8 @@ pub struct ContextBundle { /// relevance-ranked one whose ranking has gone wrong. See /// [`crate::ordering`] for why ordering is rendered rather than retrieved. pub chronological: bool, + /// Conflicts observed in eligible evidence before delivery trimming. + pub known_fact_conflicts: Vec, } /// Discriminating weight per query token, for *bundle* coverage. @@ -711,6 +721,7 @@ pub(crate) fn retrieve_context_with_embedder_and_backend( &request.query, query_embedding.as_ref(), half_life_days, + request.include_fact_evidence, )?); for extra in extra_memory_conns { candidates.extend(backend.memory_candidates( @@ -718,6 +729,7 @@ pub(crate) fn retrieve_context_with_embedder_and_backend( &request.query, query_embedding.as_ref(), half_life_days, + request.include_fact_evidence, )?); } // v2.5.2 consolidation v1: bounded query-association routing boost — @@ -1029,6 +1041,7 @@ pub(crate) fn retrieve_context_with_embedder_and_backend( uncovered_terms: Vec::new(), // Nothing was rendered, so nothing was rendered in time order. chronological: false, + known_fact_conflicts: vec![], }); } @@ -1052,8 +1065,10 @@ pub(crate) fn retrieve_context_with_embedder_and_backend( excluded.push(capsule); continue; } - if used_tokens.saturating_add(capsule.token_estimate) <= capsule_budget { - used_tokens += capsule.token_estimate; + if (request.defer_fact_budget && request.max_capsules > 0) + || used_tokens.saturating_add(capsule.token_estimate) <= capsule_budget + { + used_tokens = used_tokens.saturating_add(capsule.token_estimate); included.push(capsule); } else { excluded.push(capsule); @@ -1089,6 +1104,7 @@ pub(crate) fn retrieve_context_with_embedder_and_backend( evidence_coverage: coverage, uncovered_terms, chronological, + known_fact_conflicts: vec![], }) } @@ -1161,7 +1177,9 @@ pub fn search_memories_including_expired( } else { String::new() }; - let claim_revision = Some(crate::projector::claim_revision_at(conn, &memory_id, None)?); + let revision = crate::projector::claim_revision_at(conn, &memory_id, None)?; + let facts = crate::fact_store::load(conn, &memory_id, &revision)?; + let claim_revision = Some(revision); capsules.push(ContextCapsule { id: new_id().to_string(), kind: "memory".to_string(), @@ -1181,6 +1199,7 @@ pub fn search_memories_including_expired( superseded_hint: false, rerank_policy_tier: 0, claim_revision, + facts, rerank_usefulness: None, rerank_trust: None, }); @@ -1231,6 +1250,7 @@ fn memory_ann_candidates( k: u32, query_tokens: &[String], half_life_days: f32, + include_facts: bool, ) -> KimetsuResult> { // Tier-3: ANN candidate generation via the usearch HNSW index. let handle = crate::ann::handle_for_query(conn, qe.vector.len(), &qe.model_id)?; @@ -1326,12 +1346,30 @@ fn memory_ann_candidates( row_vec, ) { candidate.capsule.claim_revision = Some(claim_revision); + hydrate_fact_evidence(conn, &mut candidate, include_facts)?; candidates.push(candidate); } } Ok(candidates) } +/// Attach evidence only after row eligibility, while the text SELECT holds its snapshot. +pub(crate) fn hydrate_fact_evidence( + conn: &Connection, + candidate: &mut Candidate, + include_facts: bool, +) -> KimetsuResult<()> { + if include_facts { + if let (Some(id), Some(revision)) = ( + candidate.capsule.expansion_handle.strip_prefix("memory:"), + candidate.capsule.claim_revision.as_deref(), + ) { + candidate.capsule.facts = crate::fact_store::load(conn, id, revision)?; + } + } + Ok(()) +} + /// S5.1: the flat memory candidate function exposed as `pub(crate)` so /// [`crate::backend::FlatBackend`] can delegate to it without copying logic. /// @@ -1343,8 +1381,16 @@ pub(crate) fn memory_candidates_flat( query_embedding: Option<&QueryEmbedding>, half_life_days: f32, fusion: crate::fusion::Fusion, + include_facts: bool, ) -> KimetsuResult> { - memory_candidates(conn, query, query_embedding, half_life_days, fusion) + memory_candidates( + conn, + query, + query_embedding, + half_life_days, + fusion, + include_facts, + ) } /// Build the flat candidate pool, merging the lexical and semantic rankings @@ -1361,6 +1407,7 @@ fn memory_candidates( // Read only on the embeddings build: the lean path has a single ranking, // so there is nothing to fuse and any rule is the identity. #[cfg_attr(not(feature = "embeddings"), allow(unused_variables))] fusion: crate::fusion::Fusion, + include_facts: bool, ) -> KimetsuResult> { let query_tokens = query_tokens(query); @@ -1379,13 +1426,15 @@ fn memory_candidates( 80, Some(qe), half_life_days, + include_facts, )? } else { Vec::new() }; // ANN candidates — top-80 nearest neighbours from the usearch index. - let ann_candidates = memory_ann_candidates(conn, qe, 80, &query_tokens, half_life_days)?; + let ann_candidates = + memory_ann_candidates(conn, qe, 80, &query_tokens, half_life_days, include_facts)?; // Both sources return best-first, which is what rank-based fusion needs. return Ok(crate::fusion::fuse( @@ -1403,13 +1452,21 @@ fn memory_candidates( 80, query_embedding, half_life_days, + include_facts, )?; if !candidates.is_empty() { return Ok(candidates); } } - latest_memory_candidates(conn, &query_tokens, 200, query_embedding, half_life_days) + latest_memory_candidates( + conn, + &query_tokens, + 200, + query_embedding, + half_life_days, + include_facts, + ) } fn latest_memory_candidates( @@ -1418,6 +1475,7 @@ fn latest_memory_candidates( limit: u32, query_embedding: Option<&QueryEmbedding>, half_life_days: f32, + include_facts: bool, ) -> KimetsuResult> { // MP-4d: exclude invalidated memories from retrieval. The row stays in // brain.db so `memory list` and replay can still see the history; only @@ -1501,6 +1559,7 @@ fn latest_memory_candidates( row_vec, ) { candidate.capsule.claim_revision = Some(claim_revision); + hydrate_fact_evidence(conn, &mut candidate, include_facts)?; candidates.push(candidate); } } @@ -1514,6 +1573,7 @@ fn memory_fts_candidates( limit: u32, query_embedding: Option<&QueryEmbedding>, half_life_days: f32, + include_facts: bool, ) -> KimetsuResult> { let mut stmt = conn.prepare_cached( " @@ -1594,6 +1654,7 @@ fn memory_fts_candidates( row_vec, ) { candidate.capsule.claim_revision = Some(claim_revision); + hydrate_fact_evidence(conn, &mut candidate, include_facts)?; candidates.push(candidate); } } @@ -1755,6 +1816,7 @@ pub(crate) fn memory_row_to_candidate( superseded_hint: false, rerank_policy_tier, claim_revision: None, + facts: vec![], rerank_usefulness: Some(multiplier), rerank_trust: Some(crate::trust::trust_multiplier( provenance, @@ -1903,6 +1965,7 @@ fn repo_file_candidates( superseded_hint: false, rerank_policy_tier: 0, claim_revision: None, + facts: vec![], rerank_usefulness: None, rerank_trust: None, }, @@ -1976,6 +2039,7 @@ fn manifest_candidates( superseded_hint: false, rerank_policy_tier: 0, claim_revision: None, + facts: vec![], rerank_usefulness: None, rerank_trust: None, }, @@ -2041,6 +2105,7 @@ fn manifest_fts_candidates( superseded_hint: false, rerank_policy_tier: 0, claim_revision: None, + facts: vec![], rerank_usefulness: None, rerank_trust: None, }, @@ -3342,6 +3407,7 @@ mod tests { superseded_hint: false, rerank_policy_tier: 0, claim_revision: None, + facts: vec![], rerank_usefulness: None, rerank_trust: None, } @@ -5274,6 +5340,7 @@ mod tests { superseded_hint: false, rerank_policy_tier: 0, claim_revision: None, + facts: vec![], rerank_usefulness: None, rerank_trust: None, }, @@ -5350,6 +5417,7 @@ mod tests { superseded_hint: false, rerank_policy_tier: 0, claim_revision: None, + facts: vec![], rerank_usefulness: None, rerank_trust: None, }, @@ -6068,6 +6136,7 @@ mod tests { superseded_hint: false, rerank_policy_tier: 0, claim_revision: None, + facts: vec![], rerank_usefulness: None, rerank_trust: None, } @@ -6330,6 +6399,7 @@ mod tests { evidence_coverage: 1.0, uncovered_terms: vec![], chronological: false, + known_fact_conflicts: vec![], } } @@ -6628,6 +6698,7 @@ mod evidence_tests { superseded_hint: false, rerank_policy_tier: 0, claim_revision: None, + facts: vec![], rerank_usefulness: None, rerank_trust: None, } @@ -6646,6 +6717,7 @@ mod evidence_tests { evidence_coverage: coverage, uncovered_terms: uncovered.iter().map(|s| s.to_string()).collect(), chronological: false, + known_fact_conflicts: vec![], } } @@ -7117,8 +7189,17 @@ mod hardening_tests { ) .unwrap(); for candidates in [ - memory_fts_candidates(&conn, &["routing".into()], "routing*", 80, None, 30.0).unwrap(), - latest_memory_candidates(&conn, &["routing".into()], 200, None, 30.0).unwrap(), + memory_fts_candidates( + &conn, + &["routing".into()], + "routing*", + 80, + None, + 30.0, + false, + ) + .unwrap(), + latest_memory_candidates(&conn, &["routing".into()], 200, None, 30.0, false).unwrap(), ] { let ids: Vec<_> = candidates .iter() @@ -7134,8 +7215,16 @@ mod hardening_tests { #[test] fn hardening_hydration_binds_text_revision_before_later_correction() { let conn = corpus(); - let candidates = - memory_fts_candidates(&conn, &["routing".into()], "routing*", 80, None, 30.0).unwrap(); + let candidates = memory_fts_candidates( + &conn, + &["routing".into()], + "routing*", + 80, + None, + 30.0, + false, + ) + .unwrap(); let capsules: Vec<_> = candidates.into_iter().map(|c| c.capsule).collect(); assert_eq!(memory_revision_bindings(&capsules)["live"], "baseline:live"); conn.execute("INSERT INTO memory_revisions(memory_id,event_id,text,kind,known_at,effective_at,confidence,use_count,usefulness_score) @@ -7188,7 +7277,7 @@ mod hardening_tests { vector: vec![1.0, 0.0], model_id: "test".into(), }; - let out = memory_ann_candidates(&conn, &qe, 80, &["routing".into()], 30.0).unwrap(); + let out = memory_ann_candidates(&conn, &qe, 80, &["routing".into()], 30.0, false).unwrap(); assert_eq!(out.len(), 2); for c in out { assert!(matches!( @@ -7209,3 +7298,161 @@ mod hardening_tests { assert_eq!(corpus_token_idf(&conn, &tokens).unwrap()["absent"], 0.0); } } + +#[cfg(test)] +mod structured_fact_hydration_tests { + use super::*; + use kimetsu_core::{event::Event, ids::RunId}; + + #[test] + fn lexical_and_recency_capsules_keep_their_delivered_fact_revision() { + let c = Connection::open_in_memory().unwrap(); + crate::schema::initialize(&c).unwrap(); + crate::projector::apply_events(&c,&[Event::new(RunId::new(),"memory.accepted",serde_json::json!({ + "memory_id":"m","scope":"project","kind":"fact","text":"Orchid staging gateway port is 7319." + }))]).unwrap(); + let mut delivered = Vec::new(); + for candidates in [ + memory_fts_candidates(&c, &["orchid".into()], "orchid*", 80, None, 30.0, true).unwrap(), + latest_memory_candidates(&c, &["orchid".into()], 200, None, 30.0, true).unwrap(), + ] { + let capsule = &candidates[0].capsule; + assert_eq!(capsule.facts.len(), 1); + assert_eq!(capsule.facts[0].claim.value, "7319"); + assert_eq!( + capsule.claim_revision.as_deref(), + Some(capsule.facts[0].claim_revision.as_str()) + ); + delivered.push(capsule.clone()); + } + crate::projector::apply_events( + &c, + &[Event::new( + RunId::new(), + "memory.corrected", + serde_json::json!({ + "memory_id":"m","text":"Orchid staging gateway port is 8420." + }), + )], + ) + .unwrap(); + for capsule in delivered { + assert!(capsule.summary.contains("7319")); + assert_eq!(capsule.facts[0].claim.value, "7319"); + } + let latest = + latest_memory_candidates(&c, &["orchid".into()], 200, None, 30.0, true).unwrap(); + assert_eq!(latest[0].capsule.facts[0].claim.value, "8420"); + } + #[test] + fn legacy_wire_capsules_default_to_empty_fact_evidence() { + let c = ContextCapsule::wire_minimal("hello".into(), "memory".into(), 1.0); + let json = serde_json::to_value(&c).unwrap(); + assert!(json.get("facts").is_none()); + assert!( + serde_json::from_value::(json) + .unwrap() + .facts + .is_empty() + ); + } +} + +#[cfg(test)] +mod disabled_fact_hydration_tests { + use super::*; + #[test] + fn ordinary_retrieval_does_not_read_the_fact_projection() { + let c = Connection::open_in_memory().unwrap(); + crate::schema::initialize(&c).unwrap(); + crate::projector::apply_events( + &c, + &[kimetsu_core::event::Event::new( + kimetsu_core::ids::RunId::new(), + "memory.accepted", + serde_json::json!({"memory_id":"m","text":"Orchid gateway port is 7319."}), + )], + ) + .unwrap(); + c.execute_batch("DROP TABLE memory_facts").unwrap(); + for query in ["Orchid", ""] { + let out = + memory_candidates_flat(&c, query, None, 30.0, crate::fusion::Fusion::Linear, false) + .unwrap(); + assert_eq!(out.len(), 1); + assert!(out[0].capsule.facts.is_empty()); + } + } +} + +#[cfg(test)] +mod deferred_fact_budget_tests { + use super::*; + #[test] + fn initial_retrieval_budget_must_not_hide_an_eligible_conflicting_fact() { + let c = Connection::open_in_memory().unwrap(); + crate::schema::initialize(&c).unwrap(); + for (id, value) in [("a", "7319"), ("b", "7320")] { + let text = format!( + "Orchid gateway port is {value}. Stable operation. Recorded settings. {}", + "Operational notes remain available. ".repeat(350) + ); + crate::projector::apply_events( + &c, + &[kimetsu_core::event::Event::new( + kimetsu_core::ids::RunId::new(), + "memory.accepted", + serde_json::json!({"memory_id":id,"scope":"project","kind":"fact","text":text}), + )], + ) + .unwrap(); + } + let query = "What is the Orchid gateway port?"; + let policy = crate::serving::ServingPolicy { + budget: 6000, + cap: 1, + explicit_fact_guard: true, + ..Default::default() + }; + let request = ContextRequest { + stage: "localization".into(), + query: query.into(), + budget_tokens: 6000, + ..Default::default() + }; + let weights = BrokerWeights::default(); + let mut ordinary = request.clone(); + ordinary.max_capsules = 6; + let ordinary = retrieve_context_with_embedder( + &c, + "/fake-repo", + &weights, + ordinary, + &[], + &crate::embeddings::NoopEmbedder, + ) + .unwrap(); + assert_eq!(ordinary.capsules.len(), 1); + assert!(ordinary.used_tokens <= 3000); + let selected = retrieve_context_with_embedder( + &c, + "/fake-repo", + &weights, + policy.prepare(request, false), + &[], + &crate::embeddings::NoopEmbedder, + ) + .unwrap(); + assert_eq!( + selected.capsules.len(), + 2, + "both eligible claims must reach arbitration before delivery budgeting" + ); + let selected = policy.arbitrate(query, selected, None, 0.0); + let delivered = + policy.render_for_query(query, selected, true, crate::serving::EVAL_EXPOSURE_ID); + assert_eq!(delivered.capsules.len(), 1); + assert_eq!(delivered.payload["answerability"]["status"], "conflicting"); + assert!(delivered.payload["used_tokens"].as_u64().unwrap() <= 6000); + } +} diff --git a/crates/kimetsu-brain/src/fact_query.rs b/crates/kimetsu-brain/src/fact_query.rs new file mode 100644 index 0000000..b6d33e3 --- /dev/null +++ b/crates/kimetsu-brain/src/fact_query.rs @@ -0,0 +1,524 @@ +//! Scope-bound requests and evidence accounting for explicit configuration facts. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct FactRequest { + pub subject: String, + pub environment: Option, + pub attributes: Vec, +} + +fn folded(text: &str) -> String { + text.to_lowercase() + .chars() + .map(|c| match c { + 'á' => 'a', + 'é' => 'e', + 'í' => 'i', + 'ó' => 'o', + 'ú' | 'ü' => 'u', + 'ñ' => 'n', + _ => c, + }) + .collect() +} +fn attribute(text: &str) -> Option { + Some( + match text.trim().trim_matches('`') { + "port" | "puerto" => "port", + "timeout" | "tiempo de espera" => "timeout", + "version" | "release number" => "version", + "replicas" | "replica count" => "replicas", + "password" | "passphrase" | "contrasena" => "password", + "encryption key" | "clave de cifrado" => "encryption_key", + "retries" | "retry count" | "reintentos" => "retries", + "retention" | "retencion" => "retention", + "memory limit" | "limite de memoria" => "memory_limit", + key if key.contains(['.', '_']) + && key + .chars() + .all(|c| c.is_ascii_alphanumeric() || "._-".contains(c)) => + { + key + } + _ => return None, + } + .to_owned(), + ) +} +fn attributes(text: &str) -> Option> { + let text = text.replace(" and ", ",").replace(" y ", ","); + let mut result = Vec::new(); + for part in text.split(',') { + let attr = attribute(part)?; + if !result.contains(&attr) { + result.push(attr); + } + } + if result.is_empty() || result.len() > 4 { + None + } else { + Some(result) + } +} +fn request(subject: &str, attrs: &str) -> Option { + let (subject, environment) = crate::facts::canonical_subject(subject); + if subject.is_empty() + || subject.split_whitespace().any(|w| { + matches!( + w, + "effect" + | "impact" + | "cause" + | "causes" + | "changing" + | "configure" + | "configuration" + | "best" + | "meaning" + | "difference" + ) + }) + { + return None; + } + Some(FactRequest { + subject, + environment, + attributes: attributes(attrs)?, + }) +} +/// Recognize direct attribute questions only, with one explicit shared subject. +/// Multi-subject, explanatory and unsupported language retain normal retrieval. +pub fn parse(query: &str) -> Option { + if query.len() > 1024 { + return None; + } + let normalized = folded(query); + let q = normalized.trim_matches(['¿', '?', ' ', '.', '\n', '\t']); + if let Some(rest) = q.strip_prefix("what ") { + if let Some((attrs, subject)) = rest.split_once(" does ") { + for suffix in [" use", " require", " run", " have"] { + if let Some(subject) = subject.strip_suffix(suffix) { + return request(subject, attrs); + } + } + return None; + } + } + let rest = [ + "what is ", + "what are ", + "what's ", + "which is ", + "which are ", + "cual es ", + "cuales son ", + "que es ", + ] + .iter() + .find_map(|p| q.strip_prefix(p))?; + let rest = ["the ", "el ", "la ", "los ", "las "] + .iter() + .find_map(|p| rest.strip_prefix(p)) + .unwrap_or(rest); + for separator in [" for ", " of ", " del ", " de "] { + if let Some((attrs, subject)) = rest.split_once(separator) { + if attributes(attrs).is_some() { + return request(subject, attrs); + } + } + } + // Split at every word boundary: exactly one split must form a complete + // attribute list; arbitrary suffix words cannot be silently ignored. + for (index, ch) in rest.char_indices() { + if ch == ' ' && attributes(&rest[index + 1..]).is_some() { + return request(&rest[..index], &rest[index + 1..]); + } + } + None +} + +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)] +pub struct SupportedFact { + pub attribute: String, + pub value: String, + pub sources: Vec, +} +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)] +pub struct FactAssessment { + pub status: String, + pub subject: String, + pub environment: Option, + pub supported: Vec, + pub missing: Vec, + pub conflicting: Vec, +} +pub fn matches(request: &FactRequest, claim: &crate::facts::FactClaim) -> bool { + request.subject == claim.subject + && request.environment == claim.environment + && request.attributes.contains(&claim.attribute) +} +pub fn assess( + request: &FactRequest, + claims: &[(&crate::facts::FactClaim, &str)], +) -> FactAssessment { + use std::collections::{BTreeMap, BTreeSet}; + let mut supported = Vec::new(); + let mut missing = Vec::new(); + let mut conflicting = Vec::new(); + for attribute in &request.attributes { + let mut values: BTreeMap)> = BTreeMap::new(); + for (claim, source) in claims { + if matches(request, claim) && claim.attribute == *attribute { + values + .entry(crate::fact_values::equivalence_key(attribute, &claim.value)) + .or_insert_with(|| (claim.value.as_str(), BTreeSet::new())) + .1 + .insert(source); + } + } + match values.len() { + 0 => missing.push(attribute.clone()), + 1 => { + let (_, (value, sources)) = values.into_iter().next().unwrap(); + supported.push(SupportedFact { + attribute: attribute.clone(), + value: value.into(), + sources: sources.into_iter().map(str::to_owned).collect(), + }); + } + _ => conflicting.push(attribute.clone()), + } + } + let status = if !conflicting.is_empty() { + "conflicting" + } else if supported.is_empty() { + "missing" + } else if missing.is_empty() { + "supported" + } else { + "partial" + }; + FactAssessment { + status: status.into(), + subject: request.subject.clone(), + environment: request.environment.clone(), + supported, + missing, + conflicting, + } +} + +/// Retain conflicts already observed before output-budget trimming. Sources in +/// supported claims still come exclusively from the final delivered slice. +pub fn preserve_conflicts(result: &mut FactAssessment, known: &[String]) { + for attr in known { + result.supported.retain(|s| &s.attribute != attr); + result.missing.retain(|s| s != attr); + if !result.conflicting.contains(attr) { + result.conflicting.push(attr.clone()); + } + } + if !result.conflicting.is_empty() { + result.status = "conflicting".into(); + } +} +/// Facts can support only the claim and exact excerpt that are actually visible. +pub fn visible( + capsule: &crate::context::ContextCapsule, + fact: &crate::fact_store::StoredFact, +) -> bool { + capsule.expansion_handle.strip_prefix("memory:") == Some(fact.memory_id.as_str()) + && capsule.claim_revision.as_deref() == Some(fact.claim_revision.as_str()) + && !fact.claim.evidence.is_empty() + && capsule.summary.contains(&fact.claim.evidence) +} +pub fn evaluate( + query: &str, + capsules: &[crate::context::ContextCapsule], +) -> Option { + let request = parse(query)?; + let claims: Vec<_> = capsules + .iter() + .flat_map(|c| { + c.facts + .iter() + .filter(move |f| visible(c, f)) + .map(move |f| (&f.claim, c.expansion_handle.as_str())) + }) + .collect(); + Some(assess(&request, &claims)) +} +pub fn compress_capsule( + query: &str, + capsule: &crate::context::ContextCapsule, + sentences: usize, +) -> String { + let short = + crate::answerability::compress_preserving_evidence(query, &capsule.summary, sentences); + if parse(query).is_some_and(|request| { + capsule.facts.iter().any(|f| { + visible(capsule, f) && matches(&request, &f.claim) && !short.contains(&f.claim.evidence) + }) + }) { + capsule.summary.clone() + } else { + short + } +} +pub fn notice(query: &str, capsules: &[crate::context::ContextCapsule]) -> Option { + notice_with_conflicts(query, capsules, &[]) +} +pub fn notice_with_conflicts( + query: &str, + capsules: &[crate::context::ContextCapsule], + known: &[String], +) -> Option { + let mut result = evaluate(query, capsules)?; + preserve_conflicts(&mut result, known); + if result.status == "supported" { + return None; + } + let mut details = Vec::new(); + if !result.missing.is_empty() { + details.push(format!( + "no supported value for {}", + result.missing.join(", ") + )); + } + if !result.conflicting.is_empty() { + details.push(format!( + "conflicting values for {}", + result.conflicting.join(", ") + )); + } + Some(format!("Retrieved fact evidence: {}.", details.join("; "))) +} + +#[cfg(test)] +mod tests { + use super::*; + pub(crate) fn capsule(text: &str, id: &str) -> crate::context::ContextCapsule { + let mut c = + crate::context::ContextCapsule::wire_minimal(text.into(), "memory".into(), 0.99); + c.expansion_handle = format!("memory:{id}"); + c.claim_revision = Some(format!("baseline:{id}")); + c.facts = crate::facts::extract(text) + .into_iter() + .map(|claim| crate::fact_store::StoredFact { + memory_id: id.into(), + claim_revision: format!("baseline:{id}"), + source_event_id: "accepted-event".into(), + valid_from: None, + valid_to: None, + claim, + }) + .collect(); + c + } + #[test] + fn only_visible_revision_bound_facts_support_an_answer() { + let q = "What is the Orchid staging gateway port?"; + let mut c = capsule("Orchid staging gateway port is 7319.", "port"); + assert_eq!(evaluate(q, &[c.clone()]).unwrap().status, "supported"); + c.claim_revision = Some("different-revision".into()); + assert_eq!(evaluate(q, &[c.clone()]).unwrap().status, "missing"); + c.claim_revision = Some("baseline:port".into()); + c.summary = "Unrelated text.".into(); + assert_eq!(evaluate(q, &[c]).unwrap().status, "missing"); + } + #[test] + fn compression_preserves_each_supported_attribute() { + let q = "What are the Orchid gateway port and timeout?"; + let c = capsule( + "Orchid gateway port is 7319. Other setup notes. More setup notes. Orchid gateway timeout is 30 seconds.", + "settings", + ); + assert!(compress_capsule(q, &c, 3).contains("30 seconds")); + } + #[test] + fn budget_trimming_does_not_hide_a_known_conflict() { + use crate::context::ContextBundle; + let q = "What is the Orchid gateway port?"; + let bundle = ContextBundle { + stage: "localization".into(), + budget_tokens: 6000, + used_tokens: 0, + capsules: vec![ + capsule("Orchid gateway port is 7319.", "a"), + capsule("Orchid gateway port is 7320.", "b"), + ], + excluded: vec![], + skipped: false, + top_score: 0.99, + top_abs_evidence: 0.99, + evidence_coverage: 1.0, + uncovered_terms: vec![], + chronological: false, + known_fact_conflicts: vec![], + }; + let mut found = false; + for budget in (300..2000).step_by(20) { + let delivered = crate::serving::ServingPolicy { + budget, + explicit_fact_guard: true, + ..Default::default() + } + .render_for_query( + q, + bundle.clone(), + true, + crate::serving::EVAL_EXPOSURE_ID, + ); + if delivered.capsules.len() == 1 { + found = true; + assert_eq!(delivered.payload["answerability"]["status"], "conflicting"); + assert_eq!( + delivered.payload["answerability"]["conflicting"], + serde_json::json!(["port"]) + ); + break; + } + } + assert!(found); + } + #[test] + fn serving_metadata_tracks_the_final_budgeted_slice() { + use crate::context::ContextBundle; + let q = "What are the Orchid gateway port and timeout?"; + let bundle = ContextBundle { + stage: "localization".into(), + budget_tokens: 6000, + used_tokens: 0, + capsules: vec![ + capsule("Orchid gateway port is 7319.", "port"), + capsule("Orchid gateway timeout is 30 seconds.", "timeout"), + ], + excluded: vec![], + skipped: false, + top_score: 0.99, + top_abs_evidence: 0.99, + evidence_coverage: 1.0, + uncovered_terms: vec![], + chronological: false, + known_fact_conflicts: vec![], + }; + let policy = crate::serving::ServingPolicy { + explicit_fact_guard: true, + ..Default::default() + }; + let full = + policy.render_for_query(q, bundle.clone(), true, crate::serving::EVAL_EXPOSURE_ID); + assert_eq!(full.payload["answerability"]["status"], "supported"); + let mut found = false; + for budget in (300..2000).step_by(20) { + let delivered = crate::serving::ServingPolicy { budget, ..policy }.render_for_query( + q, + bundle.clone(), + true, + crate::serving::EVAL_EXPOSURE_ID, + ); + if delivered.capsules.len() == 1 { + found = true; + assert_eq!(delivered.payload["answerability"]["status"], "partial"); + assert_eq!( + delivered.payload["answerability"]["missing"], + serde_json::json!(["timeout"]) + ); + assert!( + crate::context::delivery::serialized_output_tokens(&delivered.payload) + <= budget + ); + break; + } + } + assert!(found, "expected a budget admitting only the first capsule"); + } + fn claim( + subject: &str, + environment: Option<&str>, + attribute: &str, + value: &str, + ) -> crate::facts::FactClaim { + crate::facts::FactClaim { + subject: subject.into(), + environment: environment.map(str::to_owned), + attribute: attribute.into(), + value: value.into(), + evidence: "evidence".into(), + } + } + #[test] + fn supports_partial_answers_without_borrowing_another_scope() { + let request = parse("What are the Orchid staging gateway port and timeout?").unwrap(); + let port = claim("orchid gateway", Some("staging"), "port", "7319"); + let wrong_env = claim( + "orchid gateway", + Some("production"), + "timeout", + "30 seconds", + ); + let wrong_subject = claim("quartz gateway", Some("staging"), "timeout", "30 seconds"); + let result = assess( + &request, + &[ + (&port, "memory:port"), + (&wrong_env, "memory:wrong-env"), + (&wrong_subject, "memory:wrong-subject"), + ], + ); + assert_eq!(result.status, "partial"); + assert_eq!(result.missing, ["timeout"]); + assert_eq!(result.supported[0].sources, ["memory:port"]); + } + #[test] + fn conflicting_values_are_reported_instead_of_selecting_one() { + let request = parse("What is the Orchid gateway port?").unwrap(); + let a = claim("orchid gateway", None, "port", "7319"); + let b = claim("orchid gateway", None, "port", "7320"); + let result = assess(&request, &[(&a, "memory:a"), (&b, "memory:b")]); + assert_eq!(result.status, "conflicting"); + assert_eq!(result.conflicting, ["port"]); + assert!(result.supported.is_empty()); + } + #[test] + fn parses_shared_subject_and_environment_for_compound_request() { + let request = parse("What are the Orchid staging gateway port and timeout?").unwrap(); + assert_eq!(request.subject, "orchid gateway"); + assert_eq!(request.environment.as_deref(), Some("staging")); + assert_eq!(request.attributes, ["port", "timeout"]); + } + #[test] + fn parses_subject_after_attributes_and_spanish_aliases() { + assert_eq!( + parse("What is the port for the Quartz gateway?") + .unwrap() + .subject, + "quartz gateway" + ); + assert_eq!( + parse("What port does the Quartz gateway use?") + .unwrap() + .attributes, + ["port"] + ); + assert_eq!( + parse("¿Cuál es el puerto del Quartz gateway?") + .unwrap() + .attributes, + ["port"] + ); + } + #[test] + fn broad_or_ambiguous_questions_keep_normal_retrieval() { + for query in [ + "What causes a version conflict?", + "What version control system do we use?", + "What is the effect of changing port?", + "What does `cache.size` control?", + "What are the gateway port and database timeout?", + "What is the port?", + ] { + assert_eq!(parse(query), None, "{query}"); + } + } +} diff --git a/crates/kimetsu-brain/src/fact_store.rs b/crates/kimetsu-brain/src/fact_store.rs new file mode 100644 index 0000000..e644fff --- /dev/null +++ b/crates/kimetsu-brain/src/fact_store.rs @@ -0,0 +1,345 @@ +//! Rebuildable evidence derived only from redacted memory text. +use crate::facts::FactClaim; +use kimetsu_core::KimetsuResult; +use rusqlite::{Connection, OptionalExtension, params}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct StoredFact { + pub memory_id: String, + pub claim_revision: String, + pub source_event_id: String, + pub valid_from: Option, + pub valid_to: Option, + pub claim: FactClaim, +} + +/// Caller holds the event/migration write transaction. +pub fn refresh(conn: &Connection, memory_id: &str) -> KimetsuResult<()> { + conn.execute("DELETE FROM memory_facts WHERE memory_id=?1", [memory_id])?; + let row: Option<(String, Option)> = conn + .query_row( + "SELECT text,source_event_id FROM memories WHERE memory_id=?1", + [memory_id], + |r| Ok((r.get(0)?, r.get(1)?)), + ) + .optional()?; + let Some((text, accepted_event)) = row else { + return Ok(()); + }; + let revision = crate::projector::claim_revision_at(conn, memory_id, None)?; + let source = if revision.starts_with("baseline:") { + accepted_event.unwrap_or_else(|| revision.clone()) + } else { + revision.clone() + }; + // Defense in depth for legacy backfills. Never duplicate raw legacy secrets. + let redacted = crate::redact::redact_secrets(&text).text; + let digest = blake3::hash(text.as_bytes()).to_hex().to_string(); + let mut insert=conn.prepare_cached("INSERT INTO memory_facts(memory_id,claim_revision,ordinal,source_event_id,source_digest,claim_json) VALUES(?1,?2,?3,?4,?5,?6)")?; + for (ordinal, claim) in crate::facts::extract(&redacted).into_iter().enumerate() { + insert.execute(params![ + memory_id, + revision, + ordinal as i64, + source, + digest, + serde_json::to_string(&claim)? + ])?; + } + Ok(()) +} + +/// Caller holds the migration write transaction. +pub fn backfill(conn: &Connection) -> KimetsuResult<()> { + let mut stmt = conn.prepare("SELECT memory_id FROM memories ORDER BY memory_id")?; + let ids = stmt + .query_map([], |r| r.get::<_, String>(0))? + .collect::, _>>()?; + drop(stmt); + for id in ids { + refresh(conn, &id)?; + } + Ok(()) +} + +/// Current evidence only. The text, lifecycle and revision predicates are read +/// in one SQLite statement. Retrieval calls this while its text SELECT remains +/// active, so both queries share the capsule's read snapshot. +pub fn load( + conn: &Connection, + memory_id: &str, + claim_revision: &str, +) -> KimetsuResult> { + let mut stmt = conn.prepare_cached( + "SELECT f.source_event_id,m.valid_from,m.valid_to,f.claim_json,f.source_digest,m.text + FROM memory_facts f JOIN memories m ON m.memory_id=f.memory_id + WHERE f.memory_id=?1 AND f.claim_revision=?2 + AND m.invalidated_at IS NULL AND m.superseded_by IS NULL + AND (m.valid_from IS NULL OR julianday(m.valid_from)<=julianday('now')) + AND (m.valid_to IS NULL OR julianday(m.valid_to)>julianday('now')) + AND f.claim_revision=COALESCE((SELECT event_id FROM ( + SELECT event_id,revision_id,text,LAG(text) OVER (ORDER BY revision_id) AS previous_text + FROM memory_revisions WHERE memory_id=?1) + WHERE previous_text IS NULL OR text!=previous_text + ORDER BY revision_id DESC LIMIT 1),'baseline:' || m.memory_id) + ORDER BY f.ordinal", + )?; + let rows = stmt.query_map(params![memory_id, claim_revision], |r| { + Ok(( + r.get::<_, String>(0)?, + r.get::<_, Option>(1)?, + r.get::<_, Option>(2)?, + r.get::<_, String>(3)?, + r.get::<_, String>(4)?, + r.get::<_, String>(5)?, + )) + })?; + let mut out = Vec::new(); + let mut source_digest = None; + for row in rows { + let (source_event_id, valid_from, valid_to, json, digest, text) = row?; + // Historical temporary views may swap text without changing revisions. + let current_digest = + source_digest.get_or_insert_with(|| blake3::hash(text.as_bytes()).to_hex().to_string()); + if current_digest.as_str() != digest { + continue; + } + out.push(StoredFact { + memory_id: memory_id.into(), + claim_revision: claim_revision.into(), + source_event_id, + valid_from, + valid_to, + claim: serde_json::from_str(&json)?, + }); + } + Ok(out) +} + +#[cfg(test)] +mod tests { + use crate::{projector, schema}; + use kimetsu_core::{event::Event, ids::RunId}; + use rusqlite::Connection; + + fn event(kind: &str, payload: serde_json::Value) -> Event { + Event::new(RunId::new(), kind, payload) + } + fn seeded() -> Connection { + let c = Connection::open_in_memory().unwrap(); + schema::initialize(&c).unwrap(); + projector::apply_events( + &c, + &[event( + "memory.accepted", + serde_json::json!({ + "memory_id":"m", "scope":"project", "kind":"fact", + "text":"Orchid staging gateway port is 7319." + }), + )], + ) + .unwrap(); + c + } + #[test] + fn acceptance_projects_redacted_claim_and_correction_replaces_it_on_replay() { + let c = seeded(); + let count: i64 = c + .query_row( + "SELECT COUNT(*) FROM memory_facts WHERE memory_id='m'", + [], + |r| r.get(0), + ) + .unwrap(); + assert_eq!(count, 1); + projector::apply_events( + &c, + &[event( + "memory.corrected", + serde_json::json!({ + "memory_id":"m", "text":"Orchid staging gateway port is 8420." + }), + )], + ) + .unwrap(); + let before: String = c + .query_row( + "SELECT claim_json FROM memory_facts WHERE memory_id='m'", + [], + |r| r.get(0), + ) + .unwrap(); + assert!(before.contains("8420")); + assert!(!before.contains("7319")); + projector::rebuild_in_place(&c).unwrap(); + let after: String = c + .query_row( + "SELECT claim_json FROM memory_facts WHERE memory_id='m'", + [], + |r| r.get(0), + ) + .unwrap(); + assert_eq!(before, after); + } +} + +#[cfg(test)] +mod migration_tests { + use rusqlite::Connection; + #[test] + fn schema_fifteen_backfills_existing_text() { + let c = Connection::open_in_memory().unwrap(); + crate::schema::initialize(&c).unwrap(); + c.execute("INSERT INTO memories(memory_id,scope,kind,text,normalized_text,confidence,created_at,provenance_snapshot_json) VALUES('legacy','project','fact','Orchid staging gateway port is 7319.','',1,'2026-01-01T00:00:00Z','{}')",[]).unwrap(); + c.execute_batch("DROP TABLE IF EXISTS memory_facts; UPDATE schema_info SET value=14 WHERE key='kimetsu_schema_version'").unwrap(); + crate::migrate::run_migrations(&c).unwrap(); + let count: i64 = c + .query_row( + "SELECT COUNT(*) FROM memory_facts WHERE memory_id='legacy'", + [], + |r| r.get(0), + ) + .unwrap(); + assert_eq!(count, 1); + } +} + +#[cfg(test)] +mod transaction_tests { + use kimetsu_core::{event::Event, ids::RunId}; + use rusqlite::Connection; + #[test] + fn failed_event_batch_rolls_back_fact_projection() { + let c = Connection::open_in_memory().unwrap(); + crate::schema::initialize(&c).unwrap(); + let events = [ + Event::new( + RunId::new(), + "memory.accepted", + serde_json::json!({"memory_id":"m","text":"Orchid gateway port is 7319."}), + ), + Event::new( + RunId::new(), + "memory.corrected", + serde_json::json!({"memory_id":"missing","text":"Orchid gateway port is 8420."}), + ), + ]; + assert!(crate::projector::apply_events(&c, &events).is_err()); + let count: i64 = c + .query_row("SELECT COUNT(*) FROM memory_facts", [], |r| r.get(0)) + .unwrap(); + assert_eq!(count, 0); + } +} + +#[cfg(test)] +mod load_tests { + use super::*; + use kimetsu_core::{event::Event, ids::RunId}; + fn apply(c: &Connection, kind: &str, payload: serde_json::Value) -> Event { + let e = Event::new(RunId::new(), kind, payload); + crate::projector::apply_events(c, &[e.clone()]).unwrap(); + e + } + fn seeded() -> Connection { + let c = Connection::open_in_memory().unwrap(); + crate::schema::initialize(&c).unwrap(); + apply( + &c, + "memory.accepted", + serde_json::json!({"memory_id":"m","text":"Orchid staging gateway port is 7319."}), + ); + c + } + #[test] + fn revisions_and_text_must_match_and_provenance_tracks_text_changes() { + let c = seeded(); + let original = load(&c, "m", "baseline:m").unwrap(); + assert_eq!(original.len(), 1); + let correction = apply( + &c, + "memory.corrected", + serde_json::json!({"memory_id":"m","text":"Orchid staging gateway port is 8420."}), + ); + assert!(load(&c, "m", "baseline:m").unwrap().is_empty()); + let revision = correction.event_id.to_string(); + let changed = load(&c, "m", &revision).unwrap(); + assert_eq!(changed.len(), 1); + assert_eq!(changed[0].claim.value, "8420"); + assert_eq!(changed[0].source_event_id, revision); + apply( + &c, + "memory.corrected", + serde_json::json!({"memory_id":"m","kind":"preference"}), + ); + assert_eq!(load(&c, "m", &revision).unwrap(), changed); + // The historical retrieval clone can retain revision rows but swap text. + c.execute( + "UPDATE memories SET text='Orchid staging gateway port is 7319.' WHERE memory_id='m'", + [], + ) + .unwrap(); + assert!(load(&c, "m", &revision).unwrap().is_empty()); + } + #[test] + fn lifecycle_and_temporal_bounds_are_authoritative_at_read_time() { + let c = seeded(); + for update in [ + "invalidated_at='2026-01-01T00:00:00Z'", + "superseded_by='other'", + "valid_from='2999-01-01T00:00:00Z'", + "valid_to='2020-01-01T00:00:00Z'", + ] { + c.execute( + &format!("UPDATE memories SET {update} WHERE memory_id='m'"), + [], + ) + .unwrap(); + assert!(load(&c, "m", "baseline:m").unwrap().is_empty(), "{update}"); + c.execute("UPDATE memories SET invalidated_at=NULL,superseded_by=NULL,valid_from=NULL,valid_to=NULL WHERE memory_id='m'",[]).unwrap(); + assert_eq!(load(&c, "m", "baseline:m").unwrap().len(), 1); + } + apply( + &c, + "memory.temporal", + serde_json::json!({"memory_id":"m","valid_from":"2020-01-01T00:00:00Z","valid_to":"2999-01-01T00:00:00Z"}), + ); + let projected = load(&c, "m", "baseline:m").unwrap(); + assert_eq!( + projected[0].valid_from.as_deref(), + Some("2020-01-01T00:00:00Z") + ); + assert_eq!( + projected[0].valid_to.as_deref(), + Some("2999-01-01T00:00:00Z") + ); + crate::projector::rebuild_in_place(&c).unwrap(); + assert_eq!(load(&c, "m", "baseline:m").unwrap(), projected); + } + #[test] + fn ingestion_and_legacy_backfill_do_not_project_secrets() { + let c = seeded(); + let secret = "ghp_abcdefghijklmnopqrstuvwxyz1234567890ABCD"; + apply( + &c, + "memory.accepted", + serde_json::json!({"memory_id":"secret","text":format!("Orchid gateway password is {secret}. Orchid gateway port is 7319.")}), + ); + let rows: Vec = c + .prepare("SELECT claim_json FROM memory_facts") + .unwrap() + .query_map([], |r| r.get(0)) + .unwrap() + .collect::>() + .unwrap(); + assert!(rows.iter().all(|s| !s.contains(secret))); + c.execute( + "UPDATE memories SET text=?1 WHERE memory_id='secret'", + [format!("Orchid gateway password is {secret}.")], + ) + .unwrap(); + backfill(&c).unwrap(); + assert!(load(&c, "secret", "baseline:secret").unwrap().is_empty()); + } +} diff --git a/crates/kimetsu-brain/src/fact_values.rs b/crates/kimetsu-brain/src/fact_values.rs new file mode 100644 index 0000000..45c7985 --- /dev/null +++ b/crates/kimetsu-brain/src/fact_values.rs @@ -0,0 +1,193 @@ +//! Exact equivalence keys for bounded numeric configuration values. + +fn gcd(mut a: u128, mut b: u128) -> u128 { + while b != 0 { + (a, b) = (b, a % b); + } + a +} + +fn decimal(number: &str) -> Option<(u128, u128)> { + let (whole, fraction) = number.split_once('.').unwrap_or((number, "")); + if whole.is_empty() + || !whole.bytes().all(|b| b.is_ascii_digit()) + || !fraction.bytes().all(|b| b.is_ascii_digit()) + || (number.contains('.') && fraction.is_empty()) + { + return None; + } + let denominator = 10_u128.checked_pow(fraction.len().try_into().ok()?)?; + let numerator = whole + .parse::() + .ok()? + .checked_mul(denominator)? + .checked_add(if fraction.is_empty() { + 0 + } else { + fraction.parse().ok()? + })?; + let divisor = gcd(numerator, denominator); + Some((numerator / divisor, denominator / divisor)) +} + +fn scaled(number: &str, top: u128, bottom: u128) -> Option<(u128, u128)> { + let (mut numerator, mut denominator) = decimal(number)?; + // Cross-cancel before multiplication, including zero, to avoid needless overflow. + let cancel_top = gcd(top, denominator); + denominator /= cancel_top; + let cancel_bottom = gcd(numerator, bottom); + numerator /= cancel_bottom; + Some(( + numerator.checked_mul(top / cancel_top)?, + denominator.checked_mul(bottom / cancel_bottom)?, + )) +} + +fn numeric_key(attribute: &str, value: &str) -> Option { + if value.len() > 128 { + return None; + } + let input = value.trim(); + if matches!(attribute, "port" | "retries" | "replicas") { + if input.is_empty() || !input.bytes().all(|b| b.is_ascii_digit()) { + return None; + } + let number = input.parse::().ok()?; + if attribute == "port" && number > u16::MAX.into() { + return None; + } + return Some(format!("integer:{number}")); + } + if !matches!(attribute, "timeout" | "retention" | "memory_limit") { + return None; + } + let split = input.find(|c: char| !c.is_ascii_digit() && c != '.')?; + let number = &input[..split]; + let unit = input[split..].trim().to_ascii_lowercase(); + let (dimension, top, bottom) = if attribute == "memory_limit" { + ( + "bytes", + match unit.as_str() { + "bytes" => 1, + "kb" => 1_000, + "mb" => 1_000_000, + "gb" => 1_000_000_000, + "kib" => 1_024, + "mib" => 1_048_576, + "gib" => 1_073_741_824, + _ => return None, + }, + 1, + ) + } else { + let (top, bottom) = match unit.as_str() { + "ms" | "millisecond" | "milliseconds" | "milisegundo" | "milisegundos" => (1, 1_000), + "s" | "second" | "seconds" | "segundo" | "segundos" => (1, 1), + "minute" | "minutes" | "minuto" | "minutos" => (60, 1), + "hour" | "hours" | "hora" | "horas" => (3_600, 1), + "day" | "days" | "dia" | "dias" => (86_400, 1), + "week" | "weeks" | "semana" | "semanas" => (604_800, 1), + _ => return None, + }; + ("seconds", top, bottom) + }; + let (numerator, denominator) = scaled(number, top, bottom)?; + Some(format!("{dimension}:{numerator}/{denominator}")) +} + +/// Comparison-only key; never replace a displayed value with this key. +/// Numeric parsing is bounded to 128 bytes and checked u128 rational arithmetic. +/// Unsupported units (including calendar months/years), malformed numbers and +/// overflow retain an exact, case-sensitive key in a separate namespace. +pub fn equivalence_key(attribute: &str, value: &str) -> String { + numeric_key(attribute, value).unwrap_or_else(|| format!("exact:{value}")) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn equivalent_durations_share_keys_without_rounding() { + for (left, right) in [ + ("30 seconds", "30000 ms"), + ("0.5s", "500ms"), + ("1 minute", "60 seconds"), + ("1.25 hours", "75 minutes"), + ("21 days", "3 weeks"), + ("0.0001ms", "0.0000001s"), + ] { + assert_eq!( + equivalence_key("timeout", left), + equivalence_key("timeout", right), + "{left} / {right}" + ); + } + assert_ne!( + equivalence_key("timeout", "0.0000001s"), + equivalence_key("timeout", "0.0000002s") + ); + assert_eq!( + equivalence_key("retention", "12 horas"), + equivalence_key("retention", "720 minutos") + ); + } + + #[test] + fn binary_and_decimal_memory_units_are_distinct() { + assert_eq!( + equivalence_key("memory_limit", "0.5 MiB"), + equivalence_key("memory_limit", "524288 bytes") + ); + assert_eq!( + equivalence_key("memory_limit", "1 GB"), + equivalence_key("memory_limit", "1000 MB") + ); + assert_ne!( + equivalence_key("memory_limit", "1 MiB"), + equivalence_key("memory_limit", "1 MB") + ); + } + + #[test] + fn integer_attributes_normalize_only_valid_counts() { + for attribute in ["port", "retries", "replicas"] { + assert_eq!( + equivalence_key(attribute, "00042"), + equivalence_key(attribute, "42") + ); + assert_ne!( + equivalence_key(attribute, "4.2"), + equivalence_key(attribute, "42") + ); + } + } + + #[test] + fn unsupported_values_and_sensitive_strings_remain_exact() { + for attribute in ["password", "encryption_key", "cache.key", "version"] { + assert_ne!( + equivalence_key(attribute, "AbC"), + equivalence_key(attribute, "abc") + ); + assert_ne!( + equivalence_key(attribute, "01"), + equivalence_key(attribute, "1") + ); + } + for (left, right) in [ + ("1 month", "30 days"), + ("1 year", "365 days"), + ("1..0s", "1s"), + ("-1s", "1s"), + ("1e3ms", "1s"), + ("1s extra", "1s"), + ("340282366920938463463374607431768211456s", "0s"), + ] { + assert_ne!( + equivalence_key("timeout", left), + equivalence_key("timeout", right) + ); + } + } +} diff --git a/crates/kimetsu-brain/src/facts.rs b/crates/kimetsu-brain/src/facts.rs new file mode 100644 index 0000000..f4cb77f --- /dev/null +++ b/crates/kimetsu-brain/src/facts.rs @@ -0,0 +1,470 @@ +//! Bounded deterministic extraction of explicitly scoped configuration evidence. +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct FactClaim { + pub subject: String, + pub attribute: String, + pub value: String, + pub environment: Option, + pub evidence: String, +} + +fn folded(text: &str) -> String { + text.to_lowercase() + .chars() + .map(|c| match c { + 'á' => 'a', + 'é' => 'e', + 'í' => 'i', + 'ó' => 'o', + 'ú' | 'ü' => 'u', + 'ñ' => 'n', + _ => c, + }) + .collect() +} + +/// Canonicalize a bounded, explicit noun phrase. Invalid/ambiguous scope is empty. +pub fn canonical_subject(text: &str) -> (String, Option) { + let normalized = folded(text).replace('’', "'"); + let mut words = Vec::new(); + let mut environment = None; + for word in normalized.split_whitespace() { + let word = word.strip_suffix("'s").unwrap_or(word); + if matches!(word, "the" | "a" | "an" | "el" | "la" | "los" | "las") { + continue; + } + if matches!(word, "production" | "staging" | "development" | "test") { + if environment.is_some() { + return (String::new(), None); + } + environment = Some(word.to_owned()); + continue; + } + if word.len() > 48 + || !word + .chars() + .all(|c| c.is_alphanumeric() || "_-".contains(c)) + || matches!( + word, + "and" + | "or" + | "but" + | "while" + | "if" + | "when" + | "for" + | "of" + | "de" + | "del" + | "changing" + | "causes" + | "is" + | "are" + | "was" + | "were" + | "has" + | "uses" + | "stores" + | "requires" + | "should" + | "could" + | "would" + | "may" + | "might" + | "not" + | "no" + | "never" + | "without" + | "sin" + | "nunca" + | "example" + | "ejemplo" + | "hypothetical" + | "unknown" + | "redacted" + | "this" + | "that" + | "it" + | "we" + | "you" + ) + { + return (String::new(), None); + } + words.push(word); + } + if words.is_empty() || words.len() > 8 { + return (String::new(), None); + } + (words.join(" "), environment) +} + +fn patterns() -> &'static [(String, regex::Regex)] { + static PATTERNS: std::sync::OnceLock> = std::sync::OnceLock::new(); + PATTERNS.get_or_init(|| { + [ + ("port", "port|puerto", r"\d{1,5}"), + ("timeout", "timeout|tiempo de espera", r"\d+(?:\.\d+)?\s*(?:ms|s|seconds?|segundos?|minutes?|minutos?)"), + ("version", "version|release", r"v?\d+(?:\.\d+){0,5}"), + ("retries", "retries|retry count|reintentos", r"\d{1,9}"), + ("replicas", "replicas?|replica count", r"\d{1,9}"), + ("memory_limit", "memory limit|limite de memoria", r"\d+\s*(?:kib|mib|gib|kb|mb|gb|bytes)"), + ("retention", "retention|retencion", r"\d+\s*(?:seconds?|minutes?|hours?|days?|weeks?|months?|years?|segundos?|minutos?|horas?|dias?|semanas?|meses|anos?)"), + ("password", "password|passphrase|contrasena", r#"[a-z0-9_+./-]{1,128}"#), + ("encryption_key", "encryption key|clave de cifrado", r#"[a-z0-9_+./-]{1,128}"#), + ("literal", r"(?P[a-z_][a-z0-9_-]*(?:[._][a-z0-9_-]+)+)", r#"[a-z0-9_+./-]{1,128}"#), + ].into_iter().map(|(name, attr, value)| { + let assignment = r"\s*(?:is\s*|are\s+|es\s*|son\s+|=\s*|:\s*)"; + let binder = if matches!(name, "password" | "encryption_key" | "literal") { + assignment.to_owned() + } else { + format!(r"(?:{assignment}|\s+)") + }; + let pattern = format!(r#"(?i)^(?P.+?)\s+`?(?:{attr})`?{binder}[`"']?(?P{value})[`"']?$"#); + (name.to_owned(), regex::Regex::new(&pattern).expect("constant fact grammar")) + }).collect() + }) +} + +fn parse_clause(evidence: &str) -> Option { + if evidence.len() > 512 { + return None; + } + let body = evidence.trim_end_matches(['.', ',', ';']).trim(); + let lower = folded(body); + // Unknown/redacted/provisional language is never converted to a value. + if [ + "redacted", + "unknown", + "example", + "ejemplo", + "hypothetical", + "not configured", + "no longer", + "unavailable", + "hidden", + ] + .iter() + .any(|w| lower.contains(w)) + { + return None; + } + let absent_subject = lower + .strip_prefix("no password is required for ") + .or_else(|| lower.strip_prefix("no password required for ")) + .or_else(|| lower.strip_suffix(" no password required")) + .or_else(|| lower.strip_suffix(" no password is required")) + .or_else(|| lower.strip_suffix(" password is not required")); + if let Some(subject) = absent_subject { + let (subject, environment) = canonical_subject(subject); + if subject.is_empty() { + return None; + } + return Some(FactClaim { + subject, + environment, + attribute: "password".into(), + value: "not required".into(), + evidence: evidence.into(), + }); + } + for (attribute, pattern) in patterns() { + let Some(captures) = pattern.captures(body) else { + continue; + }; + let (subject, environment) = canonical_subject(&captures["subject"]); + if subject.is_empty() { + continue; + } + let value = captures["value"].to_owned(); + if matches!( + folded(&value).as_str(), + "missing" + | "not" + | "stored" + | "configured" + | "required" + | "managed" + | "generated" + | "provided" + | "set" + | "secret" + | "none" + | "null" + ) { + continue; + } + if attribute == "port" && value.parse::().is_err() { + continue; + } + return Some(FactClaim { + subject, + environment, + attribute: if attribute == "literal" { + folded(&captures["key"]) + } else { + attribute.clone() + }, + value: if matches!( + attribute.as_str(), + "password" | "encryption_key" | "literal" + ) { + value + } else { + folded(&value) + .split_whitespace() + .collect::>() + .join(" ") + }, + evidence: evidence.into(), + }); + } + None +} + +/// Parse complete clauses only: explicit subject + attribute + is/=/colon + value. +/// Numeric attributes also accept whitespace instead of an assignment binder. +/// No inherited scope, free-form entailment, model calls, or secret recovery. +/// Sentences containing commas are unsupported: a comma can introduce scope, +/// qualification or a numeric separator. Never discard that context or truncate +/// its number. Within comma-free sentences, semicolons separate explicit clauses. +/// Explicit example/hypothetical markers reject the entire input, since a header +/// may qualify following sentences without repeating its provisional status. +/// One leading `[tags: ...] ` record metadata prefix (at most 512 bytes, no nested +/// brackets or newlines) is ignored without contributing subject or environment. +/// Limits: 64 KiB input (oversize rejected), 256 clauses, 512 bytes/clause, 32 facts. +pub fn extract(text: &str) -> Vec { + if text.len() > 65_536 { + return Vec::new(); + } + let normalized = folded(text); + if ["example", "ejemplo", "hypothetical"] + .iter() + .any(|marker| normalized.contains(marker)) + { + return Vec::new(); + } + let text = if let Some(tagged) = text.strip_prefix("[tags: ") { + let Some((tags, body)) = tagged.split_once("] ") else { + return Vec::new(); + }; + if tags.len() > 503 || tags.contains(['[', ']', '\n', '\r']) || body.starts_with("[tags:") { + return Vec::new(); + } + body + } else { + text + }; + let mut facts = Vec::new(); + let mut start = 0; + let mut clauses = 0; + let sentence_ends = text.char_indices().filter_map(|(offset, c)| { + let end = offset + c.len_utf8(); + (c == '\n' + || (c == '.' && (end == text.len() || text[end..].starts_with(char::is_whitespace)))) + .then_some(end) + }); + for end in sentence_ends.chain(std::iter::once(text.len())) { + let sentence = &text[start..end]; + start = end; + if sentence.contains(',') { + continue; + } + for clause in sentence.split_inclusive(';') { + let clause = clause.trim(); + if let Some(fact) = parse_clause(clause) { + facts.push(fact); + } + clauses += 1; + if facts.len() == 32 || clauses == 256 { + return facts; + } + } + } + facts +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn scope_environment_and_exact_evidence() { + let text = "Orchid staging gateway port is 7319."; + let facts = extract(text); + assert_eq!(facts.len(), 1); + assert_eq!( + facts[0], + FactClaim { + subject: "orchid gateway".into(), + attribute: "port".into(), + value: "7319".into(), + environment: Some("staging".into()), + evidence: text.into() + } + ); + assert_eq!( + extract("The Orchid gateway timeout is45seconds.")[0].value, + "45seconds" + ); + } + + #[test] + fn clauses_do_not_inherit_scope() { + let facts = + extract("Orchid gateway port is7319; the database stores state. timeout is 45seconds."); + assert_eq!(facts.len(), 1); + assert_eq!(facts[0].subject, "orchid gateway"); + assert!(extract("Orchid gateway stores state and database port is 5432.").is_empty()); + assert!(extract("Orchid gateway port is7319, while the database stores state.").is_empty()); + } + + #[test] + fn rejects_negated_hypothetical_and_hidden_values() { + for text in [ + "Orchid gateway port is not 7319.", + "Example: Orchid gateway port is 7319.", + "Orchid gateway password = [REDACTED]", + "Orchid gateway password is unknown.", + "Orchid gateway port is 7319 or 7320.", + "If Orchid gateway port is 7319.", + "Orchid gateway port is 7319?", + "Orchid gateway port is 73190abc.", + ] { + assert!(extract(text).is_empty(), "{text}"); + } + } + + #[test] + fn recognizes_scoped_absence_and_attributes() { + for text in [ + "Orchid gateway no password required.", + "No password is required for the Orchid gateway.", + ] { + let f = extract(text); + assert_eq!(f.len(), 1, "{text}"); + assert_eq!(f[0].subject, "orchid gateway"); + assert_eq!(f[0].value, "not required"); + } + for (attribute, value) in [ + ("version", "3.46"), + ("retries", "3"), + ("memory limit", "512 MiB"), + ("retention", "21 days"), + ("encryption key", "demo-key"), + ("cache.max_entries", "200"), + ] { + let f = extract(&format!("Orchid gateway {attribute} = {value}.")); + assert_eq!(f.len(), 1, "{attribute}"); + assert_eq!(f[0].attribute, attribute.replace(' ', "_")); + } + } + + #[test] + fn output_and_input_are_bounded() { + assert_eq!( + extract(&"Orchid gateway port is 7319.\n".repeat(100)).len(), + 32 + ); + assert!(extract(&format!("{} port is 7319.", "a".repeat(600))).is_empty()); + } + + #[test] + fn canonical_scope_keeps_identity_and_rejects_mixed_environments() { + assert_eq!( + canonical_subject("The Orchid’s staging gateway"), + ("orchid gateway".into(), Some("staging".into())) + ); + assert!(canonical_subject("Orchid staging production gateway") + .0 + .is_empty()); + assert!(canonical_subject("the production").0.is_empty()); + assert_eq!( + extract("Orchid gateway `cache.max_entries` = 200.")[0].attribute, + "cache.max_entries" + ); + assert_eq!( + extract("Orchid gateway password = `AbC-123`.")[0].value, + "AbC-123" + ); + } + + #[test] + fn numeric_attributes_allow_bare_values_and_replica_counts() { + assert_eq!(extract("Orchid gateway port 7319.")[0].value, "7319"); + let facts = extract("Orchid production gateway replicas are 4."); + assert_eq!(facts[0].attribute, "replicas"); + assert_eq!(facts[0].value, "4"); + assert_eq!(facts[0].environment.as_deref(), Some("production")); + } + + #[test] + fn rejects_relational_and_action_subjects() { + for subject in [ + "effect of changing Orchid gateway", + "Orchid causes", + "puerto del gateway", + "gateway de Orchid", + ] { + assert!(canonical_subject(subject).0.is_empty(), "{subject}"); + } + } + + #[test] + fn comma_qualifiers_cannot_be_discarded() { + for text in [ + "If deployment succeeds, Orchid gateway port is 7319.", + "In staging, Orchid gateway port is 7319.", + "For example, Orchid gateway port is 7319.", + "Assuming approval, Orchid gateway port is 7319.", + ] { + assert!(extract(text).is_empty(), "{text}"); + } + let facts = extract( + "If deployment succeeds, Orchid gateway port is 7319. Orchid database port is 5432.", + ); + assert_eq!(facts.len(), 1); + assert_eq!(facts[0].subject, "orchid database"); + } + + #[test] + fn comma_numbers_cannot_be_truncated_into_facts() { + for text in [ + "Orchid gateway port is 7,319.", + "Orchid gateway replicas are 1,000.", + "Orchid gateway timeout is 0,5 seconds.", + ] { + assert!(extract(text).is_empty(), "{text}"); + } + } + + #[test] + fn provisional_headers_cannot_be_discarded_at_clause_boundaries() { + for text in [ + "Example configuration:\nOrchid gateway port is 7319.", + "Hypothetical configuration; Orchid gateway port is 7319.", + "EJEMPLO de configuración:\nOrchid gateway port is 7319.", + "Hypothetical configuration. Orchid gateway port is 7319.", + ] { + assert!(extract(text).is_empty(), "{text}"); + } + } + + #[test] + fn record_tag_metadata_does_not_become_fact_scope() { + let text = "[tags: network gateway production] Orchid staging gateway port is 7319."; + let facts = extract(text); + assert_eq!(facts.len(), 1); + assert_eq!(facts[0].subject, "orchid gateway"); + assert_eq!(facts[0].environment.as_deref(), Some("staging")); + assert_eq!(facts[0].attribute, "port"); + assert_eq!(facts[0].value, "7319"); + assert!(text.contains(&facts[0].evidence)); + assert_eq!(facts[0].evidence, "Orchid staging gateway port is 7319."); + assert!(extract("[tags: production] port is 7319.").is_empty()); + assert!(extract("[tags: example] Orchid gateway port is 7319.").is_empty()); + assert!(extract("[tags: production] [tags: gateway] Orchid port is 7319.").is_empty()); + } +} diff --git a/crates/kimetsu-brain/src/fusion.rs b/crates/kimetsu-brain/src/fusion.rs index c8b8dba..1d44d5d 100644 --- a/crates/kimetsu-brain/src/fusion.rs +++ b/crates/kimetsu-brain/src/fusion.rs @@ -211,6 +211,7 @@ mod tests { superseded_hint: false, rerank_policy_tier: 0, claim_revision: None, + facts: vec![], rerank_usefulness: None, rerank_trust: None, }, diff --git a/crates/kimetsu-brain/src/lib.rs b/crates/kimetsu-brain/src/lib.rs index 6b3152d..986b1d0 100644 --- a/crates/kimetsu-brain/src/lib.rs +++ b/crates/kimetsu-brain/src/lib.rs @@ -22,6 +22,10 @@ pub mod embeddings; /// Flagship 1 / Story 1.3: episodic work-resume capture, storage, and surface. pub mod episode; pub mod eval; +pub mod facts; +pub mod fact_values; +pub mod fact_query; +pub mod fact_store; pub mod feedback; pub mod framing; /// #2 knowledge graph: rule-based relation-edge extraction for `memory_edges`. diff --git a/crates/kimetsu-brain/src/migrate.rs b/crates/kimetsu-brain/src/migrate.rs index 68ce5e4..4760694 100644 --- a/crates/kimetsu-brain/src/migrate.rs +++ b/crates/kimetsu-brain/src/migrate.rs @@ -119,6 +119,11 @@ fn migrations() -> &'static [Migration] { description: "scope work episodes by explicit identity", up: crate::schema::migrate_v13_to_v14, }, + Migration { + version: 15, + description: "derive structured fact evidence from redacted memories", + up: crate::schema::migrate_v14_to_v15, + }, ] } diff --git a/crates/kimetsu-brain/src/ordering.rs b/crates/kimetsu-brain/src/ordering.rs index fdbdd46..713ce62 100644 --- a/crates/kimetsu-brain/src/ordering.rs +++ b/crates/kimetsu-brain/src/ordering.rs @@ -168,6 +168,7 @@ mod tests { superseded_hint: false, rerank_policy_tier: 0, claim_revision: None, + facts: vec![], rerank_usefulness: None, rerank_trust: None, } diff --git a/crates/kimetsu-brain/src/project.rs b/crates/kimetsu-brain/src/project.rs index f04217b..f428acb 100644 --- a/crates/kimetsu-brain/src/project.rs +++ b/crates/kimetsu-brain/src/project.rs @@ -378,6 +378,7 @@ impl BrainSession { /// Resolve explicit overrides before legacy sentinels. The explicit zero /// survives a second resolution at the injected/production boundary. pub fn resolve_request_floors(&self, request: &mut ContextRequest) { + request.include_fact_evidence |= self.config.broker.explicit_fact_guard; let semantic = request.min_semantic_score_override.unwrap_or_else(|| { if request.min_semantic_score == 0.0 { self.config.broker.min_semantic_score diff --git a/crates/kimetsu-brain/src/projector.rs b/crates/kimetsu-brain/src/projector.rs index 2d18e3c..4bfd833 100644 --- a/crates/kimetsu-brain/src/projector.rs +++ b/crates/kimetsu-brain/src/projector.rs @@ -246,6 +246,7 @@ fn reset_projection(conn: &Connection) -> KimetsuResult<()> { DELETE FROM sources; DELETE FROM memories; DELETE FROM memory_revisions; + DELETE FROM memory_facts; DELETE FROM memory_proposals; DELETE FROM memories_fts; DELETE FROM memory_citations; @@ -965,6 +966,7 @@ fn apply_memory_accepted(conn: &Connection, event: &Event) -> KimetsuResult<()> conn.execute("UPDATE memory_proposals SET status='accepted', decided_at=?2, decided_by='cli' WHERE proposal_id=?1", params![proposal_id,ts_text(event)?])?; } + crate::fact_store::refresh(conn, memory_id)?; Ok(()) } @@ -1286,6 +1288,7 @@ fn apply_memory_temporal(conn: &Connection, event: &Event) -> KimetsuResult<()> } (None, None) => {} // no-op } + crate::fact_store::refresh(conn, memory_id)?; Ok(()) } @@ -2820,6 +2823,7 @@ fn apply_memory_corrected(conn: &Connection, event: &Event) -> KimetsuResult<()> crate::graph::project_entities(conn, id, text)?; conn.execute("INSERT INTO memory_revisions (memory_id,event_id,text,kind,known_at,effective_at,confidence,use_count,usefulness_score) SELECT memory_id,?2,text,kind,?3,?4,confidence,use_count,usefulness_score FROM memories WHERE memory_id=?1", params![id,event.event_id.to_string(),now,effective])?; + crate::fact_store::refresh(conn, id)?; Ok(()) } diff --git a/crates/kimetsu-brain/src/reinforce.rs b/crates/kimetsu-brain/src/reinforce.rs index 97e7f3d..489f1cb 100644 --- a/crates/kimetsu-brain/src/reinforce.rs +++ b/crates/kimetsu-brain/src/reinforce.rs @@ -408,6 +408,7 @@ mod tests { superseded_hint: false, rerank_policy_tier: 0, claim_revision: None, + facts: vec![], rerank_usefulness: None, rerank_trust: None, }, diff --git a/crates/kimetsu-brain/src/schema.rs b/crates/kimetsu-brain/src/schema.rs index aee6449..0bd116c 100644 --- a/crates/kimetsu-brain/src/schema.rs +++ b/crates/kimetsu-brain/src/schema.rs @@ -1236,3 +1236,23 @@ pub(crate) fn migrate_v13_to_v14(conn: &Connection) -> KimetsuResult<()> { conn.execute_batch("CREATE INDEX IF NOT EXISTS idx_episodes_identity ON work_episodes(repo_root, identity, superseded_by)")?; Ok(()) } + +/// Derived structured evidence is replayable and never replaces memory text. +pub(crate) fn migrate_v14_to_v15(conn: &Connection) -> KimetsuResult<()> { + conn.execute_batch( + "CREATE TABLE IF NOT EXISTS memory_facts ( + memory_id TEXT NOT NULL, claim_revision TEXT NOT NULL, ordinal INTEGER NOT NULL, + source_event_id TEXT NOT NULL, source_digest TEXT NOT NULL, claim_json TEXT NOT NULL, + PRIMARY KEY(memory_id,claim_revision,ordinal));", + )?; + // Migration tools/tests can intentionally provide incomplete old schemas. + for column in ["memory_id", "text", "source_event_id"] { + if !table_has_column(conn, "memories", column)? { + return Ok(()); + } + } + if !table_has_column(conn, "memory_revisions", "revision_id")? { + return Ok(()); + } + crate::fact_store::backfill(conn) +} diff --git a/crates/kimetsu-brain/src/serving.rs b/crates/kimetsu-brain/src/serving.rs index efe5a48..3398a31 100644 --- a/crates/kimetsu-brain/src/serving.rs +++ b/crates/kimetsu-brain/src/serving.rs @@ -76,12 +76,15 @@ impl ServingPolicy { } } pub fn prepare(&self, mut request: ContextRequest, reranking: bool) -> ContextRequest { + request.include_fact_evidence |= self.explicit_fact_guard; + request.defer_fact_budget = + self.explicit_fact_guard && crate::fact_query::parse(&request.query).is_some(); request.budget_tokens = if reranking { self.budget.max(DEFAULT_BUDGET) } else { self.budget }; - request.max_capsules = if reranking { + request.max_capsules = if reranking || self.explicit_fact_guard { self.cap.max(self.pool) } else { self.cap @@ -109,6 +112,11 @@ impl ServingPolicy { ); if self.explicit_fact_guard { crate::answerability::filter_bundle(query, &mut bundle); + if let Some(assessment) = crate::fact_query::evaluate(query, &bundle.capsules) { + bundle.known_fact_conflicts.extend(assessment.conflicting); + bundle.known_fact_conflicts.sort(); + bundle.known_fact_conflicts.dedup(); + } } if self.cap > 0 { bundle.capsules.truncate(self.cap); @@ -130,13 +138,36 @@ impl ServingPolicy { if compress { for capsule in &mut bundle.capsules { capsule.summary = if self.explicit_fact_guard { - crate::answerability::compress_preserving_evidence(query, &capsule.summary, 3) + crate::fact_query::compress_capsule(query, capsule, 3) } else { crate::context::compress_for_render(&capsule.summary, 3) }; } } - self.render(bundle, false, exposure_id) + if !self.explicit_fact_guard { + return self.render(bundle, false, exposure_id); + } + let mut known_conflicts = bundle.known_fact_conflicts.clone(); + if let Some(assessment) = crate::fact_query::evaluate(query, &bundle.capsules) { + known_conflicts.extend(assessment.conflicting); + } + known_conflicts.sort(); + known_conflicts.dedup(); + let count = bundle.capsules.len(); + fit_json(bundle.capsules.clone(), self.budget, |capsules| { + let mut payload = json!({ + "ok":true,"skipped":capsules.is_empty(),"exposure_id":exposure_id, + "capsule_count":capsules.len(),"excluded_count":bundle.excluded.len()+count-capsules.len(), + "capsules":compact_capsules(capsules),"partial_evidence":bundle.evidence_coverage<1.0 || capsules.len() Delivery { if compress { @@ -401,6 +432,7 @@ mod tests { evidence_coverage: 1.0, uncovered_terms: vec![], chronological: false, + known_fact_conflicts: vec![], }; let policy = ServingPolicy { explicit_fact_guard: true, @@ -431,6 +463,7 @@ mod tests { evidence_coverage: 1.0, uncovered_terms: vec![], chronological: false, + known_fact_conflicts: vec![], }; let policy = ServingPolicy { cap: 1, @@ -466,6 +499,7 @@ mod tests { evidence_coverage: 1.0, uncovered_terms: vec![], chronological: false, + known_fact_conflicts: vec![], }; let policy = ServingPolicy::default(); let selected = policy.arbitrate("wal checkpoint", bundle, Some(&StubReranker), 0.0); @@ -476,3 +510,96 @@ mod tests { assert!(!delivery.payload.to_string().contains("remote network")); } } + +#[cfg(test)] +mod conflict_carry_tests { + use super::*; + use crate::context::ContextCapsule; + struct RejectSecond; + impl Reranker for RejectSecond { + fn rerank( + &self, + _: &str, + _: &[&str], + ) -> Result, crate::embeddings::EmbedderError> { + Ok(vec![0.9, 0.0]) + } + fn model_id(&self) -> &str { + "reject-second" + } + } + #[test] + fn guard_reserves_a_bounded_pool_before_the_final_cap() { + let policy = ServingPolicy { + cap: 1, + pool: 32, + explicit_fact_guard: true, + ..Default::default() + }; + assert_eq!( + policy + .prepare(ContextRequest::default(), false) + .max_capsules, + 32 + ); + } + #[test] + fn capsule_cap_does_not_turn_conflicting_evidence_into_support() { + let capsules = [("a", "7319"), ("b", "7320")] + .into_iter() + .map(|(id, value)| { + let text = format!("Orchid gateway port is {value}."); + let mut c = ContextCapsule::wire_minimal(text.clone(), "memory".into(), 0.99); + c.expansion_handle = format!("memory:{id}"); + c.claim_revision = Some(format!("baseline:{id}")); + c.facts = crate::facts::extract(&text) + .into_iter() + .map(|claim| crate::fact_store::StoredFact { + memory_id: id.into(), + claim_revision: format!("baseline:{id}"), + source_event_id: "source".into(), + valid_from: None, + valid_to: None, + claim, + }) + .collect(); + c + }) + .collect(); + let bundle = ContextBundle { + stage: "localization".into(), + budget_tokens: 6000, + used_tokens: 0, + capsules, + excluded: vec![], + skipped: false, + top_score: 0.99, + top_abs_evidence: 0.99, + evidence_coverage: 1.0, + uncovered_terms: vec![], + chronological: false, + known_fact_conflicts: vec![], + }; + let policy = ServingPolicy { + cap: 1, + budget: 6000, + explicit_fact_guard: true, + ..Default::default() + }; + let q = "What is the Orchid gateway port?"; + let eligible = policy.arbitrate(q, bundle.clone(), Some(&RejectSecond), 0.0); + let eligible_delivery = policy.render_for_query(q, eligible, true, EVAL_EXPOSURE_ID); + assert_eq!( + eligible_delivery.payload["answerability"]["status"], + "supported" + ); + let chosen = policy.arbitrate(q, bundle, None, 0.0); + assert_eq!(chosen.capsules.len(), 1); + let delivery = policy.render_for_query(q, chosen, true, EVAL_EXPOSURE_ID); + assert_eq!(delivery.payload["answerability"]["status"], "conflicting"); + assert_eq!( + delivery.payload["answerability"]["conflicting"], + serde_json::json!(["port"]) + ); + } +} diff --git a/crates/kimetsu-chat/src/ask.rs b/crates/kimetsu-chat/src/ask.rs index 5a4c6b2..c3c6870 100644 --- a/crates/kimetsu-chat/src/ask.rs +++ b/crates/kimetsu-chat/src/ask.rs @@ -474,6 +474,7 @@ mod tests { superseded_hint: false, rerank_policy_tier: 0, claim_revision: None, + facts: vec![], rerank_usefulness: None, rerank_trust: None, } diff --git a/crates/kimetsu-cli/src/commands/brain.rs b/crates/kimetsu-cli/src/commands/brain.rs index eaf4c87..c1ad94e 100644 --- a/crates/kimetsu-cli/src/commands/brain.rs +++ b/crates/kimetsu-cli/src/commands/brain.rs @@ -1915,8 +1915,14 @@ pub(crate) fn try_daemon_retrieve( capsules, skipped, top_score, + known_fact_conflicts, }) => Some(daemon_capsules_to_bundle( - workspace, request, capsules, skipped, top_score, + workspace, + request, + capsules, + skipped, + top_score, + known_fact_conflicts, )), _ => { // Unreachable/errored: we already know it didn't answer, so spawn @@ -1946,6 +1952,7 @@ pub(crate) fn daemon_capsules_to_bundle( capsules: Vec, skipped: bool, top_score: f32, + known_fact_conflicts: Vec, ) -> kimetsu_brain::context::ContextBundle { use kimetsu_brain::context::{ContextBundle, ContextCapsule}; let capsules: Vec = capsules @@ -1955,6 +1962,7 @@ pub(crate) fn daemon_capsules_to_bundle( capsule.id = c.id; capsule.expansion_handle = c.expansion_handle; capsule.claim_revision = c.claim_revision; + capsule.facts = c.facts; capsule }) .collect(); @@ -1983,6 +1991,7 @@ pub(crate) fn daemon_capsules_to_bundle( // Ordering queries never reach the daemon (`try_daemon_retrieve` // declines them), so a bundle from here is never time-ordered. chronological: false, + known_fact_conflicts, } } @@ -4136,3 +4145,75 @@ pub(crate) fn brain_skills(args: SkillsArgs) -> KimetsuResult<()> { skill_synth::print_synthesis_report(&report); Ok(()) } + +#[cfg(all(test, feature = "embeddings"))] +mod conflict_carry_tests { + use super::*; + #[test] + fn daemon_conversion_keeps_conflict_notice_after_one_source_is_trimmed() { + let text = "Orchid gateway port is 7319."; + let request = kimetsu_brain::context::ContextRequest { + query: "What is the Orchid gateway port?".into(), + ..Default::default() + }; + let capsule = embed_daemon::proto::Capsule { + id: "a".into(), + expansion_handle: "memory:a".into(), + claim_revision: Some("baseline:a".into()), + facts: kimetsu_brain::facts::extract(text) + .into_iter() + .map(|claim| kimetsu_brain::fact_store::StoredFact { + memory_id: "a".into(), + claim_revision: "baseline:a".into(), + source_event_id: "source".into(), + valid_from: None, + valid_to: None, + claim, + }) + .collect(), + summary: text.into(), + kind: "memory".into(), + score: 0.99, + }; + let response = embed_daemon::proto::Response::Capsules { + capsules: vec![capsule], + skipped: false, + top_score: 0.99, + known_fact_conflicts: vec!["port".into()], + }; + let response: embed_daemon::proto::Response = + serde_json::from_str(&serde_json::to_string(&response).unwrap()).unwrap(); + let embed_daemon::proto::Response::Capsules { + capsules, + skipped, + top_score, + known_fact_conflicts, + } = response + else { + panic!("expected capsules") + }; + let workspace = + std::env::temp_dir().join(format!("kimetsu-conflict-wire-{}", ulid::Ulid::new())); + let bundle = daemon_capsules_to_bundle( + &workspace, + &request, + capsules, + skipped, + top_score, + known_fact_conflicts, + ); + assert_eq!( + kimetsu_brain::fact_query::evaluate(&request.query, &bundle.capsules) + .unwrap() + .status, + "supported" + ); + let notice = kimetsu_brain::fact_query::notice_with_conflicts( + &request.query, + &bundle.capsules, + &bundle.known_fact_conflicts, + ) + .unwrap(); + assert!(notice.contains("conflicting values for port")); + } +} diff --git a/crates/kimetsu-cli/src/commands/hooks.rs b/crates/kimetsu-cli/src/commands/hooks.rs index b0882bc..19409f7 100644 --- a/crates/kimetsu-cli/src/commands/hooks.rs +++ b/crates/kimetsu-cli/src/commands/hooks.rs @@ -89,7 +89,14 @@ pub(crate) fn brain_context_hook(args: ContextHookArgs) -> KimetsuResult<()> { return flush_warm_start(warm_start_block, &mut state, state_path.as_deref()); } + let explicit_fact_guard = kimetsu_core::paths::ProjectPaths::discover(&workspace) + .ok() + .and_then(|paths| project::load_config(&paths).ok()) + .map(|cfg| cfg.broker.explicit_fact_guard) + .unwrap_or(false); + let request = ContextRequest { + include_fact_evidence: explicit_fact_guard, stage: "localization".to_string(), query: prompt, budget_tokens: 2000, @@ -98,23 +105,62 @@ pub(crate) fn brain_context_hook(args: ContextHookArgs) -> KimetsuResult<()> { ..Default::default() }; + let defer_fact_budget = + explicit_fact_guard && kimetsu_brain::fact_query::parse(&request.query).is_some(); + // Retrieval: try the warm daemon first (semantic); fall back to // floored-FTS on any miss (daemon disabled / unreachable / cold). let (mut bundle, retrieval_path) = match try_daemon_retrieve(&workspace, &request) { Some(b) => (b, "daemon"), - None => match project::retrieve_context_lexical_readonly(&workspace, request.clone()) { + None => match project::retrieve_context_lexical_readonly(&workspace, { + let mut fallback_request = request.clone(); + fallback_request.defer_fact_budget = defer_fact_budget; + if explicit_fact_guard { + fallback_request.max_capsules = fallback_request + .max_capsules + .max(kimetsu_brain::serving::RERANK_POOL); + } + fallback_request + }) { Ok(b) => (b, "fts_fallback"), Err(_) => return Ok(()), // Brain not initialized — silent fail }, }; - let explicit_fact_guard = kimetsu_core::paths::ProjectPaths::discover(&workspace) - .ok() - .and_then(|paths| project::load_config(&paths).ok()) - .map(|cfg| cfg.broker.explicit_fact_guard) - .unwrap_or(false); if explicit_fact_guard { kimetsu_brain::answerability::filter_bundle(&request.query, &mut bundle); + if let Some(assessment) = + kimetsu_brain::fact_query::evaluate(&request.query, &bundle.capsules) + { + bundle.known_fact_conflicts.extend(assessment.conflicting); + bundle.known_fact_conflicts.sort(); + bundle.known_fact_conflicts.dedup(); + } + if retrieval_path == "fts_fallback" && defer_fact_budget { + // Observe eligible contradictions first, then restore the hook's + // original half-budget and cap before any text is rendered. + let capsule_budget = request.budget_tokens / 2; + let mut used = 0u32; + for capsule in std::mem::take(&mut bundle.capsules) { + if (args.max_capsules == 0 || bundle.capsules.len() < args.max_capsules) + && used.saturating_add(capsule.token_estimate) <= capsule_budget + { + used += capsule.token_estimate; + bundle.capsules.push(capsule); + } else { + bundle.excluded.push(capsule); + } + } + } else if args.max_capsules > 0 { + bundle.capsules.truncate(args.max_capsules); + } + bundle.skipped |= bundle.capsules.is_empty(); + bundle.used_tokens = bundle.capsules.iter().map(|c| c.token_estimate).sum(); + bundle.top_score = bundle + .capsules + .iter() + .map(|c| c.score) + .fold(0.0_f32, f32::max); } // C7: emit a context.served event BEFORE the early-return so misses are @@ -277,16 +323,26 @@ pub(crate) fn brain_context_hook(args: ContextHookArgs) -> KimetsuResult<()> { additional_context.push('\n'); additional_context.push_str(kimetsu_brain::ordering::CHRONOLOGICAL_NOTE); } + let known_fact_conflicts = if explicit_fact_guard { + let mut known = bundle.known_fact_conflicts.clone(); + known.extend( + kimetsu_brain::fact_query::evaluate(&request.query, &bundle.capsules) + .map(|a| a.conflicting) + .unwrap_or_default(), + ); + known.sort(); + known.dedup(); + known + } else { + Vec::new() + }; + let mut rendered_fact_capsules = Vec::new(); for (idx, capsule) in capsules_to_render.iter().enumerate() { // v1.5 (Story 2.1): render-time compression — runs AFTER retrieval and // reranking, purely on the injected text. Full summary untouched in DB. let rendered: String = if compress_capsules { if explicit_fact_guard { - kimetsu_brain::answerability::compress_preserving_evidence( - &request.query, - &capsule.summary, - 3, - ) + kimetsu_brain::fact_query::compress_capsule(&request.query, capsule, 3) } else { kimetsu_brain::context::compress_for_render(&capsule.summary, 3) } @@ -308,6 +364,21 @@ pub(crate) fn brain_context_hook(args: ContextHookArgs) -> KimetsuResult<()> { additional_context.push_str("Relevant project memory (not independently verified): "); } additional_context.push_str(&text); + if explicit_fact_guard { + let mut visible = (**capsule).clone(); + visible.summary = text; + rendered_fact_capsules.push(visible); + } + } + if explicit_fact_guard { + if let Some(notice) = kimetsu_brain::fact_query::notice_with_conflicts( + &request.query, + &rendered_fact_capsules, + &known_fact_conflicts, + ) { + additional_context.push('\n'); + additional_context.push_str(¬ice); + } } // v2.6: when the bundle collectively covers only part of the question, say diff --git a/crates/kimetsu-cli/src/embed_daemon/proto.rs b/crates/kimetsu-cli/src/embed_daemon/proto.rs index de0842e..cdb8dcd 100644 --- a/crates/kimetsu-cli/src/embed_daemon/proto.rs +++ b/crates/kimetsu-cli/src/embed_daemon/proto.rs @@ -43,6 +43,8 @@ pub enum Response { capsules: Vec, skipped: bool, top_score: f32, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + known_fact_conflicts: Vec, }, /// Warm/Ping identity. Info { @@ -68,6 +70,8 @@ pub struct Capsule { pub expansion_handle: String, #[serde(default)] pub claim_revision: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub facts: Vec, pub summary: String, pub kind: String, pub score: f32, @@ -136,6 +140,17 @@ mod tests { } } + #[test] + fn capsule_wire_preserves_optional_structured_evidence() { + let wire = serde_json::json!({"id":"c", "expansion_handle":"memory:m", "claim_revision":"baseline:m", "summary":"Orchid gateway port is 7319.", "kind":"memory", "score":0.99, + "facts":[{"memory_id":"m","claim_revision":"baseline:m","source_event_id":"accepted", "valid_from":null,"valid_to":null, + "claim":{"subject":"orchid gateway","environment":null,"attribute":"port","value":"7319","evidence":"Orchid gateway port is 7319."}}]}); + let capsule: Capsule = serde_json::from_value(wire).unwrap(); + assert_eq!( + serde_json::to_value(capsule).unwrap()["facts"][0]["claim"]["value"], + "7319" + ); + } #[test] fn response_round_trips() { let resp = Response::Capsules { @@ -143,12 +158,14 @@ mod tests { id: "m1".into(), expansion_handle: "memory:m1".into(), claim_revision: Some("rev1".into()), + facts: vec![], summary: "repo:fact - x".into(), kind: "memory".into(), score: 0.9, }], skipped: false, top_score: 0.9, + known_fact_conflicts: vec![], }; let mut buf = Vec::new(); write_line(&mut buf, &resp).unwrap(); @@ -164,3 +181,28 @@ mod tests { assert_eq!(got.unwrap_err().kind(), io::ErrorKind::UnexpectedEof); } } + +#[cfg(test)] +mod conflict_wire_tests { + use super::*; + #[test] + fn response_preserves_conflicts_after_capsules_have_been_trimmed() { + let json = serde_json::json!({"capsules":{"capsules":[],"skipped":true,"top_score":0.0,"known_fact_conflicts":["port"]}}); + let response: Response = serde_json::from_value(json).unwrap(); + assert_eq!( + serde_json::to_value(response).unwrap()["capsules"]["known_fact_conflicts"], + serde_json::json!(["port"]) + ); + } +} + +#[cfg(test)] +mod legacy_conflict_wire_tests { + use super::*; + #[test] + fn legacy_response_without_conflicts_still_roundtrips_without_new_fields() { + let legacy = serde_json::json!({"capsules":{"capsules":[],"skipped":true,"top_score":0.0}}); + let response: Response = serde_json::from_value(legacy.clone()).unwrap(); + assert_eq!(serde_json::to_value(response).unwrap(), legacy); + } +} diff --git a/crates/kimetsu-cli/src/embed_daemon/server.rs b/crates/kimetsu-cli/src/embed_daemon/server.rs index e07cf45..ace1fab 100644 --- a/crates/kimetsu-cli/src/embed_daemon/server.rs +++ b/crates/kimetsu-cli/src/embed_daemon/server.rs @@ -100,6 +100,15 @@ impl DaemonState { }; } proto::Response::Capsules { + known_fact_conflicts: bundle.payload["answerability"]["conflicting"] + .as_array() + .map(|values| { + values + .iter() + .filter_map(|v| v.as_str().map(str::to_owned)) + .collect() + }) + .unwrap_or_default(), capsules: bundle .capsules .iter() @@ -107,6 +116,7 @@ impl DaemonState { id: c.id.clone(), expansion_handle: c.expansion_handle.clone(), claim_revision: c.claim_revision.clone(), + facts: c.facts.clone(), summary: c.summary.clone(), kind: c.kind.clone(), score: c.score, diff --git a/crates/kimetsu-cli/src/main.rs b/crates/kimetsu-cli/src/main.rs index 4a18584..4785c67 100644 --- a/crates/kimetsu-cli/src/main.rs +++ b/crates/kimetsu-cli/src/main.rs @@ -4174,6 +4174,7 @@ scope = 0.1 id: "m1".into(), expansion_handle: "memory:m1".into(), claim_revision: Some("rev1".into()), + facts: vec![], summary: "repo:fact - x".to_string(), kind: "memory".to_string(), score: 0.9, @@ -4182,7 +4183,7 @@ scope = 0.1 // the neutral (1.0, []) — which is the point: an unmeasurable bundle // must render as no claim, never as a false "memory does not cover". let tmp = std::env::temp_dir().join("kimetsu-daemon-bundle-test-no-brain"); - let bundle = daemon_capsules_to_bundle(&tmp, &request, wire, false, 0.9); + let bundle = daemon_capsules_to_bundle(&tmp, &request, wire, false, 0.9, vec![]); assert_eq!(bundle.capsules.len(), 1); assert_eq!(bundle.capsules[0].summary, "repo:fact - x"); assert_eq!(bundle.capsules[0].id, "m1"); @@ -4215,7 +4216,7 @@ scope = 0.1 ..Default::default() }; let tmp = std::env::temp_dir().join("kimetsu-daemon-bundle-test-skipped"); - let bundle = daemon_capsules_to_bundle(&tmp, &request, Vec::new(), true, 0.1); + let bundle = daemon_capsules_to_bundle(&tmp, &request, Vec::new(), true, 0.1, vec![]); assert!(bundle.skipped); assert_eq!(bundle.evidence_coverage, 0.0); assert!(bundle.uncovered_terms.is_empty()); diff --git a/crates/kimetsu-core/src/lib.rs b/crates/kimetsu-core/src/lib.rs index 5cddb89..18fc619 100644 --- a/crates/kimetsu-core/src/lib.rs +++ b/crates/kimetsu-core/src/lib.rs @@ -7,7 +7,7 @@ pub mod memory; pub mod paths; pub mod secret; -pub const KIMETSU_SCHEMA_VERSION: i64 = 14; +pub const KIMETSU_SCHEMA_VERSION: i64 = 15; /// The `project.toml` config-file format version. Deliberately decoupled /// from `KIMETSU_SCHEMA_VERSION` (the brain.db schema): the DB schema can /// advance via migrations without forcing every project.toml to be rewritten. diff --git a/crates/kimetsu-remote/src/rpc.rs b/crates/kimetsu-remote/src/rpc.rs index d29a88b..d4c8332 100644 --- a/crates/kimetsu-remote/src/rpc.rs +++ b/crates/kimetsu-remote/src/rpc.rs @@ -220,21 +220,21 @@ async fn dispatch_request( return handle_server_ingest(ingest, &repo, &root, id, session).await; } - // 6c. `kimetsu_brain_context` with a server-side reranker: intercept before - // generic dispatch so we can inject the reranker into the tool body. + // 6c. The server owns context model policy, including an explicit absence + // of a reranker. Generic stdio dispatch would reopen the repository's local + // reranker and consult a process-wide stdio warm-start cache. // The allowlist + auth + rate-limit checks above have already run. if req.method == "tools/call" && req.params.get("name").and_then(|n| n.as_str()) == Some("kimetsu_brain_context") - && state.reranker.is_some() { - let reranker = state.reranker.clone().expect("checked above"); + let reranker = state.reranker.clone(); let arguments = req .params .get("arguments") .cloned() .unwrap_or_else(|| serde_json::json!({})); let res = tokio::task::spawn_blocking(move || { - kimetsu_chat::brain_context_tool(&root, &arguments, Some(reranker.as_ref())) + kimetsu_chat::brain_context_tool(&root, &arguments, reranker.as_deref()) }) .await; diff --git a/crates/kimetsu-remote/tests/http_roundtrip.rs b/crates/kimetsu-remote/tests/http_roundtrip.rs index 9d624b4..2cf5cb3 100644 --- a/crates/kimetsu-remote/tests/http_roundtrip.rs +++ b/crates/kimetsu-remote/tests/http_roundtrip.rs @@ -119,6 +119,10 @@ async fn record_then_context_round_trips() { // Query with words from the lesson but NOT the asserted token, so the match // can only come from the retrieved capsule (not the echoed query). let ctx = inner(&send(tmp.path(), "repo-a", context("deployment restart flushing")).await); + assert!( + ctx.get("warm_start").is_none(), + "remote requests must not use the stdio session cache: {ctx}" + ); assert_eq!(ctx["skipped"], json!(false), "expected a hit: {ctx}"); assert!( ctx["capsules"].to_string().contains("wobblecache"), From 281c6df91c618c86eab0bd1fad973513b6716a6f Mon Sep 17 00:00:00 2001 From: RodCor Date: Mon, 7 Sep 2026 10:31:48 -0300 Subject: [PATCH 29/34] Document structured fact evidence validation and remaining gaps --- docs/audits/2026-09-07-structured-facts.md | 73 + .../.gitattributes | 1 + .../answerability-gold.json | 326 + .../check-delivery.py | 70 + .../checks/conflict-carry-cli-green.log | 19 + .../checks/conflict-carry-wire-red.log | 29 + .../checks/deferred-fact-budget-green.log | 14 + .../checks/deferred-fact-budget-red.log | 30 + .../checks/deferred-fact-focused-green.log | 48 + .../checks/deferred-hook-cli-green.log | 19 + .../checks/fact-benchmark-observation-red.log | 26 + .../structured-facts-agent-ingress-red.log | 5 + .../structured-facts-benchmark-tests.log | 142 + .../structured-facts-delivery-probes.log | 6 + .../checks/structured-facts-harness-build.log | 5 + .../checks/structured-facts-python-tests.log | 15 + .../checks/structured-facts-release.log | 11 + .../checks/structured-facts-workspace.log | 1736 +++++ .../checks/structured-remote-green.log | 56 + .../checks/structured-remote-isolated.log | 17 + .../independent-review.md | 5 + .../2026-09-07-structured-facts/manifest.json | 225 + .../provenance.json | 31 + .../answerability-regression/1-baseline.json | 1101 +++ .../1-baseline.stderr.log | 6 + .../1-baseline.stdout.log | 1101 +++ .../answerability-regression/1-candidate.json | 1237 +++ .../1-candidate.stderr.log | 6 + .../1-candidate.stdout.log | 1237 +++ .../answerability-regression/comparison.json | 165 + .../answerability-regression/comparison.md | 26 + .../results/development/1-baseline.json | 6811 +++++++++++++++++ .../results/development/1-baseline.stderr.log | 4 + .../results/development/1-baseline.stdout.log | 6811 +++++++++++++++++ .../results/development/1-candidate.json | 6811 +++++++++++++++++ .../development/1-candidate.stderr.log | 4 + .../development/1-candidate.stdout.log | 6811 +++++++++++++++++ .../results/development/comparison.json | 155 + .../results/development/comparison.md | 26 + .../results/validation/1-baseline.json | 1495 ++++ .../results/validation/1-baseline.stderr.log | 8 + .../results/validation/1-baseline.stdout.log | 1495 ++++ .../results/validation/1-candidate.json | 1741 +++++ .../results/validation/1-candidate.stderr.log | 8 + .../results/validation/1-candidate.stdout.log | 1741 +++++ .../results/validation/2-baseline.json | 1495 ++++ .../results/validation/2-baseline.stderr.log | 8 + .../results/validation/2-baseline.stdout.log | 1495 ++++ .../results/validation/2-candidate.json | 1741 +++++ .../results/validation/2-candidate.stderr.log | 8 + .../results/validation/2-candidate.stdout.log | 1741 +++++ .../results/validation/comparison.json | 192 + .../results/validation/comparison.md | 26 + .../run-comparisons.ps1 | 38 + .../2026-09-07-structured-facts/summarize.py | 71 + .../2026-09-07-structured-facts/summary.json | 1657 ++++ .../validation-frozen.json | 539 ++ .../plans/2026-09-07-structured-facts.md | 42 + .../2026-09-07-structured-facts-design.md | 20 + 59 files changed, 50782 insertions(+) create mode 100644 docs/audits/2026-09-07-structured-facts.md create mode 100644 docs/audits/2026-09-07-structured-facts/.gitattributes create mode 100644 docs/audits/2026-09-07-structured-facts/answerability-gold.json create mode 100644 docs/audits/2026-09-07-structured-facts/check-delivery.py create mode 100644 docs/audits/2026-09-07-structured-facts/checks/conflict-carry-cli-green.log create mode 100644 docs/audits/2026-09-07-structured-facts/checks/conflict-carry-wire-red.log create mode 100644 docs/audits/2026-09-07-structured-facts/checks/deferred-fact-budget-green.log create mode 100644 docs/audits/2026-09-07-structured-facts/checks/deferred-fact-budget-red.log create mode 100644 docs/audits/2026-09-07-structured-facts/checks/deferred-fact-focused-green.log create mode 100644 docs/audits/2026-09-07-structured-facts/checks/deferred-hook-cli-green.log create mode 100644 docs/audits/2026-09-07-structured-facts/checks/fact-benchmark-observation-red.log create mode 100644 docs/audits/2026-09-07-structured-facts/checks/structured-facts-agent-ingress-red.log create mode 100644 docs/audits/2026-09-07-structured-facts/checks/structured-facts-benchmark-tests.log create mode 100644 docs/audits/2026-09-07-structured-facts/checks/structured-facts-delivery-probes.log create mode 100644 docs/audits/2026-09-07-structured-facts/checks/structured-facts-harness-build.log create mode 100644 docs/audits/2026-09-07-structured-facts/checks/structured-facts-python-tests.log create mode 100644 docs/audits/2026-09-07-structured-facts/checks/structured-facts-release.log create mode 100644 docs/audits/2026-09-07-structured-facts/checks/structured-facts-workspace.log create mode 100644 docs/audits/2026-09-07-structured-facts/checks/structured-remote-green.log create mode 100644 docs/audits/2026-09-07-structured-facts/checks/structured-remote-isolated.log create mode 100644 docs/audits/2026-09-07-structured-facts/independent-review.md create mode 100644 docs/audits/2026-09-07-structured-facts/manifest.json create mode 100644 docs/audits/2026-09-07-structured-facts/provenance.json create mode 100644 docs/audits/2026-09-07-structured-facts/results/answerability-regression/1-baseline.json create mode 100644 docs/audits/2026-09-07-structured-facts/results/answerability-regression/1-baseline.stderr.log create mode 100644 docs/audits/2026-09-07-structured-facts/results/answerability-regression/1-baseline.stdout.log create mode 100644 docs/audits/2026-09-07-structured-facts/results/answerability-regression/1-candidate.json create mode 100644 docs/audits/2026-09-07-structured-facts/results/answerability-regression/1-candidate.stderr.log create mode 100644 docs/audits/2026-09-07-structured-facts/results/answerability-regression/1-candidate.stdout.log create mode 100644 docs/audits/2026-09-07-structured-facts/results/answerability-regression/comparison.json create mode 100644 docs/audits/2026-09-07-structured-facts/results/answerability-regression/comparison.md create mode 100644 docs/audits/2026-09-07-structured-facts/results/development/1-baseline.json create mode 100644 docs/audits/2026-09-07-structured-facts/results/development/1-baseline.stderr.log create mode 100644 docs/audits/2026-09-07-structured-facts/results/development/1-baseline.stdout.log create mode 100644 docs/audits/2026-09-07-structured-facts/results/development/1-candidate.json create mode 100644 docs/audits/2026-09-07-structured-facts/results/development/1-candidate.stderr.log create mode 100644 docs/audits/2026-09-07-structured-facts/results/development/1-candidate.stdout.log create mode 100644 docs/audits/2026-09-07-structured-facts/results/development/comparison.json create mode 100644 docs/audits/2026-09-07-structured-facts/results/development/comparison.md create mode 100644 docs/audits/2026-09-07-structured-facts/results/validation/1-baseline.json create mode 100644 docs/audits/2026-09-07-structured-facts/results/validation/1-baseline.stderr.log create mode 100644 docs/audits/2026-09-07-structured-facts/results/validation/1-baseline.stdout.log create mode 100644 docs/audits/2026-09-07-structured-facts/results/validation/1-candidate.json create mode 100644 docs/audits/2026-09-07-structured-facts/results/validation/1-candidate.stderr.log create mode 100644 docs/audits/2026-09-07-structured-facts/results/validation/1-candidate.stdout.log create mode 100644 docs/audits/2026-09-07-structured-facts/results/validation/2-baseline.json create mode 100644 docs/audits/2026-09-07-structured-facts/results/validation/2-baseline.stderr.log create mode 100644 docs/audits/2026-09-07-structured-facts/results/validation/2-baseline.stdout.log create mode 100644 docs/audits/2026-09-07-structured-facts/results/validation/2-candidate.json create mode 100644 docs/audits/2026-09-07-structured-facts/results/validation/2-candidate.stderr.log create mode 100644 docs/audits/2026-09-07-structured-facts/results/validation/2-candidate.stdout.log create mode 100644 docs/audits/2026-09-07-structured-facts/results/validation/comparison.json create mode 100644 docs/audits/2026-09-07-structured-facts/results/validation/comparison.md create mode 100644 docs/audits/2026-09-07-structured-facts/run-comparisons.ps1 create mode 100644 docs/audits/2026-09-07-structured-facts/summarize.py create mode 100644 docs/audits/2026-09-07-structured-facts/summary.json create mode 100644 docs/audits/2026-09-07-structured-facts/validation-frozen.json create mode 100644 docs/superpowers/plans/2026-09-07-structured-facts.md create mode 100644 docs/superpowers/specs/2026-09-07-structured-facts-design.md diff --git a/docs/audits/2026-09-07-structured-facts.md b/docs/audits/2026-09-07-structured-facts.md new file mode 100644 index 0000000..53b68ba --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts.md @@ -0,0 +1,73 @@ +# Structured fact evidence — 2026-09-07 + +Structured fact evidence reduced unwanted injections from 15/18 to 3/18 on the new synthetic fixture, retaining 24/27 positive hits. Exact answerability matched 36/45 queries in both repeats. Compound retrieval and unsupported-subject fallback remain incomplete, so the feature stays opt-in. + +Kimetsu now has a local, rebuildable fact projection for explicit configuration statements. The existing `broker.explicit_fact_guard` switch controls retrieval and delivery of these facts and remains false by default. No new model or dependency is introduced. + +Implementation: `3ae8329`; BrainBenchmark: `2c74dad`. No installation, merge, push or live configuration change was performed. Binary hashes and verification counts are in [provenance.json](2026-09-07-structured-facts/provenance.json). + +## Measured outcome + +The campaign contains 688 observations over 299 distinct scenario/query cases, with no execution errors or unpaired scenarios. There were no positive-hit losses between builds. Rankings and answerability meaning were stable across the two new-fixture repeats. + +| Fixture | Positive hits, old → new | Unwanted injections, old → new | Subsequent-query p95, old → new | +|---|---:|---:|---:| +| Development, 210 queries | 169/197 → 169/197 | 7/13 → 7/13 | 1,111.7 → 1,094.0 ms | +| Prior answerability, 44 queries | 24/24 → 24/24 | 0/20 → 0/20 | 409.1 → 380.7 ms | +| New structured facts, 45 queries, two repeats | 24/27 → 24/27 | 15/18 → 3/18 | 376.6 → 386.6 ms | + +On the new fixture, the unwanted-injection rate fell from 83.3% to 16.7%: a 66.7 percentage-point reduction, or 80% fewer injection failures. Exact status, supported values, missing attributes and conflicts matched on 36/45 queries per repeat (80%; 72/90 observations): 33/42 direct fact questions and all three broad-question controls. The older binary does not emit structured answerability, so its lack of that metadata is not presented as an answer-accuracy comparison. + +New-fixture p95 increased by 10.0 ms (2.7%) and mean MCP result size increased from 613.5 to 651.2 bytes (6.2%). Peak MCP working set was effectively unchanged at 653.5 MiB on this optional mMARCO configuration; the development TinyBERT run remained about 281.2 MiB. These short, sequential runs are descriptive, not proof of zero overhead or a statistically established speed change. No separate large-corpus storage or ingestion-cost benchmark was run. + +The nine exact-metadata failures per repeat fall into three repeated patterns: + +- Three port-and-password questions retrieved no port evidence. Delivery correctly reported missing evidence but failed the corpus-based partial-answer expectation. +- Three port-and-retries questions retrieved retries but omitted port. They counted as positive hits while still lacking complete attribute coverage—an example of why hit@4 alone is insufficient. +- Three questions using the subject word `Unknown` fell outside the conservative subject grammar and used legacy retrieval. They emitted unrelated port evidence and no structured answerability. These account for all three remaining unwanted injections. + +The first two findings point to retrieval per requested attribute; the third points to safer handling when a direct fact question has unsupported scope. No parameters, grammar or fixture labels were changed after these results. A subsequent fix should use these as development cases and reserve a new evaluation set. + +The fixture repeats templates across only three project names; it is not 45 independent task families. It tests delivered evidence, not generated-answer correctness. See [summary.json](2026-09-07-structured-facts/summary.json) for every mismatch and [the paired result](2026-09-07-structured-facts/results/validation/comparison.md) for measurement definitions. + +## Behavior + +The projection stores subject, attribute, value, environment, exact evidence excerpt, memory identity, claim revision and source event. Validity comes from the memory lifecycle. Corrections replace the projection transactionally; replay rebuilds it. Revision and text digest checks prevent facts from a different revision or historical temporary view from supporting a delivered capsule. + +The normal agent-facing record API prefixes lessons with `[tags: ...]`. Extraction recognizes one bounded metadata prefix while taking subject and environment only from the statement itself. An isolated MCP record-to-context probe covers this ingress path in addition to direct CLI memory writes. + +Recognized questions name one subject and up to four configuration attributes. Subject and environment must match explicitly. Delivery reports supported attributes with source handles and identifies missing or conflicting attributes. It recomputes support against the final visible evidence; a conflict already observed among eligible candidates survives capsule limits and output trimming. This does not discover conflicts outside the retrieved candidate pool. + +For these recognized requests, a bounded candidate pool reaches arbitration before token-budget admission. The final MCP renderer enforces its serialized byte budget. The lexical hook records conflicts first and then reapplies its original capsule budget and cap. + +For example, a port statement can support the port part of a port-and-timeout question while reporting timeout missing. A production value cannot fill a staging request. Two distinct current timeout values produce a conflict instead of selecting one as an answer. + +## Arithmetic + +Numeric equivalence uses bounded, checked integer fractions rather than floating-point tolerances. A decimal is represented as an integer numerator over a power of ten, reduced by the greatest common divisor and scaled to seconds or bytes. Cross-cancellation limits intermediate overflow. Thus `30 seconds` and `30000 ms` share a comparison key, while `512 MB` and `512 MiB` remain distinct. Evidence excerpts retain the original text; displayed numbers and units remain unconverted, although casing and whitespace may be normalized. Unsupported units, malformed values and overflow use exact comparison instead of guessed conversions. + +## Evaluation protocol + +The prior answerability binary is the baseline, with its guard enabled. The candidate also has the guard enabled. Model, score floor, threads and token budget are fixed on both sides. The 210-query development corpus and the prior 44-query answerability fixture are regression sets. The new 45-query synthetic fixture was frozen before candidate inference; its SHA-256 is `6b90f328e989fa0addf7c9a6d2468114fa1b233800f6e979a83830250dd4e469`. + +The new fixture has 27 positive and 18 negative queries across three template families, including scope, environment, explicit absence, partial evidence and opposing values. Its expected answerability states are corpus-based: a retrieval omission can therefore fail the expectation even if delivery accurately describes its limited evidence. Two paired repeats check stability. This is assistant-authored synthetic evidence, not independent validation of arbitrary agent tasks. + +BrainBenchmark retains delivered answerability alongside capsule text, quality and latency observations. The summary checks exact status and missing attributes, while retaining individual mismatches for inspection. It does not measure generated-answer accuracy or use an LLM judge. + +A separate value expectation file was also frozen before inference (`d4d8b3f84282ea52515a1e9dc4fe7fe20f1e26ef45c4905cefcd984db0540445`). Every repeat is checked for status, values, missing attributes and conflicts. Supported source handles must refer to final delivered capsules. Repeat comparisons ignore newly generated memory IDs while retaining them in raw evidence. + +The embeddings-enabled workspace run exposed a separate remote model-policy bug: with no server reranker, requests fell through to the stdio handler and loaded the repository's local reranker. Remote context dispatch now uses the server's optional reranker directly, including its disabled state, and avoids the stdio warm-start cache. + +## Limits + +Extraction deliberately accepts a small grammar. Comma-qualified statements and provisional examples are rejected rather than assigned an inferred scope. Unsupported prose retains the prior retrieval behavior. Environment aliases, arbitrary relations, broad paraphrases and general entailment are not solved by this implementation. + +Fact maintenance adds local write and storage work. Hydration is skipped when the guard is disabled. Local extraction and comparison require no model calls; evidence injected into an agent still consumes context tokens. Performance measurements below must determine overhead rather than assuming it is zero. + +## Verification and measured results + +The final embeddings-enabled workspace run passed 1,470 tests with six ignored. BrainBenchmark passed 132 Rust tests and 18 Python tests. Focused failures were observed before fixes for the budget, daemon transport, remote dispatch and tagged agent-ingress cases. Raw logs are retained under `2026-09-07-structured-facts/checks`. + +The optimized CLI and harness builds succeeded. Six isolated release-binary probes passed: tagged MCP record-to-context, partial answers, conflict at cap one, equivalent units, conflict across an intermediate budget, and wrong-environment rejection. Probes use Noop embeddings with reranking and daemon autostart disabled. The campaign then used the cached models listed above, with no builds or tests running alongside inference. + +To reproduce the paired measurements, run [run-comparisons.ps1](2026-09-07-structured-facts/run-comparisons.ps1) with preserved baseline/candidate binaries, the harness and a new output directory. The wrapper verifies both frozen fixture hashes. [summarize.py](2026-09-07-structured-facts/summarize.py) reads saved results without inference; the manifest records exact artifact hashes. diff --git a/docs/audits/2026-09-07-structured-facts/.gitattributes b/docs/audits/2026-09-07-structured-facts/.gitattributes new file mode 100644 index 0000000..eb7c3b9 --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/.gitattributes @@ -0,0 +1 @@ +* -text whitespace=cr-at-eol,-blank-at-eof diff --git a/docs/audits/2026-09-07-structured-facts/answerability-gold.json b/docs/audits/2026-09-07-structured-facts/answerability-gold.json new file mode 100644 index 0000000..d1f432e --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/answerability-gold.json @@ -0,0 +1,326 @@ +{ + "protocol": "Frozen before inference; hand-specified values for the frozen three-family fixture, not generated by the extractor under test. Exact source values are expected here; equivalent-unit behavior is covered by unit and delivery probes.", + "queries": [ + { + "scenario": "lumen-structured-scope", + "query": "What is the Lumen staging gateway port?", + "supported": { + "port": "7101" + }, + "conflicting": [] + }, + { + "scenario": "lumen-structured-scope", + "query": "What is the Lumen production gateway port?", + "supported": { + "port": "8101" + }, + "conflicting": [] + }, + { + "scenario": "lumen-structured-scope", + "query": "What are the Lumen staging gateway retries?", + "supported": { + "retries": "3" + }, + "conflicting": [] + }, + { + "scenario": "lumen-structured-scope", + "query": "What is `cache.max_entries` for Lumen?", + "supported": { + "cache.max_entries": "200" + }, + "conflicting": [] + }, + { + "scenario": "lumen-structured-scope", + "query": "Which database stores local state for Lumen?", + "supported": null, + "conflicting": [] + }, + { + "scenario": "lumen-structured-scope", + "query": "What are the Lumen staging gateway port and password?", + "supported": { + "port": "7101" + }, + "conflicting": [] + }, + { + "scenario": "lumen-structured-scope", + "query": "What are the Lumen staging gateway port and retries?", + "supported": { + "port": "7101", + "retries": "3" + }, + "conflicting": [] + }, + { + "scenario": "lumen-structured-scope", + "query": "What is the Lumen staging gateway timeout?", + "supported": {}, + "conflicting": [ + "timeout" + ] + }, + { + "scenario": "lumen-structured-scope", + "query": "What password does the Lumen production gateway require?", + "supported": { + "password": "not required" + }, + "conflicting": [] + }, + { + "scenario": "lumen-structured-scope", + "query": "What is the Lumen staging database port?", + "supported": {}, + "conflicting": [] + }, + { + "scenario": "lumen-structured-scope", + "query": "What is the Lumen production gateway timeout?", + "supported": {}, + "conflicting": [] + }, + { + "scenario": "lumen-structured-scope", + "query": "What is the Unknown staging gateway port?", + "supported": {}, + "conflicting": [] + }, + { + "scenario": "lumen-structured-scope", + "query": "What is the Lumen test gateway port?", + "supported": {}, + "conflicting": [] + }, + { + "scenario": "lumen-structured-scope", + "query": "What are the Lumen production gateway retries?", + "supported": {}, + "conflicting": [] + }, + { + "scenario": "lumen-structured-scope", + "query": "What is the Lumen staging worker timeout?", + "supported": {}, + "conflicting": [] + }, + { + "scenario": "harbor-structured-scope", + "query": "What is the Harbor staging gateway port?", + "supported": { + "port": "7102" + }, + "conflicting": [] + }, + { + "scenario": "harbor-structured-scope", + "query": "What is the Harbor production gateway port?", + "supported": { + "port": "8102" + }, + "conflicting": [] + }, + { + "scenario": "harbor-structured-scope", + "query": "What are the Harbor staging gateway retries?", + "supported": { + "retries": "3" + }, + "conflicting": [] + }, + { + "scenario": "harbor-structured-scope", + "query": "What is `cache.max_entries` for Harbor?", + "supported": { + "cache.max_entries": "200" + }, + "conflicting": [] + }, + { + "scenario": "harbor-structured-scope", + "query": "Which database stores local state for Harbor?", + "supported": null, + "conflicting": [] + }, + { + "scenario": "harbor-structured-scope", + "query": "What are the Harbor staging gateway port and password?", + "supported": { + "port": "7102" + }, + "conflicting": [] + }, + { + "scenario": "harbor-structured-scope", + "query": "What are the Harbor staging gateway port and retries?", + "supported": { + "port": "7102", + "retries": "3" + }, + "conflicting": [] + }, + { + "scenario": "harbor-structured-scope", + "query": "What is the Harbor staging gateway timeout?", + "supported": {}, + "conflicting": [ + "timeout" + ] + }, + { + "scenario": "harbor-structured-scope", + "query": "What password does the Harbor production gateway require?", + "supported": { + "password": "not required" + }, + "conflicting": [] + }, + { + "scenario": "harbor-structured-scope", + "query": "What is the Harbor staging database port?", + "supported": {}, + "conflicting": [] + }, + { + "scenario": "harbor-structured-scope", + "query": "What is the Harbor production gateway timeout?", + "supported": {}, + "conflicting": [] + }, + { + "scenario": "harbor-structured-scope", + "query": "What is the Unknown staging gateway port?", + "supported": {}, + "conflicting": [] + }, + { + "scenario": "harbor-structured-scope", + "query": "What is the Harbor test gateway port?", + "supported": {}, + "conflicting": [] + }, + { + "scenario": "harbor-structured-scope", + "query": "What are the Harbor production gateway retries?", + "supported": {}, + "conflicting": [] + }, + { + "scenario": "harbor-structured-scope", + "query": "What is the Harbor staging worker timeout?", + "supported": {}, + "conflicting": [] + }, + { + "scenario": "sable-structured-scope", + "query": "What is the Sable staging gateway port?", + "supported": { + "port": "7103" + }, + "conflicting": [] + }, + { + "scenario": "sable-structured-scope", + "query": "What is the Sable production gateway port?", + "supported": { + "port": "8103" + }, + "conflicting": [] + }, + { + "scenario": "sable-structured-scope", + "query": "What are the Sable staging gateway retries?", + "supported": { + "retries": "3" + }, + "conflicting": [] + }, + { + "scenario": "sable-structured-scope", + "query": "What is `cache.max_entries` for Sable?", + "supported": { + "cache.max_entries": "200" + }, + "conflicting": [] + }, + { + "scenario": "sable-structured-scope", + "query": "Which database stores local state for Sable?", + "supported": null, + "conflicting": [] + }, + { + "scenario": "sable-structured-scope", + "query": "What are the Sable staging gateway port and password?", + "supported": { + "port": "7103" + }, + "conflicting": [] + }, + { + "scenario": "sable-structured-scope", + "query": "What are the Sable staging gateway port and retries?", + "supported": { + "port": "7103", + "retries": "3" + }, + "conflicting": [] + }, + { + "scenario": "sable-structured-scope", + "query": "What is the Sable staging gateway timeout?", + "supported": {}, + "conflicting": [ + "timeout" + ] + }, + { + "scenario": "sable-structured-scope", + "query": "What password does the Sable production gateway require?", + "supported": { + "password": "not required" + }, + "conflicting": [] + }, + { + "scenario": "sable-structured-scope", + "query": "What is the Sable staging database port?", + "supported": {}, + "conflicting": [] + }, + { + "scenario": "sable-structured-scope", + "query": "What is the Sable production gateway timeout?", + "supported": {}, + "conflicting": [] + }, + { + "scenario": "sable-structured-scope", + "query": "What is the Unknown staging gateway port?", + "supported": {}, + "conflicting": [] + }, + { + "scenario": "sable-structured-scope", + "query": "What is the Sable test gateway port?", + "supported": {}, + "conflicting": [] + }, + { + "scenario": "sable-structured-scope", + "query": "What are the Sable production gateway retries?", + "supported": {}, + "conflicting": [] + }, + { + "scenario": "sable-structured-scope", + "query": "What is the Sable staging worker timeout?", + "supported": {}, + "conflicting": [] + } + ] +} diff --git a/docs/audits/2026-09-07-structured-facts/check-delivery.py b/docs/audits/2026-09-07-structured-facts/check-delivery.py new file mode 100644 index 0000000..bf194a5 --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/check-delivery.py @@ -0,0 +1,70 @@ +"""Isolated real CLI/MCP probes, using Noop embeddings and no model calls.""" +import json +import os +from pathlib import Path +import subprocess +import sys +import tempfile + +binary = str(Path(sys.argv[1]).resolve()) +env = dict(os.environ, KIMETSU_USER_BRAIN="0", KIMETSU_BRAIN_EMBEDDER="noop", + KIMETSU_EMBED_DAEMON="0", KIMETSU_TIER="free", + KIMETSU_MCP_ENABLE_WRITE_TOOLS="1", + KIMETSU_DETECT_CONFLICTS="0", KIMETSU_RESOLVE_CONFLICTS="0") +cases = [ + ("agent-mcp-record", ["Orchid staging gateway port is 7319."], + "What are the Orchid staging gateway port and timeout?", "partial", "timeout", 4), + ("partial", ["Orchid staging gateway port is 7319."], + "What are the Orchid staging gateway port and timeout?", "partial", "timeout", 4), + ("conflict-cap", ["Orchid staging gateway port is 7319. Operators record the listener settings.", + "Orchid staging gateway port is 8420. The deployment checklist records a different current value."], + "What is the Orchid staging gateway port?", "conflicting", "port", 1), + ("equivalent-units", ["Orchid staging gateway timeout is 30 seconds.", + "Orchid staging gateway timeout is 30000 ms."], + "What is the Orchid staging gateway timeout?", "supported", None, 4), + ("conflict-intermediate-budget", ["Orchid staging gateway port is 7319.", + "Orchid staging gateway port is 8420. " + "deployment-checklist " * 900], + "What is the Orchid staging gateway port?", "conflicting", "port", 4), + ("wrong-environment", ["Orchid production gateway port is 8420."], + "What is the Orchid staging gateway port?", "missing", "port", 4), +] +for name, memories, query, status, attribute, cap in cases: + with tempfile.TemporaryDirectory(prefix="structured-facts-") as folder: + def run(*args, input=None): + result = subprocess.run([binary, *args], cwd=folder, env=env, input=input, + text=True, encoding="utf-8", capture_output=True, timeout=90) + assert result.returncode == 0, result.stderr + return result.stdout + subprocess.run(["git", "init", "--quiet"], cwd=folder, check=True) + run("init") + for key, value in [("broker.explicit_fact_guard", "true"), ("broker.warm_start", "false"), + ("embedder.reranker", "off"), + ("broker.min_lexical_coverage", "0.0"), ("broker.abstain_min_score", "0.0")]: + run("config", "set", key, value) + for memory in memories: + if name != "agent-mcp-record": + run("brain", "memory", "add", "--scope", "project", "--kind", "fact", memory) + messages = [ + {"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2024-11-05", "capabilities": {}, "clientInfo": {"name": "fact-probe", "version": "1"}}}, + {"jsonrpc": "2.0", "method": "notifications/initialized"}, + {"jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {"name": "kimetsu_brain_context", "arguments": {"query": query, "budget_tokens": 6000, "include_ambient": False, "max_capsules": cap}}}, + ] + if name == "agent-mcp-record": + messages.insert(2, {"jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": {"name": "kimetsu_brain_record", "arguments": {"lesson": memories[0], "tags": ["network", "gateway"]}}}) + output = run("mcp", "serve", "--workspace", folder, input="".join(json.dumps(m)+"\n" for m in messages)) + response = next(json.loads(line) for line in output.splitlines() if json.loads(line).get("id") == 2) + assert "error" not in response, response + payload = json.loads(next(c["text"] for c in response["result"]["content"] if c["type"] == "text")) + assert payload["answerability"]["status"] == status, payload + if attribute: + field = "conflicting" if status == "conflicting" else "missing" + assert attribute in payload["answerability"][field], payload + assert len(payload["capsules"]) <= cap, payload + hook = run("brain", "context-hook", "--max-capsules", str(cap), "--min-score", "0.0", + input=json.dumps({"session_id": name, "prompt": query})) + if status in ("partial", "conflicting"): + expected = f"conflicting values for {attribute}" if status == "conflicting" else f"no supported value for {attribute}" + assert expected in hook, hook + if status == "missing": + assert not hook.strip(), hook + print(json.dumps({"probe": name, "answerability": payload["answerability"], "hook": hook.strip()})) diff --git a/docs/audits/2026-09-07-structured-facts/checks/conflict-carry-cli-green.log b/docs/audits/2026-09-07-structured-facts/checks/conflict-carry-cli-green.log new file mode 100644 index 0000000..5d74001 --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/checks/conflict-carry-cli-green.log @@ -0,0 +1,19 @@ + Compiling kimetsu-brain v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-brain) + Compiling kimetsu-agent v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-agent) + Compiling kimetsu-chat v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-chat) + Compiling kimetsu-cli v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-cli) +warning: linker stdout: Creando biblioteca E:\Kimetsu\target\debug\deps\kimetsu-aabe805e13f18efb.lib y objeto E:\Kimetsu\target\debug\deps\kimetsu-aabe805e13f18efb.exp + | + = note: `#[warn(linker_messages)]` on by default + +warning: `kimetsu-cli` (bin "kimetsu" test) generated 1 warning + Finished `test` profile [unoptimized + debuginfo] target(s) in 45.24s + Running unittests src\main.rs (E:/Kimetsu/target\debug\deps\kimetsu-aabe805e13f18efb.exe) + +running 3 tests +test embed_daemon::proto::legacy_conflict_wire_tests::legacy_response_without_conflicts_still_roundtrips_without_new_fields ... ok +test embed_daemon::proto::conflict_wire_tests::response_preserves_conflicts_after_capsules_have_been_trimmed ... ok +test commands::brain::conflict_carry_tests::daemon_conversion_keeps_conflict_notice_after_one_source_is_trimmed ... ok + +test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 281 filtered out; finished in 0.03s + diff --git a/docs/audits/2026-09-07-structured-facts/checks/conflict-carry-wire-red.log b/docs/audits/2026-09-07-structured-facts/checks/conflict-carry-wire-red.log new file mode 100644 index 0000000..f47b74c --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/checks/conflict-carry-wire-red.log @@ -0,0 +1,29 @@ + Compiling kimetsu-cli v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-cli) +warning: linker stdout: Creando biblioteca E:\Kimetsu\target\debug\deps\kimetsu-aabe805e13f18efb.lib y objeto E:\Kimetsu\target\debug\deps\kimetsu-aabe805e13f18efb.exp + | + = note: `#[warn(linker_messages)]` on by default + +warning: `kimetsu-cli` (bin "kimetsu" test) generated 1 warning + Finished `test` profile [unoptimized + debuginfo] target(s) in 21.04s + Running unittests src\main.rs (E:/Kimetsu/target\debug\deps\kimetsu-aabe805e13f18efb.exe) + +running 1 test +test embed_daemon::proto::conflict_wire_tests::response_preserves_conflicts_after_capsules_have_been_trimmed ... FAILED + +failures: + +---- embed_daemon::proto::conflict_wire_tests::response_preserves_conflicts_after_capsules_have_been_trimmed stdout ---- + +thread 'embed_daemon::proto::conflict_wire_tests::response_preserves_conflicts_after_capsules_have_been_trimmed' (5232) panicked at crates\kimetsu-cli\src\embed_daemon\proto.rs:186:9: +assertion `left == right` failed + left: Null + right: Array [String("port")] +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + + +failures: + embed_daemon::proto::conflict_wire_tests::response_preserves_conflicts_after_capsules_have_been_trimmed + +test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 281 filtered out; finished in 0.00s + +error: test failed, to rerun pass `-p kimetsu-cli --bin kimetsu` diff --git a/docs/audits/2026-09-07-structured-facts/checks/deferred-fact-budget-green.log b/docs/audits/2026-09-07-structured-facts/checks/deferred-fact-budget-green.log new file mode 100644 index 0000000..cc9d7c8 --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/checks/deferred-fact-budget-green.log @@ -0,0 +1,14 @@ + Compiling kimetsu-brain v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-brain) +warning: linker stdout: Creando biblioteca E:\Kimetsu\target\debug\deps\kimetsu_brain-4447584ffe405f3f.lib y objeto E:\Kimetsu\target\debug\deps\kimetsu_brain-4447584ffe405f3f.exp + | + = note: `#[warn(linker_messages)]` on by default + +warning: `kimetsu-brain` (lib test) generated 1 warning + Finished `test` profile [unoptimized + debuginfo] target(s) in 38.56s + Running unittests src\lib.rs (E:/Kimetsu/target\debug\deps\kimetsu_brain-4447584ffe405f3f.exe) + +running 1 test +test context::deferred_fact_budget_tests::initial_retrieval_budget_must_not_hide_an_eligible_conflicting_fact ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 774 filtered out; finished in 0.16s + diff --git a/docs/audits/2026-09-07-structured-facts/checks/deferred-fact-budget-red.log b/docs/audits/2026-09-07-structured-facts/checks/deferred-fact-budget-red.log new file mode 100644 index 0000000..37a6999 --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/checks/deferred-fact-budget-red.log @@ -0,0 +1,30 @@ + Compiling kimetsu-core v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-core) + Compiling kimetsu-brain v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-brain) +warning: linker stdout: Creando biblioteca E:\Kimetsu\target\debug\deps\kimetsu_brain-4447584ffe405f3f.lib y objeto E:\Kimetsu\target\debug\deps\kimetsu_brain-4447584ffe405f3f.exp + | + = note: `#[warn(linker_messages)]` on by default + +warning: `kimetsu-brain` (lib test) generated 1 warning + Finished `test` profile [unoptimized + debuginfo] target(s) in 1m 02s + Running unittests src\lib.rs (E:/Kimetsu/target\debug\deps\kimetsu_brain-4447584ffe405f3f.exe) + +running 1 test +test context::deferred_fact_budget_tests::initial_retrieval_budget_must_not_hide_an_eligible_conflicting_fact ... FAILED + +failures: + +---- context::deferred_fact_budget_tests::initial_retrieval_budget_must_not_hide_an_eligible_conflicting_fact stdout ---- + +thread 'context::deferred_fact_budget_tests::initial_retrieval_budget_must_not_hide_an_eligible_conflicting_fact' (22972) panicked at crates\kimetsu-brain\src\context.rs:7403:9: +assertion `left == right` failed: both eligible claims must reach arbitration before delivery budgeting + left: 1 + right: 2 +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + + +failures: + context::deferred_fact_budget_tests::initial_retrieval_budget_must_not_hide_an_eligible_conflicting_fact + +test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 774 filtered out; finished in 0.11s + +error: test failed, to rerun pass `-p kimetsu-brain --lib` diff --git a/docs/audits/2026-09-07-structured-facts/checks/deferred-fact-focused-green.log b/docs/audits/2026-09-07-structured-facts/checks/deferred-fact-focused-green.log new file mode 100644 index 0000000..c11f66e --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/checks/deferred-fact-focused-green.log @@ -0,0 +1,48 @@ +warning: linker stdout: Creando biblioteca E:\Kimetsu\target\debug\deps\kimetsu_brain-4447584ffe405f3f.lib y objeto E:\Kimetsu\target\debug\deps\kimetsu_brain-4447584ffe405f3f.exp + | + = note: `#[warn(linker_messages)]` on by default + +warning: `kimetsu-brain` (lib test) generated 1 warning + Finished `test` profile [unoptimized + debuginfo] target(s) in 0.72s + Running unittests src\lib.rs (E:/Kimetsu/target\debug\deps\kimetsu_brain-4447584ffe405f3f.exe) + +running 36 tests +test context::tests::weights_for_task_kind_refactor_up_scope_fraction ... ok +test fact_query::tests::broad_or_ambiguous_questions_keep_normal_retrieval ... ok +test fact_query::tests::parses_shared_subject_and_environment_for_compound_request ... ok +test fact_query::tests::parses_subject_after_attributes_and_spanish_aliases ... ok +test fact_query::tests::conflicting_values_are_reported_instead_of_selecting_one ... ok +test fact_query::tests::supports_partial_answers_without_borrowing_another_scope ... ok +test context::structured_fact_hydration_tests::legacy_wire_capsules_default_to_empty_fact_evidence ... ok +test fact_values::tests::binary_and_decimal_memory_units_are_distinct ... ok +test fact_values::tests::equivalent_durations_share_keys_without_rounding ... ok +test fact_values::tests::integer_attributes_normalize_only_valid_counts ... ok +test fact_values::tests::unsupported_values_and_sensitive_strings_remain_exact ... ok +test facts::tests::comma_numbers_cannot_be_truncated_into_facts ... ok +test facts::tests::comma_qualifiers_cannot_be_discarded ... ok +test fact_query::tests::only_visible_revision_bound_facts_support_an_answer ... ok +test facts::tests::output_and_input_are_bounded ... ok +test facts::tests::numeric_attributes_allow_bare_values_and_replica_counts ... ok +test facts::tests::provisional_headers_cannot_be_discarded_at_clause_boundaries ... ok +test facts::tests::clauses_do_not_inherit_scope ... ok +test facts::tests::canonical_scope_keeps_identity_and_rejects_mixed_environments ... ok +test facts::tests::rejects_relational_and_action_subjects ... ok +test facts::tests::scope_environment_and_exact_evidence ... ok +test facts::tests::rejects_negated_hypothetical_and_hidden_values ... ok +test facts::tests::recognizes_scoped_absence_and_attributes ... ok +test fact_query::tests::compression_preserves_each_supported_attribute ... ok +test serving::tests::explicit_fact_guard_excludes_topic_match_before_output_cap ... ok +test fact_query::tests::serving_metadata_tracks_the_final_budgeted_slice ... ok +test fact_query::tests::budget_trimming_does_not_hide_a_known_conflict ... ok +test fact_store::migration_tests::schema_fifteen_backfills_existing_text ... ok +test fact_store::transaction_tests::failed_event_batch_rolls_back_fact_projection ... ok +test context::disabled_fact_hydration_tests::ordinary_retrieval_does_not_read_the_fact_projection ... ok +test fact_store::load_tests::ingestion_and_legacy_backfill_do_not_project_secrets ... ok +test fact_store::load_tests::revisions_and_text_must_match_and_provenance_tracks_text_changes ... ok +test fact_store::tests::acceptance_projects_redacted_claim_and_correction_replaces_it_on_replay ... ok +test fact_store::load_tests::lifecycle_and_temporal_bounds_are_authoritative_at_read_time ... ok +test context::structured_fact_hydration_tests::lexical_and_recency_capsules_keep_their_delivered_fact_revision ... ok +test context::deferred_fact_budget_tests::initial_retrieval_budget_must_not_hide_an_eligible_conflicting_fact ... ok + +test result: ok. 36 passed; 0 failed; 0 ignored; 0 measured; 739 filtered out; finished in 0.20s + diff --git a/docs/audits/2026-09-07-structured-facts/checks/deferred-hook-cli-green.log b/docs/audits/2026-09-07-structured-facts/checks/deferred-hook-cli-green.log new file mode 100644 index 0000000..859f511 --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/checks/deferred-hook-cli-green.log @@ -0,0 +1,19 @@ + Compiling kimetsu-brain v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-brain) + Compiling kimetsu-agent v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-agent) + Compiling kimetsu-chat v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-chat) + Compiling kimetsu-cli v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-cli) +warning: linker stdout: Creando biblioteca E:\Kimetsu\target\debug\deps\kimetsu-aabe805e13f18efb.lib y objeto E:\Kimetsu\target\debug\deps\kimetsu-aabe805e13f18efb.exp + | + = note: `#[warn(linker_messages)]` on by default + +warning: `kimetsu-cli` (bin "kimetsu" test) generated 1 warning + Finished `test` profile [unoptimized + debuginfo] target(s) in 44.85s + Running unittests src\main.rs (E:/Kimetsu/target\debug\deps\kimetsu-aabe805e13f18efb.exe) + +running 3 tests +test embed_daemon::proto::legacy_conflict_wire_tests::legacy_response_without_conflicts_still_roundtrips_without_new_fields ... ok +test embed_daemon::proto::conflict_wire_tests::response_preserves_conflicts_after_capsules_have_been_trimmed ... ok +test commands::brain::conflict_carry_tests::daemon_conversion_keeps_conflict_notice_after_one_source_is_trimmed ... ok + +test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 281 filtered out; finished in 0.04s + diff --git a/docs/audits/2026-09-07-structured-facts/checks/fact-benchmark-observation-red.log b/docs/audits/2026-09-07-structured-facts/checks/fact-benchmark-observation-red.log new file mode 100644 index 0000000..03b6704 --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/checks/fact-benchmark-observation-red.log @@ -0,0 +1,26 @@ + Compiling kimetsu-brain v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-brain) + Compiling kimetsu-agent v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-agent) + Compiling kimetsu-bench v0.2.0 (E:\tmp\kimetsu-brain-hardening\bench) + Finished `test` profile [unoptimized + debuginfo] target(s) in 39.08s + Running unittests src\main.rs (E:/Kimetsu/bench/target\debug\deps\kbench-a3892fa7ea342860.exe) + +running 1 test +test drivers::brainbench::tests::query_observation_retains_delivered_answerability ... FAILED + +failures: + +---- drivers::brainbench::tests::query_observation_retains_delivered_answerability stdout ---- + +thread 'drivers::brainbench::tests::query_observation_retains_delivered_answerability' (13516) panicked at src\drivers\brainbench.rs:4829:9: +assertion `left == right` failed + left: Null + right: Array [String("timeout")] +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + + +failures: + drivers::brainbench::tests::query_observation_retains_delivered_answerability + +test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 131 filtered out; finished in 0.00s + +error: test failed, to rerun pass `--bin kbench` diff --git a/docs/audits/2026-09-07-structured-facts/checks/structured-facts-agent-ingress-red.log b/docs/audits/2026-09-07-structured-facts/checks/structured-facts-agent-ingress-red.log new file mode 100644 index 0000000..ffa33f2 --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/checks/structured-facts-agent-ingress-red.log @@ -0,0 +1,5 @@ +Traceback (most recent call last): + File "E:\tmp\kimetsu-brain-hardening\docs\audits\2026-09-07-structured-facts\check-delivery.py", line 58, in + assert payload["answerability"]["status"] == status, payload + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +AssertionError: {'answerability': {'conflicting': [], 'environment': 'staging', 'missing': ['port', 'timeout'], 'status': 'missing', 'subject': 'orchid gateway', 'supported': []}, 'budget_tokens': 6000, 'capsule_count': 1, 'capsules': [{'expansion_handle': 'memory:01M1XZ10N6N08Q2VJP47SGSQPC', 'id': '01M1XZ10XJ418ZJ0X4TMDAGV1C', 'kind': 'memory', 'score': 0.9549999833106995, 'summary': 'project:fact - [tags: network gateway] Orchid staging gateway port is 7319.'}], 'excluded_count': 0, 'exposure_id': '01M1XZ10XH9N57GSTP6VKWRM17', 'ok': True, 'partial_evidence': True, 'skipped': False, 'token_accounting': 'utf8_byte_upper_bound', 'used_tokens': 702} diff --git a/docs/audits/2026-09-07-structured-facts/checks/structured-facts-benchmark-tests.log b/docs/audits/2026-09-07-structured-facts/checks/structured-facts-benchmark-tests.log new file mode 100644 index 0000000..9275721 --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/checks/structured-facts-benchmark-tests.log @@ -0,0 +1,142 @@ + Compiling kimetsu-brain v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-brain) + Compiling kimetsu-agent v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-agent) + Compiling kimetsu-bench v0.2.0 (E:\tmp\kimetsu-brain-hardening\bench) + Finished `test` profile [unoptimized + debuginfo] target(s) in 27.39s + Running unittests src\main.rs (E:/Kimetsu/bench/target\debug\deps\kbench-a3892fa7ea342860.exe) + +running 132 tests +test drivers::brain_mcp::tests::windows_process_memory_reports_live_working_set_and_peak ... ok +test drivers::brainbench::tests::ci95_half_width_undefined_for_small_n ... ok +test drivers::beam::tests::synthetic_fixture_is_well_formed ... ok +test drivers::brainbench::tests::ci95_half_width_zero_for_constant_scores ... ok +test drivers::brainbench::tests::ci95_half_width_shrinks_with_n ... ok +test drivers::brain_mcp::tests::malformed_or_failed_protocol_output_is_not_abstention ... ok +test drivers::brain_mcp::tests::measures_serialized_result_and_utf8_text_without_losing_escapes ... ok +test drivers::brainbench::tests::dimension_from_str ... ok +test drivers::brainbench::tests::expand_eval_fixtures_empty_refs_is_empty ... ok +test drivers::brainbench::tests::dated_compressed_capsule_matches_visible_fixture_text ... ok +test drivers::brainbench::tests::build_report_excludes_skipped_from_index ... ok +test drivers::brainbench::tests::filter_by_dimension ... ok +test drivers::brainbench::tests::filter_by_limit ... ok +test drivers::brainbench::tests::filter_by_tier ... ok +test drivers::brainbench::tests::filter_empty_returns_all ... ok +test drivers::brainbench::tests::forget_f1_partial_wrong_and_overproposed ... ok +test drivers::brainbench::tests::forget_f1_perfect_and_empty ... ok +test drivers::brainbench::tests::irrelevant_memory_is_not_perfect_no_answer_retrieval ... ok +test drivers::brainbench::tests::malformed_capsule_summary_is_an_error ... ok +test drivers::brainbench::tests::model_override_survives_new_project_retrieval_preset ... ok +test drivers::brainbench::tests::mrr_first_position ... ok +test drivers::brainbench::tests::mrr_no_relevant_is_zero ... ok +test drivers::brainbench::tests::mrr_second_position ... ok +test drivers::brainbench::tests::normalize_collapses_whitespace_and_case ... ok +test drivers::brainbench::tests::pairwise_missing_keys_are_skipped ... ok +test drivers::brainbench::tests::pairwise_one_swapped_pair_is_two_thirds ... ok +test drivers::brainbench::tests::pairwise_perfect_order_is_one ... ok +test drivers::brainbench::tests::pairwise_reversed_order_is_zero ... ok +test drivers::brainbench::tests::pairwise_tie_is_half ... ok +test drivers::brainbench::tests::query_observation_retains_delivered_answerability ... ok +test drivers::brainbench::tests::recall_empty_relevant_is_one ... ok +test drivers::brainbench::tests::recall_full_and_partial ... ok +test drivers::brainbench::tests::recall_zero_k_or_empty_ranked_is_zero ... ok +test drivers::brainbench::tests::expand_calibration_gen_rejects_tiny_pool ... ok +test drivers::brainbench::tests::resolution_relevant_absent_is_false ... ok +test drivers::brainbench::tests::report_does_not_let_calibration_volume_hide_failed_retrieval ... ok +test drivers::brainbench::tests::resolution_relevant_outranks_stale ... ok +test drivers::brainbench::tests::resolution_stale_absent_is_true ... ok +test drivers::brainbench::tests::retrieval_measurements_disable_unscored_warm_start_for_every_model ... ok +test drivers::brainbench::tests::config_base_strips_storage_table_for_clean_backend_switch ... ok +test drivers::brainbench::tests::load_dataset_roundtrips_synthetic_fixture ... ok +test drivers::brainbench::tests::retrieval_rejects_stale_context_even_below_correct_answer ... ok +test drivers::brainbench::tests::score_dedup_balances_precision_and_recall ... ok +test drivers::brainbench::tests::score_dedup_detects_true_positive ... ok +test drivers::brainbench::tests::score_dedup_no_groups_falls_back_to_zero ... ok +test drivers::brainbench::tests::expand_eval_fixtures_one_scenario_per_kind ... ok +test drivers::brainbench::tests::score_dedup_perfect_precision_no_false_positive ... ok +test drivers::brainbench::tests::stale_empty_is_zero ... ok +test drivers::brainbench::tests::expand_calibration_gen_is_deterministic_and_well_formed ... ok +test drivers::brainbench::tests::stale_hit_within_and_beyond_k ... ok +test drivers::brainbench::tests::strip_prefix_summary_strips_scope_kind ... ok +test drivers::brainbench::tests::tier_from_str ... ok +test drivers::brainbench::tests::unknown_capsules_keep_their_rank_and_count_as_injection ... ok +test drivers::brainbench::tests::workflow_abstention_episode ... ok +test drivers::brainbench::tests::workflow_gold_episode_scores_recall ... ok +test drivers::brainbench::tests::workflow_learning_curve_halves ... ok +test drivers::brainbench::tests::workflow_stale_gates_gold_score ... ok +test drivers::brainbench::tests::workflow_spec_parses_from_json ... ok +test drivers::brainbench::tests::workflow_trap_gate ... ok +test drivers::brainbench::tests::write_precision_all_captured_all_on_target ... ok +test drivers::brainbench::tests::write_precision_empty_distilled_precision_one ... ok +test drivers::brainbench::tests::expand_workflow_gen_background_grows_the_haystack ... ok +test drivers::brainbench::tests::write_precision_empty_gold_recall_one ... ok +test drivers::beam::tests::load_dataset_accepts_envelope_and_bare_array ... ok +test drivers::brainbench::tests::write_precision_offtarget_lesson_lowers_precision ... ok +test drivers::brainbench::tests::write_precision_partial_recall ... ok +test drivers::locomo::tests::category_names_cover_paper_taxonomy ... ok +test drivers::locomo::tests::render_markdown_reports_non_adversarial_slice ... ok +test drivers::longmemeval::tests::build_report_computes_accuracy_correctly ... ok +test drivers::longmemeval::tests::codex_argv_construction ... ok +test drivers::longmemeval::tests::codex_argv_no_model_omits_m_flag ... ok +test drivers::longmemeval::tests::codex_backend_validate_config_ok ... ok +test drivers::brainbench::tests::expand_workflow_gen_is_deterministic_and_well_formed ... ok +test drivers::longmemeval::tests::codex_judge_prompt_abstention_note ... ok +test drivers::longmemeval::tests::codex_judge_prompt_correct_incorrect_instruction ... ok +test drivers::longmemeval::tests::codex_judge_prompt_preference_rubric ... ok +test drivers::longmemeval::tests::codex_reader_prompt_contains_key_instructions ... ok +test drivers::longmemeval::tests::codex_reader_prompt_includes_date_when_present ... ok +test drivers::longmemeval::tests::filter_by_limit ... ok +test drivers::longmemeval::tests::filter_by_question_type ... ok +test drivers::longmemeval::tests::filter_limit_stratifies_across_types ... ok +test drivers::longmemeval::tests::filter_zero_limit_returns_all ... ok +test drivers::longmemeval::tests::heuristic_judge_abstention_correct ... ok +test drivers::longmemeval::tests::heuristic_judge_abstention_incorrect ... ok +test drivers::longmemeval::tests::heuristic_judge_mismatch ... ok +test drivers::longmemeval::tests::heuristic_judge_substring_match ... ok +test drivers::longmemeval::tests::ingest_plan_no_dates_for_single_session ... ok +test drivers::longmemeval::tests::ingest_plan_uses_dates_for_temporal_types ... ok +test drivers::longmemeval::tests::is_abstention_detects_abs_suffix ... ok +test drivers::longmemeval::tests::is_abstention_false_for_normal_types ... ok +test drivers::longmemeval::tests::llm_backend_default_is_http ... ok +test drivers::longmemeval::tests::llm_backend_from_str_roundtrip ... ok +test drivers::longmemeval::tests::no_model_configured_error_message_is_actionable ... ok +test drivers::longmemeval::tests::parse_instance_missing_optional_fields ... ok +test drivers::longmemeval::tests::parse_instance_without_has_answer_defaults_to_false ... ok +test drivers::longmemeval::tests::parse_minimal_instance_from_json ... ok +test drivers::terminal_bench::tests::capture_wrapper_maps_every_agent_we_actually_run ... ok +test drivers::locomo::tests::limit_samples_round_robin_across_categories ... ok +test drivers::terminal_bench::tests::capture_wrapper_refuses_unknown_agent_rather_than_running_uncaptured ... ok +test drivers::longmemeval::tests::dry_run_smoke_test_with_codex_backend ... ok +test drivers::terminal_bench::tests::deepswe_named_metrics_are_scored_not_silently_zeroed ... ok +test drivers::locomo::tests::parses_sessions_turns_and_qa ... ok +test drivers::terminal_bench::tests::deepswe_zero_reward_stays_a_loss ... ok +test drivers::longmemeval::tests::dry_run_smoke_test_with_synthetic_fixture ... ok +test drivers::terminal_bench::tests::merge_mounts_combines_driver_and_extra_args ... ok +test drivers::terminal_bench::tests::merge_mounts_returns_none_when_nothing_to_mount ... ok +test drivers::terminal_bench::tests::merge_mounts_uses_only_extra_when_driver_none ... ok +test drivers::terminal_bench::tests::parse_cost_from_stdout_finds_embedded_cost_line ... ok +test drivers::terminal_bench::tests::parse_cost_from_stdout_returns_none_when_absent ... ok +test drivers::terminal_bench::tests::parse_harbor_result_averages_multiple_evals ... ok +test drivers::terminal_bench::tests::parse_harbor_result_errored_task_surfaces_exception_kinds ... ok +test drivers::terminal_bench::tests::parse_harbor_result_missing_stats_grades_zero ... ok +test drivers::terminal_bench::tests::parse_harbor_result_partial_credit_passes_through ... ok +test drivers::terminal_bench::tests::parse_harbor_result_pass_surfaces_clean_grade ... ok +test drivers::terminal_bench::tests::parse_harbor_result_surfaces_useful_error_on_garbage ... ok +test drivers::terminal_bench::tests::parse_list_tasks_accepts_id_alias ... ok +test drivers::terminal_bench::tests::parse_list_tasks_accepts_task_id_field ... ok +test drivers::terminal_bench::tests::parse_list_tasks_surfaces_useful_error_on_garbage ... ok +test drivers::terminal_bench::tests::tail_lossy_returns_whole_string_when_under_limit ... ok +test drivers::terminal_bench::tests::tail_lossy_trims_to_max_with_ellipsis ... ok +test drivers::terminal_bench::tests::terminal_bench_mean_still_takes_precedence ... ok +test drivers::terminal_bench::tests::parse_tasks_from_dataset_path_errors_on_non_directory ... ok +test setup::auth::tests::to_harbor_args_emits_ae_for_env_tokens ... ok +test setup::auth::tests::to_harbor_args_emits_mounts_for_codex_dir ... ok +test drivers::terminal_bench::tests::missing_or_empty_model_patch_is_distinguishable_from_a_real_patch ... ok +test setup::binary::tests::cached_returns_none_when_missing ... ok +test setup::auth::tests::dotenv_skips_comments_and_blank_lines ... ok +test setup::auth::tests::dotenv_returns_quoted_value_unquoted ... ok +test drivers::terminal_bench::tests::parse_tasks_from_dataset_path_returns_registry_prefixed_ids ... ok +test setup::binary::tests::cached_fresh_when_newer_than_sources ... ok +test setup::binary::tests::cached_stale_when_older_than_sources ... ok +test drivers::beam::tests::dry_run_counts_probes_without_calls ... ok + +test result: ok. 132 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.15s + diff --git a/docs/audits/2026-09-07-structured-facts/checks/structured-facts-delivery-probes.log b/docs/audits/2026-09-07-structured-facts/checks/structured-facts-delivery-probes.log new file mode 100644 index 0000000..450258d --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/checks/structured-facts-delivery-probes.log @@ -0,0 +1,6 @@ +{"probe": "agent-mcp-record", "answerability": {"conflicting": [], "environment": "staging", "missing": ["timeout"], "status": "partial", "subject": "orchid gateway", "supported": [{"attribute": "port", "sources": ["memory:01M1Y0460EV5EDKW83TPW0PYWR"], "value": "7319"}]}, "hook": "{\"continue\":true,\"hookSpecificOutput\":{\"additionalContext\":\"Recorded in this project's Kimetsu brain. These are prior conclusions, not observations of the current tree \u2014 where one conflicts with what you can check now, what you can check now wins:\\nRelevant project memory (not independently verified): [tags: network gateway] Orchid staging gateway port is 7319.\\nRetrieved fact evidence: no supported value for timeout.\\nPartial memory: nothing above covers timeout. Treat the rest as unknown rather than inferring it.\",\"hookEventName\":\"UserPromptSubmit\"}}"} +{"probe": "partial", "answerability": {"conflicting": [], "environment": "staging", "missing": ["timeout"], "status": "partial", "subject": "orchid gateway", "supported": [{"attribute": "port", "sources": ["memory:01M1Y049Q8ATYSETP2TFV9GWX3"], "value": "7319"}]}, "hook": "{\"continue\":true,\"hookSpecificOutput\":{\"additionalContext\":\"Recorded in this project's Kimetsu brain. These are prior conclusions, not observations of the current tree \u2014 where one conflicts with what you can check now, what you can check now wins:\\nRelevant project memory (not independently verified): Orchid staging gateway port is 7319.\\nRetrieved fact evidence: no supported value for timeout.\\nPartial memory: nothing above covers timeout. Treat the rest as unknown rather than inferring it.\",\"hookEventName\":\"UserPromptSubmit\"}}"} +{"probe": "conflict-cap", "answerability": {"conflicting": ["port"], "environment": "staging", "missing": [], "status": "conflicting", "subject": "orchid gateway", "supported": []}, "hook": "{\"continue\":true,\"hookSpecificOutput\":{\"additionalContext\":\"Recorded in this project's Kimetsu brain. These are prior conclusions, not observations of the current tree \u2014 where one conflicts with what you can check now, what you can check now wins:\\nRelevant project memory (not independently verified): Orchid staging gateway port is 7319. Operators record the listener settings.\\nRetrieved fact evidence: conflicting values for port.\",\"hookEventName\":\"UserPromptSubmit\"}}"} +{"probe": "equivalent-units", "answerability": {"conflicting": [], "environment": "staging", "missing": [], "status": "supported", "subject": "orchid gateway", "supported": [{"attribute": "timeout", "sources": ["memory:01M1Y04D6E38EZSCBK6DE8BKJS", "memory:01M1Y04DBKB2PYQTGSQDPVPKXF"], "value": "30 seconds"}]}, "hook": "{\"continue\":true,\"hookSpecificOutput\":{\"additionalContext\":\"Recorded in this project's Kimetsu brain. These are prior conclusions, not observations of the current tree \u2014 where one conflicts with what you can check now, what you can check now wins:\\nRelevant project memory (not independently verified): Orchid staging gateway timeout is 30 seconds.\\nOrchid staging gateway timeout is 30000 ms.\",\"hookEventName\":\"UserPromptSubmit\"}}"} +{"probe": "conflict-intermediate-budget", "answerability": {"conflicting": ["port"], "environment": "staging", "missing": [], "status": "conflicting", "subject": "orchid gateway", "supported": []}, "hook": "{\"continue\":true,\"hookSpecificOutput\":{\"additionalContext\":\"Recorded in this project's Kimetsu brain. These are prior conclusions, not observations of the current tree \u2014 where one conflicts with what you can check now, what you can check now wins:\\nRelevant project memory (not independently verified): Orchid staging gateway port is 7319.\\nRetrieved fact evidence: conflicting values for port.\",\"hookEventName\":\"UserPromptSubmit\"}}"} +{"probe": "wrong-environment", "answerability": {"conflicting": [], "environment": "staging", "missing": ["port"], "status": "missing", "subject": "orchid gateway", "supported": []}, "hook": ""} diff --git a/docs/audits/2026-09-07-structured-facts/checks/structured-facts-harness-build.log b/docs/audits/2026-09-07-structured-facts/checks/structured-facts-harness-build.log new file mode 100644 index 0000000..c7f1668 --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/checks/structured-facts-harness-build.log @@ -0,0 +1,5 @@ + Compiling kimetsu-core v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-core) + Compiling kimetsu-brain v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-brain) + Compiling kimetsu-agent v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-agent) + Compiling kimetsu-bench v0.2.0 (E:\tmp\kimetsu-brain-hardening\bench) + Finished `release` profile [optimized] target(s) in 2m 34s diff --git a/docs/audits/2026-09-07-structured-facts/checks/structured-facts-python-tests.log b/docs/audits/2026-09-07-structured-facts/checks/structured-facts-python-tests.log new file mode 100644 index 0000000..9bf3d31 --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/checks/structured-facts-python-tests.log @@ -0,0 +1,15 @@ +repeat 1/1: baseline +repeat 1/1: candidate +repeat 1/1: baseline +repeat 1/1: candidate +repeat 1/1: baseline +repeat 1/1: baseline +repeat 1/1: baseline +repeat 1/1: baseline +repeat 1/1: baseline +repeat 1/1: baseline +.................. +---------------------------------------------------------------------- +Ran 18 tests in 2.678s + +OK diff --git a/docs/audits/2026-09-07-structured-facts/checks/structured-facts-release.log b/docs/audits/2026-09-07-structured-facts/checks/structured-facts-release.log new file mode 100644 index 0000000..1100980 --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/checks/structured-facts-release.log @@ -0,0 +1,11 @@ + Compiling kimetsu-core v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-core) + Compiling kimetsu-brain v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-brain) + Compiling kimetsu-agent v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-agent) + Compiling kimetsu-chat v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-chat) + Compiling kimetsu-cli v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-cli) +warning: linker stdout: Creando biblioteca E:\Kimetsu\target\release\deps\kimetsu.lib y objeto E:\Kimetsu\target\release\deps\kimetsu.exp + | + = note: `#[warn(linker_messages)]` on by default + +warning: `kimetsu-cli` (bin "kimetsu") generated 1 warning + Finished `release` profile [optimized] target(s) in 4m 00s diff --git a/docs/audits/2026-09-07-structured-facts/checks/structured-facts-workspace.log b/docs/audits/2026-09-07-structured-facts/checks/structured-facts-workspace.log new file mode 100644 index 0000000..b600e83 --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/checks/structured-facts-workspace.log @@ -0,0 +1,1736 @@ + Compiling kimetsu-brain v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-brain) + Compiling kimetsu-agent v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-agent) + Compiling kimetsu-chat v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-chat) + Compiling kimetsu-e2e v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-e2e) + Compiling kimetsu-remote v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-remote) + Compiling kimetsu-cli v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-cli) +warning: linker stdout: Creando biblioteca E:\Kimetsu\target\debug\deps\kimetsu-1f656e727ac9da73.lib y objeto E:\Kimetsu\target\debug\deps\kimetsu-1f656e727ac9da73.exp + | + = note: `#[warn(linker_messages)]` on by default + +warning: `kimetsu-cli` (bin "kimetsu" test) generated 1 warning +warning: linker stdout: Creando biblioteca E:\Kimetsu\target\debug\deps\cli_smoke-47b17745ceeaf409.lib y objeto E:\Kimetsu\target\debug\deps\cli_smoke-47b17745ceeaf409.exp + | + = note: `#[warn(linker_messages)]` on by default + +warning: `kimetsu-cli` (test "cli_smoke") generated 1 warning +warning: linker stdout: Creando biblioteca E:\Kimetsu\target\debug\deps\kimetsu.lib y objeto E:\Kimetsu\target\debug\deps\kimetsu.exp + | + = note: `#[warn(linker_messages)]` on by default + +warning: `kimetsu-cli` (bin "kimetsu") generated 1 warning +warning: linker stdout: Creando biblioteca E:\Kimetsu\target\debug\deps\kimetsu_remote-ffd0f62781b49c2c.lib y objeto E:\Kimetsu\target\debug\deps\kimetsu_remote-ffd0f62781b49c2c.exp + | + = note: `#[warn(linker_messages)]` on by default + +warning: `kimetsu-remote` (bin "kimetsu-remote" test) generated 1 warning +warning: linker stdout: Creando biblioteca E:\Kimetsu\target\debug\deps\server_ingest-0c980c63b0afd619.lib y objeto E:\Kimetsu\target\debug\deps\server_ingest-0c980c63b0afd619.exp + | + = note: `#[warn(linker_messages)]` on by default + +warning: `kimetsu-remote` (test "server_ingest") generated 1 warning +warning: linker stdout: Creando biblioteca E:\Kimetsu\target\debug\deps\org_brain-b8014038469356e8.lib y objeto E:\Kimetsu\target\debug\deps\org_brain-b8014038469356e8.exp + | + = note: `#[warn(linker_messages)]` on by default + +warning: `kimetsu-remote` (test "org_brain") generated 1 warning +warning: linker stdout: Creando biblioteca E:\Kimetsu\target\debug\deps\kimetsu_remote.lib y objeto E:\Kimetsu\target\debug\deps\kimetsu_remote.exp + | + = note: `#[warn(linker_messages)]` on by default + +warning: `kimetsu-remote` (bin "kimetsu-remote") generated 1 warning +warning: linker stdout: Creando biblioteca E:\Kimetsu\target\debug\deps\http_roundtrip-bcd2e27d23bee19a.lib y objeto E:\Kimetsu\target\debug\deps\http_roundtrip-bcd2e27d23bee19a.exp + | + = note: `#[warn(linker_messages)]` on by default + +warning: `kimetsu-remote` (test "http_roundtrip") generated 1 warning +warning: linker stdout: Creando biblioteca E:\Kimetsu\target\debug\deps\citations-197c1a11846a1672.lib y objeto E:\Kimetsu\target\debug\deps\citations-197c1a11846a1672.exp + | + = note: `#[warn(linker_messages)]` on by default + +warning: `kimetsu-e2e` (test "citations") generated 1 warning +warning: linker stdout: Creando biblioteca E:\Kimetsu\target\debug\deps\pipeline_events_survive_rebuild-f75bc5ae05a30c91.lib y objeto E:\Kimetsu\target\debug\deps\pipeline_events_survive_rebuild-f75bc5ae05a30c91.exp + | + = note: `#[warn(linker_messages)]` on by default + +warning: `kimetsu-e2e` (test "pipeline_events_survive_rebuild") generated 1 warning +warning: linker stdout: Creando biblioteca E:\Kimetsu\target\debug\deps\decay-75a255e08a1fbe62.lib y objeto E:\Kimetsu\target\debug\deps\decay-75a255e08a1fbe62.exp + | + = note: `#[warn(linker_messages)]` on by default + +warning: `kimetsu-e2e` (test "decay") generated 1 warning +warning: linker stdout: Creando biblioteca E:\Kimetsu\target\debug\deps\migration-e3b6fec65a0b8141.lib y objeto E:\Kimetsu\target\debug\deps\migration-e3b6fec65a0b8141.exp + | + = note: `#[warn(linker_messages)]` on by default + +warning: `kimetsu-e2e` (test "migration") generated 1 warning +warning: linker stdout: Creando biblioteca E:\Kimetsu\target\debug\deps\golden_path-79592df7acb30cb7.lib y objeto E:\Kimetsu\target\debug\deps\golden_path-79592df7acb30cb7.exp + | + = note: `#[warn(linker_messages)]` on by default + +warning: `kimetsu-e2e` (test "golden_path") generated 1 warning +warning: linker stdout: Creando biblioteca E:\Kimetsu\target\debug\deps\insights-eb0536cc0d52b9fc.lib y objeto E:\Kimetsu\target\debug\deps\insights-eb0536cc0d52b9fc.exp + | + = note: `#[warn(linker_messages)]` on by default + +warning: `kimetsu-e2e` (test "insights") generated 1 warning +warning: linker stdout: Creando biblioteca E:\Kimetsu\target\debug\deps\conflicts-9fc2df8bd04ef049.lib y objeto E:\Kimetsu\target\debug\deps\conflicts-9fc2df8bd04ef049.exp + | + = note: `#[warn(linker_messages)]` on by default + +warning: `kimetsu-e2e` (test "conflicts") generated 1 warning +warning: linker stdout: Creando biblioteca E:\Kimetsu\target\debug\deps\kimetsu_remote-69c354a12d32479e.lib y objeto E:\Kimetsu\target\debug\deps\kimetsu_remote-69c354a12d32479e.exp + | + = note: `#[warn(linker_messages)]` on by default + +warning: `kimetsu-remote` (lib test) generated 1 warning +warning: linker stdout: Creando biblioteca E:\Kimetsu\target\debug\deps\kimetsu_brain-7daef08b9e22563e.lib y objeto E:\Kimetsu\target\debug\deps\kimetsu_brain-7daef08b9e22563e.exp + | + = note: `#[warn(linker_messages)]` on by default + +warning: `kimetsu-brain` (lib test) generated 1 warning +warning: linker stdout: Creando biblioteca E:\Kimetsu\target\debug\deps\kimetsu_chat-03711f11f092810b.lib y objeto E:\Kimetsu\target\debug\deps\kimetsu_chat-03711f11f092810b.exp + | + = note: `#[warn(linker_messages)]` on by default + +warning: `kimetsu-chat` (lib test) generated 1 warning +warning: linker stdout: Creando biblioteca E:\Kimetsu\target\debug\deps\kimetsu_e2e-84ccffbea5cf0b75.lib y objeto E:\Kimetsu\target\debug\deps\kimetsu_e2e-84ccffbea5cf0b75.exp + | + = note: `#[warn(linker_messages)]` on by default + +warning: `kimetsu-e2e` (lib test) generated 1 warning +warning: linker stdout: Creando biblioteca E:\Kimetsu\target\debug\deps\kimetsu_agent-a35151b1182f45c4.lib y objeto E:\Kimetsu\target\debug\deps\kimetsu_agent-a35151b1182f45c4.exp + | + = note: `#[warn(linker_messages)]` on by default + +warning: `kimetsu-agent` (lib test) generated 1 warning + Finished `test` profile [unoptimized + debuginfo] target(s) in 4m 12s + Running unittests src\lib.rs (E:/Kimetsu/target\debug\deps\kimetsu_agent-a35151b1182f45c4.exe) + +running 135 tests +test anthropic::tests::a_region_prefixed_frontier_id_still_omits_temperature ... ok +test anthropic::tests::an_unknown_model_omits_temperature ... ok +test agent_loop::tests::structured_json_parser_extracts_object_from_text ... ok +test anthropic::tests::messages_url_uses_base_when_set ... ok +test anthropic::tests::temperature_is_kept_on_models_that_accept_it ... ok +test anthropic::tests::temperature_is_omitted_on_models_that_reject_it ... ok +test bedrock::tests::bedrock_live_invoke ... ignored, requires real AWS credentials and Bedrock model access +test anthropic::tests::response_maps_text_tool_use_and_usage ... ok +test anthropic::tests::the_request_body_drops_temperature_for_a_frontier_model ... ok +test anthropic::tests::request_maps_system_and_tool_blocks_to_anthropic_shape ... ok +test bedrock::tests::bedrock_body_has_anthropic_version_and_no_model_key ... ok +test bedrock::tests::bedrock_body_includes_system_and_tools ... ok +test bedrock::tests::direct_anthropic_body_regression ... ok +test bedrock::tests::parse_bedrock_response_shape ... ok +test anthropic::tests::debug_format_does_not_leak_api_key ... ok +test anthropic::tests::for_distiller_builds_provider_with_base_url ... ok +test bench::tests::auto_accept_policy_matches_documented_thresholds ... ok +test bench::tests::auto_accept_shadowed_by_low_usefulness_memory_is_rejected ... ok +test claude_code::tests::fingerprint_distinguishes_system_prompt_and_model ... ok +test claude_code::tests::cache_stats_round_trip_through_parser ... ok +test claude_code::tests::cache_stats_default_to_zero_when_absent ... ok +test bedrock::tests::sigv4_with_session_token_adds_security_token_header ... ok +test bedrock::tests::sigv4_headers_contain_expected_structure ... ok +test claude_code::tests::fingerprint_is_collision_resistant_for_close_strings ... ok +test claude_code::tests::parses_finish_envelope_as_end_turn ... ok +test claude_code::tests::parses_success_json ... ok +test claude_code::tests::parses_tool_call_envelope_from_response_text ... ok +test claude_code::tests::renders_text_only_request ... ok +test bedrock::tests::from_config_returns_none_when_not_bedrock_provider ... ok +test claude_code::tests::renders_tool_call_message_as_text_for_history ... ok +test claude_code::tests::renders_tool_protocol_when_tools_present ... ok +test claude_code::tests::stream_json_parser_errors_on_empty_output ... ok +test claude_code::tests::stream_json_parser_errors_when_no_result_event ... ok +test claude_code::tests::stream_json_parser_falls_back_to_single_blob ... ok +test claude_code::tests::stream_json_parser_picks_last_result_when_multiple ... ok +test claude_code::tests::stream_json_parser_returns_last_result_event ... ok +test bedrock::tests::from_config_returns_none_when_access_key_missing ... ok +test claude_code::tests::stream_json_parser_skips_non_result_events ... ok +test claude_code::tests::stream_json_parser_tolerates_malformed_lines ... ok +test claude_code::tests::debug_format_does_not_leak_api_key ... ok +test harness::tests::check_workspace_path_rejects_escape_attempts ... ok +test harness::tests::codex_patch_translator_handles_add_file ... ok +test harness::tests::codex_patch_translator_handles_delete_file ... ok +test harness::tests::codex_patch_translator_handles_update_file ... ok +test harness::tests::codex_patch_translator_rejects_unmarked_input ... ok +test harness::tests::codex_patch_translator_synthesizes_hunk_when_missing ... ok +test harness::tests::diff_target_escapes_detects_traversal ... ok +test bedrock::tests::from_config_returns_none_when_region_missing ... ok +test bedrock::tests::from_config_builds_provider_when_all_present ... ok +test harness::tests::extract_between_pulls_section ... ok +test harness::tests::extract_diff_path_strips_ab_prefix ... ok +test harness::tests::extract_marker_finds_first_match ... ok +test harness::tests::is_useful_tool_excludes_deliberation_tools ... ok +test harness::tests::expand_capsule_appears_in_full_tool_loadout ... ok +test harness::tests::is_useful_tool_recognises_workspace_actions ... ok +test harness::tests::parse_bg_status_line_exited_state ... ok +test harness::tests::parse_bg_status_line_running_state ... ok +test harness::tests::parse_unified_diff_detects_delete_file ... ok +test harness::tests::parse_unified_diff_detects_new_file ... ok +test harness::tests::parse_unified_diff_multi_file ... ok +test harness::tests::parse_unified_diff_rejects_empty ... ok +test harness::tests::parse_unified_diff_rejects_missing_plus_header ... ok +test harness::tests::parse_unified_diff_single_file_single_hunk ... ok +test harness::tests::parse_wh_handles_well_formed_and_garbage ... ok +test harness::tests::plan_tool_rejects_invalid_status ... ok +test harness::tests::plan_tool_rejects_missing_fields ... ok +test harness::tests::plan_tool_validates_and_normalizes_todos ... ok +test harness::tests::file_tools_reject_workspace_escape ... ok +test harness::tests::record_deviation_accepts_complete_input ... ok +test harness::tests::record_deviation_rejects_empty_strings ... ok +test harness::tests::record_deviation_rejects_missing_fields ... ok +test harness::tests::render_verify_nudge_includes_task_recap ... ok +test harness::tests::render_verify_nudge_renders_plan_recap_when_present ... ok +test harness::tests::render_verify_nudge_second_iteration_is_firmer ... ok +test harness::tests::render_verify_nudge_strict_mode_requires_record_deviation ... ok +test harness::tests::strip_path_components_mimics_patch_p ... ok +test harness::tests::task_signals_verification_matches_specific_triggers ... ok +test harness::tests::task_signals_verification_skips_generic_language ... ok +test harness::tests::think_tool_acknowledges_and_reports_length ... ok +test harness::tests::validate_bg_handle_accepts_well_formed ... ok +test harness::tests::validate_bg_handle_rejects_garbage ... ok +test harness::tests::dynamic_loadout_starts_small_and_loads_profiles_on_request ... ok +test openai::tests::request_maps_system_and_user_to_responses_body ... ok +test harness::tests::expand_capsule_no_resolver_returns_not_configured_error ... ok +test openai::tests::response_maps_incomplete_max_tokens ... ok +test openai::tests::response_maps_output_text_and_usage ... ok +test harness::tests::expand_capsule_dispatches_via_resolver_and_emits_event ... ok +test openai::tests::response_maps_top_level_output_text ... ok +test openai::tests::responses_url_uses_base_when_set ... ok +test harness::tests::read_only_loadout_does_not_advertise_edit_or_shell_tools ... ok +test openai::tests::for_distiller_builds_provider_with_base_url ... ok +test openai::tests::debug_format_does_not_leak_api_key ... ok +test pipeline::tests::build_memory_proposal_request_includes_existing_memories ... ok +test harness::tests::expand_capsule_unknown_handle_returns_error_not_crash ... ok +test pipeline::tests::e1_no_match_returns_none ... ok +test pipeline::tests::e1_ledger_suppresses_pitfall_on_retry ... ok +test pipeline::tests::e1_pitfall_request_uses_correct_parameters ... ok +test pipeline::tests::e1_pitfall_surfaces_in_first_attempt ... ok +test pipeline::tests::e1_surfaced_and_injected_are_independent ... ok +test pipeline::tests::f1_back_ref_is_cheaper_than_full_render ... ok +test pipeline::tests::f1_dedup_across_two_renders_same_bundle ... ok +test pipeline::tests::f1_partial_overlap_second_bundle ... ok +test pipeline::tests::f2_already_injected_capsule_is_back_referenced_not_charged_again ... ok +test pipeline::tests::f2_headline_does_not_double_charge_after_tool_expansion ... ok +test pipeline::tests::f2_headline_tier_charges_small_cost ... ok +test pipeline::tests::f2_top_tier_full_rest_headline ... ok +test pipeline::tests::f3_estimate_task_tokens_matches_pipeline_heuristic ... ok +test pipeline::tests::f3_overhead_ratio_falls_on_larger_task ... ok +test pipeline::tests::f3_per_run_global_cap_limits_total_brain_tokens ... ok +test pipeline::tests::filter_memory_proposals_drops_duplicates_by_normalized_text ... ok +test pipeline::tests::filter_memory_proposals_drops_invalid_scopes_and_kinds ... ok +test pipeline::tests::filter_memory_proposals_drops_run_specific_text ... ok +test pipeline::tests::filter_memory_proposals_keeps_long_guidance_even_with_path_mention ... ok +test pipeline::tests::render_memory_proposal_section_renders_accepted_proposals ... ok +test pipeline::tests::render_retry_context_includes_command_and_stderr_excerpt ... ok +test pipeline::tests::verification_section_renders_pass_and_fail_results ... ok +test pipeline::tests::verification_section_reports_no_commands ... ok +test pipeline::tests::verification_section_skipped_in_dry_run ... ok +test recall_ledger::tests::fresh_ledger_is_empty ... ok +test recall_ledger::tests::mark_injected_tracks_membership_and_tokens ... ok +test recall_ledger::tests::reinjection_counts_tokens_once_keeping_original_charge ... ok +test recall_ledger::tests::surfaced_dedup_for_proactive_recall ... ok +test swe_bench::tests::formats_task_brief_with_hints ... ok +test swe_bench::tests::parses_minimal_swe_bench_task ... ok +test pipeline::tests::fingerprint_strips_ansi_escapes ... ok +test pipeline::tests::fingerprint_differs_for_different_errors ... ok +test pipeline::tests::fingerprint_is_stable_across_paths_and_line_numbers ... ok +test agent_loop::tests::loop_enforces_tool_budget ... ok +test tools::tests::apply_patch_rejects_symlink_targets ... ok +test agent_loop::tests::loop_executes_tool_calls_and_writes_model_artifacts ... ok +test agent_loop::tests::loop_applies_patch_under_active_plan ... ok +test tools::tests::shell_policy_blocks_network_and_allows_direct_programs ... ok +test tools::tests::file_tools_and_apply_patch_emit_trace_events ... ok +test pipeline::tests::dry_run_pipeline_writes_trace_patch_plan_and_report ... ok +test bench::tests::benchmark_reports_warm_memory_reuse has been running for over 60 seconds +test bench::tests::benchmark_reports_warm_memory_reuse ... ok + +test result: ok. 134 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 88.41s + + Running unittests src\lib.rs (E:/Kimetsu/target\debug\deps\kimetsu_brain-7daef08b9e22563e.exe) + +running 776 tests +test ambient::tests::render_includes_branch_only_when_present ... ok +test ambient::tests::parse_git_status_handles_typical_lines ... ok +test ambient::tests::parse_git_status_respects_limit ... ok +test ambient::tests::augment_query_appends_suffix_when_nonempty ... ok +test ambient::tests::render_omits_empty_fields ... ok +test ambient::tests::ambient_enabled_respects_env ... ok +test ambient::tests::w3_ambient_enabled_with_config_false_when_env_unset ... ok +test ambient::tests::render_normalizes_windows_path_separators ... ok +test ambient::tests::render_collapses_multiple_fields_with_separator ... ok +test ambient::tests::w3_ambient_env_disable_overrides_config_true ... ok +test ambient::tests::w3_ambient_env_enable_overrides_config_false ... ok +test ann::tests::add_is_upsert_and_remove_drops ... ok +test ann::tests::build_from_conn_indexes_all_active_rows ... ok +test ambient::tests::collect_recent_files_skips_dotkimetsu ... ok +test ann::tests::build_persists_sidecar ... ok +test ann::tests::concurrent_same_key_builds_once ... ok +test ann::tests::concurrent_saves_do_not_collide ... ok +test ann::tests::handle_for_query_reconciles_cached_index_on_new_rows ... ok +test ann::tests::invalidate_sidecar_removes_file_and_cache ... ok +test ann::tests::malformed_embedding_rows_do_not_keep_index_stale ... ok +test ann::tests::is_stale_detects_new_rows ... ok +test ann::tests::manifest_model_mismatch_forces_rebuild ... ok +test ann::tests::mixed_equal_count_sidecar_generations_are_rejected ... ok +test ann::tests::on_invalidate_removes_from_cached_index ... ok +test analytics::tests::citation_stats_rate_correct ... ok +test analytics::tests::corpus_health_counts_active_vs_invalidated ... ok +test analytics::tests::log_telemetry_event_writes_context_served_to_db ... ok +test ann::tests::reconcile_adds_new_and_removes_invalidated ... ok +test ann::tests::reconcile_advances_watermark_past_malformed_delta ... ok +test ann::tests::registry_caches_per_ondisk_db_and_transient_for_memory ... ok +test ann::tests::save_then_open_reuses_sidecar_and_search_matches ... ok +test analytics::tests::harvest_stats_by_source_and_yield ... ok +test ann::tests::warm_caches_handle ... ok +test ann::tests::warm_reader_refreshes_existing_vectors_from_another_connection ... ok +test answerability::tests::a_value_for_another_component_is_not_an_answer ... ok +test answerability::tests::broad_tasks_remain_outside_this_bounded_guard ... ok +test answerability::tests::explicit_absence_and_short_retention_are_useful_answers ... ok +test answerability::tests::explicit_values_survive_in_both_languages ... ok +test answerability::tests::mentions_and_unknown_values_are_not_answers ... ok +test answerability::tests::related_topics_do_not_supply_missing_configuration_values ... ok +test answerability::tests::review_competing_clause ... ok +test answerability::tests::review_negated_values ... ok +test answerability::tests::review_scope_regressions ... ok +test backend::tests::backend_for_all_known_variants_no_panic ... ok +test backend::tests::backend_for_flat_resolves ... ok +test backend::tests::backend_for_graph_lite_resolves_to_graph_lite_backend ... ok +test backend::tests::backend_for_graph_resolves_to_petgraph_backend ... ok +test backend::tests::graph_lite_1_hop_surfaces_edge_connected_memory ... ok +test backend::tests::graph_lite_is_a_superset_of_flat_with_edges_present ... ok +test backend::tests::graph_lite_no_edges_returns_flat_set ... ok +test backend::tests::graph_reached_candidates_are_hop_decayed_below_their_seed ... ok +test backend::tests::graph_rerank_retains_trust_and_decayed_usefulness ... ok +test backend::tests::hardening_graph_hydration_checks_future_and_offset_expiry ... ok +test backend::tests::petgraph_backend_from_conn_empty_db ... ok +test backend::tests::petgraph_backend_graph_algorithms_on_seeded_topology ... ok +test backend::tests::petgraph_backend_memory_candidates_superset_of_flat ... ok +test backend::tests::superseded_event_inserts_edge_and_edge_survives_rebuild ... ok +test backend_bench::tests::cross_backend_bench_backend_names ... ok +test backend_bench::tests::cross_backend_bench_runs_without_panic ... ok +test backend_bench::tests::format_results_markdown_includes_headers ... ok +test analytics::tests::proposal_stats_acceptance_rate_and_pending ... ok +test backend_bench::tests::graph_lite_candidate_count_gte_flat ... ok +test backend_bench::tests::graph_lite_recall_gte_flat ... ok +test backend_bench::tests::v25_decision_criterion_documents_verdict ... ok +test benchmark::tests::cold_brain_excludes_memory_capsules ... ok +test benchmark::tests::detects_known_and_suffix_task_slugs ... ok +test benchmark::tests::ignores_generic_terminal_bench_tokens ... ok +test benchmark::tests::outcome_memory_text_marks_episodic ... ok +test benchmark::tests::playbook_prioritizes_generalizable_memory_over_exact_episodic ... ok +test benchmark::tests::playbook_prioritizes_task_memory ... ok +test benchmark::tests::proposal_memory_text_marks_generalizable_and_review_pending ... ok +test benchmark::tests::required_mode_accepts_generalizable_memory_without_exact_slug ... ok +test bitemporal::tests::a_memory_written_later_is_not_in_the_past_view ... ok +test bitemporal::tests::a_retracted_memory_is_still_visible_before_its_retraction ... ok +test bitemporal::tests::a_superseded_memory_still_counts_as_a_past_belief ... ok +test bitemporal::tests::an_expired_memory_drops_out_after_its_valid_to ... ok +test bitemporal::tests::as_of_capsules_render_the_scope_and_kind_prefix ... ok +test bitemporal::tests::belief_delta_reports_what_was_learned_and_retired ... ok +test backend_bench::tests::petgraph_candidate_count_equals_graph_lite ... ok +test bitemporal::tests::the_limit_is_respected_and_zero_means_all ... ok +test bitemporal::tests::valid_time_is_independent_of_when_it_was_recorded ... ok +test analytics::tests::retrieval_stats_all_hits_skip_rate_zero ... ok +test conflict::tests::auto_resolution_stamps_loser_valid_to_when_new_wins ... ok +test conflict::tests::auto_resolution_stamps_new_memory_when_existing_wins ... ok +test conflict::tests::auto_resolution_survives_rebuild ... ok +test conflict::tests::cross_model_rows_are_skipped ... ok +test conflict::tests::detect_and_record_noop_writes_nothing ... ok +test conflict::tests::exact_match_is_not_flagged_as_conflict ... ok +test conflict::tests::exclude_id_prevents_self_conflict ... ok +test conflict::tests::high_similarity_and_score_gap_do_not_prove_contradiction ... ok +test conflict::tests::list_unresolved_excludes_resolved_rows ... ok +test conflict::tests::near_tie_goes_to_queue_not_auto_resolved ... ok +test conflict::tests::noop_embedder_returns_no_conflicts ... ok +test analytics::tests::retrieval_stats_counts_hits_and_misses ... ok +test conflict::tests::record_conflict_is_idempotent ... ok +test conflict::tests::resolution_score_higher_confidence_wins_all_else_equal ... ok +test conflict::tests::resolution_score_newer_wins_all_else_equal ... ok +test conflict::tests::resolve_conflict_invalidates_loser_side ... ok +test conflict::tests::resolve_conflict_is_idempotent ... ok +test conflict::tests::resolve_conflict_rejects_invalid_resolution_strings ... ok +test analytics::tests::retrieval_stats_no_context_served_events_returns_none ... ok +test conflict::tests::similar_but_different_text_is_flagged ... ok +test consolidate::tests::apply_merge_preserves_evidence_without_counting_copies_as_independent ... ok +test consolidate::tests::citations_reassigned_on_merge ... ok +test consolidate::tests::consolidation_is_rebuild_safe ... ok +test consolidate::tests::cosine_dim_mismatch_returns_zero ... ok +test consolidate::tests::cosine_empty_returns_zero ... ok +test consolidate::tests::cosine_opposite_is_minus_one ... ok +test consolidate::tests::cosine_orthogonal_is_zero ... ok +test consolidate::tests::cosine_same_vector_is_one ... ok +test consolidate::tests::find_distill_clusters_requires_shared_tags ... ok +test consolidate::tests::find_distill_clusters_shared_tag_and_band_clusters ... ok +test consolidate::tests::find_merge_clusters_different_models_do_not_cluster ... ok +test consolidate::tests::find_merge_clusters_identical_vectors_cluster ... ok +test consolidate::tests::find_merge_clusters_orthogonal_no_clusters ... ok +test consolidate::tests::merge_preserves_scope_kind_and_distinct_claims ... ok +test consolidate::tests::merge_similarity_chain_cannot_bridge_distant_members ... ok +test consolidate::tests::parse_tags_case_insensitive_key ... ok +test consolidate::tests::parse_tags_deduplicates ... ok +test consolidate::tests::parse_tags_extracts_tags ... ok +test consolidate::tests::parse_tags_no_block_returns_empty ... ok +test consolidate::tests::run_reflection_creates_proposal_from_cluster ... ok +test consolidate::tests::run_reflection_without_model_reports_only ... ok +test consolidate::tests::stale_merge_plan_does_not_retire_any_member ... ok +test consolidate::tests::superseded_row_excluded_from_latest_memory_candidates ... ok +test consolidate::tests::survivor_is_highest_usefulness_score ... ok +test consolidate::tests::v2_brain_migrates_to_v3_with_backup_and_superseded_by_column ... ok +test context::deferred_fact_budget_tests::initial_retrieval_budget_must_not_hide_an_eligible_conflicting_fact ... ok +test context::delivery::tests::final_serialization_bounds_unicode_identifiers_and_escaping ... ok +test context::delivery::tests::repeated_playbook_text_is_included_in_final_bound ... ok +test context::delivery::tests::tiny_budget_reports_actual_error_cost_and_no_exposure ... ok +test context::disabled_fact_hydration_tests::ordinary_retrieval_does_not_read_the_fact_projection ... ok +test context::evidence_tests::a_complete_bundle_gets_no_notice ... ok +test context::evidence_tests::a_partial_bundle_names_what_is_missing_and_tells_the_reader_what_to_do ... ok +test analytics::tests::superseded_memory_excluded_from_active_count ... ok +test context::evidence_tests::a_term_the_corpus_has_never_seen_counts_as_a_gap ... ok +test context::evidence_tests::a_vowel_y_is_not_stripped ... ok +test context::evidence_tests::an_empty_or_skipped_bundle_gets_no_notice ... ok +test context::evidence_tests::an_empty_query_does_not_claim_a_gap ... ok +test context::evidence_tests::a_ubiquitous_term_carries_no_weight ... ok +test context::evidence_tests::an_inflected_corpus_term_counts_as_covered ... ok +test context::evidence_tests::an_ordering_query_returns_a_dated_chronological_bundle ... ok +test context::evidence_tests::an_unmeasurable_query_does_not_claim_a_gap ... ok +test context::evidence_tests::an_ordinary_query_is_untouched ... ok +test context::evidence_tests::full_coverage_names_nothing ... ok +test context::evidence_tests::coverage_is_collective_not_per_capsule ... ok +test context::evidence_tests::partial_coverage_names_the_missing_terms ... ok +test context::evidence_tests::short_words_keep_their_ending ... ok +test context::evidence_tests::ordering_changes_the_rendering_not_the_selection ... ok +test context::evidence_tests::the_notice_caps_how_many_terms_it_names ... ok +test context::evidence_tests::the_original_suffix_rules_still_hold ... ok +test context::evidence_tests::the_y_ies_pair_shares_a_stem ... ok +test context::evidence_tests::the_dates_are_counted_against_the_budget ... ok +test context::hardening_tests::hardening_ann_hydration_filters_time_bounds ... ok +test context::hardening_tests::hardening_hydration_binds_text_revision_before_later_correction ... ok +test context::hardening_tests::hardening_idf_counts_prefix_documents_not_occurrences_or_substrings ... ok +test context::structured_fact_hydration_tests::legacy_wire_capsules_default_to_empty_fact_evidence ... ok +test context::hardening_tests::hardening_live_lexical_and_recency_apply_both_time_bounds ... ok +test context::structured_fact_hydration_tests::lexical_and_recency_capsules_keep_their_delivered_fact_revision ... ok +test context::tests::aged_cited_memory_does_not_decay_when_half_life_is_zero ... ok +test context::tests::abstain_evidence_gate_skips_on_weak_absolute_evidence ... ok +test context::tests::aged_cited_memory_ranks_below_recently_cited_memory ... ok +test context::tests::band_arbitration_follows_the_cross_encoder ... ok +test context::tests::band_arbitration_never_converts_out_of_band_bundles ... ok +test context::tests::ann_finds_semantic_match_fts_misses ... ok +test context::tests::band_arbitration_uses_raw_evidence_before_policy_cap ... ok +test context::tests::band_arbitration_uses_raw_rerank_evidence ... ok +test context::tests::band_fails_closed_without_a_reranker ... ok +test context::tests::band_spares_bundles_with_repo_evidence ... ok +test context::tests::boost_gain_is_capped_so_cited_junk_cannot_beat_relevant_uncited ... ok +test context::tests::boost_still_reorders_within_a_relevance_band ... ok +test context::tests::capsule_matches_kind_reads_memory_summary_prefix ... ok +test context::tests::classify_task_maps_each_kind_deterministically ... ok +test context::tests::classify_task_respects_precedence_order ... ok +test context::tests::compress_for_render_caps_sentences ... ok +test context::tests::compress_for_render_empty_input_safe ... ok +test context::tests::compress_for_render_long_memory_reduces_tokens_by_25_percent ... ok +test context::tests::compress_for_render_preserves_scope_prefix ... ok +test context::tests::compress_for_render_short_text_unchanged ... ok +test context::tests::compress_for_render_strips_context_suffix ... ok +test context::tests::compress_for_render_strips_tags_prefix ... ok +test context::tests::compress_for_render_utf8_safe ... ok +test context::tests::compress_for_render_zero_max_sentences_returns_original ... ok +test context::tests::content_tokens_strips_stopwords_keeps_topical_words ... ok +test context::tests::debug_surfaces_more_failure_pattern_than_docs ... ok +test context::tests::d1f_token_economy_fewer_capsules_signal_preserved ... ok +test context::tests::dedup_memory_matched_by_fts_and_ann_appears_once ... ok +test context::tests::global_normalization_keeps_relevance_comparable_across_kinds ... ok +test context::tests::hardening_freshness_has_thirty_day_half_life ... ok +test context::tests::hardening_rerank_preserves_decayed_usefulness_and_trust ... ok +test context::tests::hardening_usefulness_cannot_dominate_relevance ... ok +test context::tests::hybrid_retrieval_skips_cosine_on_model_id_mismatch ... ok +test analytics::tests::token_economy_all_old_events_returns_none ... ok +test context::tests::embedding_mmr_collapses_paraphrases_but_jaccard_does_not ... ok +test context::tests::jaccard_is_one_for_identical_sets ... ok +test context::tests::hybrid_retrieval_uses_cosine_score_to_rerank ... ok +test context::tests::jaccard_is_zero_for_disjoint_sets ... ok +test context::tests::jaccard_partial_overlap ... ok +test context::tests::hybrid_retrieval_with_noop_embedder_is_lexical_only ... ok +test context::tests::lean_noop_embedder_uses_fts_then_recency_unchanged ... ok +test context::tests::light_stem_strips_one_inflection_suffix ... ok +test context::tests::lexical_floor_drops_offtopic_memories_sharing_project_name ... ok +test context::tests::lexical_floor_keeps_ontopic_memory ... ok +test context::tests::penalty_side_remains_multiplicative ... ok +test context::tests::per_kind_normalization_flatters_the_best_of_a_weak_kind ... ok +test context::tests::query_tokens_expands_build_class ... ok +test context::tests::query_tokens_expands_edit_class ... ok +test context::tests::query_tokens_expands_search_class ... ok +test context::tests::query_tokens_no_expansion_on_unrelated_query ... ok +test context::tests::rerank_capsules_cap_truncates ... ok +test context::tests::rerank_capsules_empty_input_returns_empty ... ok +test context::tests::rerank_capsules_fail_open_preserves_input_order ... ok +test context::tests::rerank_capsules_floor_drops_zero_overlap ... ok +test context::tests::rerank_capsules_reorders_by_query_overlap ... ok +test context::tests::rerank_reapplies_usefulness_but_not_to_superseded_capsules ... ok +test context::tests::resolve_capsule_file_rejects_absolute_path ... ok +test context::tests::resolve_capsule_file_caps_large_file ... ok +test context::tests::resolve_capsule_malformed_handle_returns_err ... ok +test context::tests::resolve_capsule_file_returns_bounded_content ... ok +test context::tests::resolve_capsule_memory_missing_id_returns_err ... ok +test context::tests::resolve_capsule_run_handle_returns_deferred_err ... ok +test context::tests::resolve_capsule_unknown_handle_returns_err ... ok +test context::tests::resolve_capsule_memory_returns_full_text ... ok +test context::tests::summary_token_set_lowercases_and_filters_short ... ok +test context::tests::supersession_ignores_distinct_memories_and_applies_once ... ok +test context::tests::supersession_is_inert_without_embeddings_or_timestamps ... ok +test context::tests::supersession_penalizes_the_older_near_duplicate ... ok +test context::tests::supersession_penalty_survives_reranking ... ok +test context::tests::stemmed_query_matches_inflected_corpus_through_floor ... ok +test context::tests::unknown_normalization_falls_back_to_per_kind ... ok +test context::tests::usefulness_decay_disabled_when_half_life_is_zero_or_negative ... ok +test context::tests::usefulness_decay_falls_back_to_created_at_when_last_useful_is_none ... ok +test context::tests::usefulness_decay_follows_half_life_curve ... ok +test context::tests::usefulness_decay_full_at_zero_age ... ok +test context::tests::usefulness_decay_returns_one_on_unparseable_timestamps ... ok +test context::tests::usefulness_multiplier_blends_smoothly_in_transition ... ok +test context::tests::usefulness_multiplier_clamps_to_envelope ... ok +test context::tests::usefulness_multiplier_maps_ratio_onto_envelope ... ok +test context::tests::usefulness_multiplier_neutral_at_zero_uses ... ok +test context::tests::weighted_coverage_ignores_zero_idf_tokens ... ok +test context::tests::weights_for_task_kind_debug_up_freshness_fraction ... ok +test context::tests::weights_for_task_kind_feature_is_unchanged ... ok +test context::tests::weights_for_task_kind_refactor_up_scope_fraction ... ok +test context::tests::weights_for_task_kind_renormalizes_to_unit_sum ... ok +test context::tests::task_kind_feature_is_retrieval_neutral ... ok +test digest::tests::digest_size_within_400_token_budget ... ok +test context::tests::min_semantic_score_floor_drops_off_topic_queries ... ok +test analytics::tests::token_economy_averages_new_events_and_tolerates_old_events ... ok +test analytics::tests::usefulness_trend_gate_failure_excluded_from_window_net ... ok +test ann::tests::default_quant_is_f16 ... ok +test ann::tests::i8_quant_builds_and_searches ... ok +test ann::tests::manifest_quant_mismatch_forces_rebuild ... ok +test ann::tests::recall_at_10_is_at_least_0_95_vs_brute_force ... ok +test ann::tests::recall_guard_holds_under_f16 ... ok +test ann::tests::search_returns_nearest_rowid_first ... ok +test conflict::tests::conflict_detection_enabled_config_false_when_env_unset ... ok +test conflict::tests::conflict_detection_enabled_env_disable_overrides_config_true ... ok +test drift::tests::a_return_to_topic_resets_the_run ... ok +test drift::tests::a_session_that_holds_its_topic_does_not_drift ... ok +test conflict::tests::off_switch_prevents_conflict_detection ... ok +test conflict::tests::resolve_conflicts_enabled_env_disable_overrides_config_true ... ok +test drift::tests::a_slow_walk_away_from_the_opening_turn_is_still_drift ... ok +test drift::tests::a_sustained_run_marks_where_the_session_turned ... ok +test drift::tests::an_empty_session_reports_nothing ... ok +test drift::tests::one_off_anchor_turn_is_not_drift ... ok +test drift::tests::the_anchor_turn_is_never_the_drift_point ... ok +test digest::tests::record_warmstart_served_is_best_effort ... ok +test drift::tests::a_single_turn_session_is_not_reported ... ok +test dropped_capsule::tests::match_and_remove_finds_and_removes ... ok +test dropped_capsule::tests::match_and_remove_on_empty_is_safe ... ok +test drift::tests::sessions_without_stored_queries_are_absent_not_clean ... ok +test dropped_capsule::tests::match_and_remove_returns_none_when_absent ... ok +test drift::tests::turns_without_a_session_id_are_skipped ... ok +test dropped_capsule::tests::prune_window_caps_at_max_entries ... ok +test drift::tests::turns_come_back_in_order_grouped_by_session ... ok +test dropped_capsule::tests::prune_window_empty_input_is_safe ... ok +test dropped_capsule::tests::prune_window_keeps_boundary_entry ... ok +test dropped_capsule::tests::prune_window_removes_old_entries ... ok +test embeddings::checked_serving_loader_tests::failed_requested_model_is_not_an_intentional_lexical_measurement ... ok +test embeddings::configured_reranker_tests::configured_cache_reuses_model_and_off_never_loads ... ok +test embeddings::explicit_embedder_tests::aliases_and_disable_have_one_effective_model_identity ... ok +test embeddings::tests::builtin_models_table_is_consistent ... ok +test embeddings::tests::cosine_similarity_handles_edge_cases ... ok +test embeddings::tests::cosine_similarity_is_symmetric ... ok +test embeddings::tests::decode_embedding_rejects_dim_mismatch ... ok +test embeddings::tests::decode_embedding_rejects_unaligned_blob ... ok +test embeddings::tests::embed_batch_empty_is_empty ... ok +test embeddings::tests::embed_batch_length_matches_input ... ok +test embeddings::tests::embed_batch_matches_per_row ... ok +test embeddings::tests::encode_decode_embedding_round_trip ... ok +test embeddings::tests::map_builtin_id_maps_aliases_and_defaults_unknown ... ok +test embeddings::tests::noop_embedder_returns_not_implemented_and_is_noop ... ok +test dropped_capsule::tests::save_is_atomic_no_tmp_leftover ... ok +test embeddings::tests::resolve_embedder_id_uses_config_when_env_unset ... ok +test embeddings::tests::runtime_threads_are_explicit_bounded_and_invalid_values_are_errors ... ok +test embeddings::tests::stub_embedder_distinguishes_disjoint_inputs ... ok +test embeddings::tests::stub_embedder_handles_empty_input ... ok +test embeddings::tests::stub_embedder_is_deterministic ... ok +test embeddings::tests::stub_reranker_empty_query_returns_floor ... ok +test embeddings::tests::stub_reranker_higher_overlap_scores_higher ... ok +test embeddings::tests::stub_reranker_model_id ... ok +test embeddings::tests::stub_reranker_returns_doc_order_scores ... ok +test embeddings::correction_race_tests::slow_embedding_cannot_overwrite_a_newer_correction ... ok +test digest::tests::cache_is_reused_on_second_call ... ok +test digest::tests::digest_with_memories_is_bounded ... ok +test digest::tests::empty_brain_returns_none ... ok +test episode::tests::episode_inserts_lesson_from_edges ... ok +test episode::tests::project_episode_round_trip ... ok +test episode::tests::rebuild_in_place_reprojects_episodes ... ok +test episode::tests::render_resume_context_formats_episode ... ok +test episode::tests::reset_projection_clears_episodes ... ok +test episode::tests::rule_based_episode_parses_transcript ... ok +test episode::tests::second_episode_supersedes_first ... ok +test eval::tests::delivered_metrics_separate_fraction_hit_and_known_negative_accuracy ... ok +test eval::tests::mean_all_ones ... ok +test eval::tests::mean_empty_is_zero ... ok +test eval::tests::mean_normal ... ok +test eval::tests::mean_single ... ok +test eval::tests::mrr_absent_is_zero ... ok +test eval::tests::mrr_empty_ranked_is_zero ... ok +test eval::tests::mrr_empty_relevant_is_zero ... ok +test eval::tests::mrr_first_position_is_one ... ok +test eval::tests::mrr_second_position_is_half ... ok +test eval::tests::mrr_third_position_is_one_third ... ok +test eval::tests::mrr_uses_first_hit_when_multiple_relevant ... ok +test eval::tests::recall_at_k_duplicates_in_ranked_count_once ... ok +test eval::tests::recall_at_k_exact_hits ... ok +test eval::tests::recall_at_k_k_larger_than_ranked_uses_full_list ... ok +test eval::tests::recall_at_k_negative_has_no_vacuous_quality_credit ... ok +test eval::tests::recall_at_k_no_hits_is_zero ... ok +test eval::tests::recall_at_k_zero_k_is_zero ... ok +test eval::tests::resolution_correct_empty_relevant_is_false ... ok +test eval::tests::resolution_correct_relevant_above_stale_is_true ... ok +test eval::tests::resolution_correct_relevant_absent_is_false ... ok +test eval::tests::resolution_correct_stale_above_relevant_is_false ... ok +test eval::tests::resolution_correct_stale_absent_is_true ... ok +test eval::tests::stale_hit_rate_no_stale_is_zero ... ok +test eval::tests::stale_hit_rate_stale_absent_is_zero ... ok +test eval::tests::stale_hit_rate_stale_beyond_k_is_zero ... ok +test eval::tests::stale_hit_rate_stale_in_top_k_is_one ... ok +test fact_query::tests::broad_or_ambiguous_questions_keep_normal_retrieval ... ok +test fact_query::tests::budget_trimming_does_not_hide_a_known_conflict ... ok +test fact_query::tests::compression_preserves_each_supported_attribute ... ok +test fact_query::tests::conflicting_values_are_reported_instead_of_selecting_one ... ok +test fact_query::tests::only_visible_revision_bound_facts_support_an_answer ... ok +test fact_query::tests::parses_shared_subject_and_environment_for_compound_request ... ok +test fact_query::tests::parses_subject_after_attributes_and_spanish_aliases ... ok +test fact_query::tests::serving_metadata_tracks_the_final_budgeted_slice ... ok +test fact_query::tests::supports_partial_answers_without_borrowing_another_scope ... ok +test fact_store::load_tests::ingestion_and_legacy_backfill_do_not_project_secrets ... ok +test fact_store::load_tests::lifecycle_and_temporal_bounds_are_authoritative_at_read_time ... ok +test fact_store::load_tests::revisions_and_text_must_match_and_provenance_tracks_text_changes ... ok +test fact_store::migration_tests::schema_fifteen_backfills_existing_text ... ok +test fact_store::tests::acceptance_projects_redacted_claim_and_correction_replaces_it_on_replay ... ok +test fact_store::transaction_tests::failed_event_batch_rolls_back_fact_projection ... ok +test fact_values::tests::binary_and_decimal_memory_units_are_distinct ... ok +test fact_values::tests::equivalent_durations_share_keys_without_rounding ... ok +test fact_values::tests::integer_attributes_normalize_only_valid_counts ... ok +test fact_values::tests::unsupported_values_and_sensitive_strings_remain_exact ... ok +test facts::tests::canonical_scope_keeps_identity_and_rejects_mixed_environments ... ok +test facts::tests::clauses_do_not_inherit_scope ... ok +test facts::tests::comma_numbers_cannot_be_truncated_into_facts ... ok +test facts::tests::comma_qualifiers_cannot_be_discarded ... ok +test facts::tests::numeric_attributes_allow_bare_values_and_replica_counts ... ok +test facts::tests::output_and_input_are_bounded ... ok +test facts::tests::provisional_headers_cannot_be_discarded_at_clause_boundaries ... ok +test facts::tests::recognizes_scoped_absence_and_attributes ... ok +test facts::tests::record_tag_metadata_does_not_become_fact_scope ... ok +test facts::tests::rejects_negated_hypothetical_and_hidden_values ... ok +test facts::tests::rejects_relational_and_action_subjects ... ok +test facts::tests::scope_environment_and_exact_evidence ... ok +test framing::tests::every_framing_states_which_side_wins_a_conflict ... ok +test framing::tests::the_framing_does_not_hedge_the_memory_itself ... ok +test framing::tests::the_proactive_framing_stays_short ... ok +test fusion::tests::fuse_dispatches_on_the_mode ... ok +test fusion::tests::fusion_parses_and_falls_back_to_linear ... ok +test fusion::tests::rrf_is_deterministic_on_ties ... ok +test fusion::tests::rrf_normalizes_the_top_score_to_one ... ok +test fusion::tests::rrf_over_one_list_preserves_its_order ... ok +test fusion::tests::rrf_rewards_agreement_between_lists ... ok +test fusion::tests::union_max_keeps_the_best_instance_of_each_candidate ... ok +test graph::tests::build_edges_excludes_superseded ... ok +test graph::tests::build_edges_links_shared_entity_and_skips_unrelated ... ok +test graph::tests::build_edges_persist_roundtrip ... ok +test graph::tests::entity_source_prefers_the_author_supplied_tag ... ok +test graph::tests::extract_entities_is_sorted_and_deduped ... ok +test graph::tests::extract_entities_picks_tags_and_salient_terms ... ok +test graph::tests::incremental_edges_link_a_new_memory_to_its_neighbours ... ok +test graph::tests::incremental_edges_need_more_than_one_shared_entity ... ok +test digest::tests::force_rebuild_bypasses_cache ... ok +test graph::tests::incremental_edges_respect_the_fan_out_cap ... ok +test graph::tests::incremental_edges_skip_inactive_neighbours ... ok +test graph::tests::project_entities_replaces_rather_than_accumulates ... ok +test graph::tests::reproject_all_entities_backfills_an_existing_corpus ... ok +test hardening_evidence_tests::hardening_archive_restore_replay_preserves_expiry_and_invalidity ... ok +test hardening_evidence_tests::hardening_concurrent_episode_lanes_replay ... ok +test hardening_evidence_tests::hardening_explicit_revision_cannot_override_actual_delivery ... ok +test digest::tests::hardening_warm_digest_excludes_invalid_time_and_other_task_focus ... ok +test hardening_evidence_tests::hardening_identity_uses_first_usable_task_session_or_worktree ... ok +test hardening_evidence_tests::hardening_manual_conflict_rejection_cannot_restore_archived_loser ... ok +test hardening_evidence_tests::hardening_manual_conflict_replay_and_atomic_validation ... ok +test hardening_evidence_tests::hardening_mixed_revision_run_is_not_current_claim_evidence ... ok +test hardening_evidence_tests::hardening_partial_episode_preserves_explicit_lane ... ok +test hardening_evidence_tests::hardening_roi_labels_assumptions_and_keeps_delivery_units_separate ... ok +test ingest::tests::effective_ingest_limits_clamp_hostile_project_config ... ok +test ingest::tests::index_file_redacts_secrets_in_snippet ... ok +test ingest::tests::read_file_capped_rejects_oversized_content ... ok +test inject_policy::tests::a_policy_round_trips_through_json ... ok +test inject_policy::tests::a_small_dataset_does_not_move_the_policy ... ok +test inject_policy::tests::a_wrong_shaped_policy_is_invalid ... ok +test inject_policy::tests::an_unexercised_surface_is_omitted_rather_than_scored_zero ... ok +test inject_policy::tests::feature_names_line_up_with_the_vector ... ok +test inject_policy::tests::history_from_before_surfaces_is_dropped_not_defaulted ... ok +test inject_policy::tests::only_citations_after_the_injection_count ... ok +test inject_policy::tests::sigmoid_is_stable_at_the_extremes ... ok +test inject_policy::tests::single_class_data_does_not_move_the_policy ... ok +test inject_policy::tests::suppressed_injections_do_not_count_against_a_surface ... ok +test inject_policy::tests::surface_strings_round_trip ... ok +test inject_policy::tests::surfaces_are_scored_separately ... ok +test inject_policy::tests::the_prior_ignores_every_untrained_feature ... ok +test inject_policy::tests::the_prior_reproduces_the_legacy_threshold ... ok +test inject_policy::tests::training_can_also_make_the_policy_speak_sooner ... ok +test inject_policy::tests::training_moves_the_boundary_towards_the_evidence ... ok +test digest::tests::hardening_warm_digest_revalidates_corrected_and_retired_claims ... ok +test digest::tests::hardening_warm_profile_honors_user_brain_opt_out ... ok +test ann::tests::parallel_build_indexes_all_rows ... ok +test lifecycle::tests::forgetting_uses_meaningful_recency_and_not_harmful_popularity ... ok +test digest::tests::is_stale_false_after_build ... ok +test lifecycle::tests::hardening_archive_scan_revalidates_correction_and_recent_use ... ok +test lifecycle::tests::invalidation_reason_as_str_round_trips ... ok +test lifecycle::tests::invalidation_reason_legacy_strings_parse_correctly ... ok +test embeddings::tests::env_disables_embedder_recognizes_off_values ... ok +test embeddings::tests::pick_builtin_model_from_env_handles_aliases ... ok +test embeddings::tests::w3_embedder_enabled_for_config_false_when_env_unset ... ok +test lock::tests::corrupt_lock_is_reclaimed ... ok +test digest::tests::is_stale_true_when_no_cache ... ok +test lock::tests::process_alive_current_is_alive ... ok +test lock::tests::process_alive_dead_pid_is_dead ... ok +test lock::tests::stale_lock_dead_pid_is_reclaimed ... ok +test maintain::tests::a_backwards_clock_does_not_wedge_the_schedule ... ok +test maintain::tests::a_pass_becomes_due_again_after_its_interval ... ok +test maintain::tests::a_pass_that_just_ran_is_not_due ... ok +test maintain::tests::everything_is_due_on_a_brain_that_has_never_run_upkeep ... ok +test maintain::tests::pass_names_round_trip ... ok +test lock::tests::concurrent_acquire_serializes ... ok +test maintain::tests::state_round_trips_and_a_corrupt_file_makes_everything_due ... ok +test migrate::tests::applies_single_migration ... ok +test migrate::tests::backup_brain_custom_path ... ok +test migrate::tests::backup_brain_default_path_does_not_overwrite_existing_backup ... ok +test migrate::tests::backup_brain_default_path_exists_and_valid ... ok +test maintain::tests::passes_are_best_effort_against_a_missing_brain ... ok +test migrate::tests::idempotent_rerun ... ok +test migrate::tests::migrate_v7_forward_adds_origin_and_hlc ... ok +test migrate::tests::multi_step_chain ... ok +test migrate::tests::no_backup_for_in_memory_db ... ok +test migrate::tests::no_backup_for_noop ... ok +test migrate::tests::noop_when_at_target ... ok +test migrate::tests::rejects_newer_db ... ok +test migrate::tests::retention_keep_3 ... ok +test migrate::tests::rollback_on_failing_migration ... ok +test ordering::tests::a_prefixless_summary_is_dated_at_the_front ... ok +test ordering::tests::capsules_are_reordered_by_time_and_dated ... ok +test ordering::tests::equal_timestamps_keep_the_brokers_order ... ok +test ordering::tests::markers_match_whole_words_only ... ok +test ordering::tests::ordering_questions_are_recognised ... ok +test ordering::tests::ordinary_questions_are_not_ordering_questions ... ok +test ordering::tests::reordering_never_adds_or_drops_a_capsule ... ok +test ordering::tests::the_date_survives_the_hooks_summary_stripping ... ok +test ordering::tests::the_token_estimate_accounts_for_the_date_prefix ... ok +test ordering::tests::undated_capsules_are_kept_after_the_timeline ... ok +test migrate::tests::backup_created_for_file_db ... ok +test digest::tests::load_cached_digest_serves_stale_text ... ok +test embeddings::tests::w3_embedder_env_disable_overrides_config_true ... ok +test embeddings::tests::w3_embedder_env_model_id_overrides_config_false ... ok +test digest::tests::warm_start_block_respects_gate_and_renders_digest ... ok +test episode::tests::capture_episode_end_to_end ... ok +test graph::tests::recording_memories_links_them_without_a_manual_graph_build ... ok +test lock::tests::live_held_lock_times_out ... ok +test hardening_evidence_tests::hardening_exposure_no_invention_stale_feedback_and_unknown_outcome ... ok +test lifecycle::tests::forget_brain_apply_invalidates_noise_keeps_signal ... ok +test lifecycle::tests::forget_brain_dry_run_identifies_noise_keeps_signal ... ok +test project::tests::apply_export_redaction_both_flags_strips_tags_and_context ... ok +test project::tests::apply_export_redaction_no_flags_is_passthrough ... ok +test project::tests::apply_export_redaction_redact_only_strips_context ... ok +test lifecycle::tests::forget_protects_recently_retrieved_via_last_used_at ... ok +test lifecycle::tests::gc_proposals_expires_old_pending_keeps_fresh ... ok +test lifecycle::tests::invalidations_by_reason_groups_structured_reasons ... ok +test lifecycle::tests::regret_flagged_memories_flags_above_threshold ... ok +test project::tests::a_quarantined_pack_reaches_the_review_queue_and_not_retrieval ... ok +test project::tests::abort_run_already_finished_returns_err ... ok +test project::tests::abort_run_stamps_aborted_and_frees_lock ... ok +test project::tests::abort_run_unknown_id_returns_err ... ok +test project::tests::accepting_a_quarantined_proposal_admits_it ... ok +test project::tests::add_memories_batch_all_entries_same_embedding_model ... ok +test project::tests::add_memories_batch_deduplicates ... ok +test project::tests::add_memories_batch_present_retrievable_rebuild_safe ... ok +test project::tests::add_memory_distinct_texts_no_conflicts ... ok +test project::tests::add_memory_redacts_secrets_before_persist ... ok +test project::tests::ann_retrieval_round_trips_and_invalidate_drops ... ok +test project::tests::at_root_init_and_round_trip_memory ... ok +test project::tests::at_root_init_is_idempotent ... ok +test project::tests::batch_review_accepts_filtered_subset_and_rejects_remainder ... ok +test project::tests::blame_run_separates_cited_from_silent_passengers ... ok +test project::tests::cite_outcome_survives_rebuild ... ok +test project::tests::compact_brain_default_preserves_everything ... ok +test project::tests::compact_brain_event_trim_keeps_materialized_memories ... ok +test project::tests::compact_brain_event_trim_then_rebuild_is_consistent ... ok +test project::tests::invalidate_memory_persists_invalidated_metadata_and_survives_rebuild ... ok +test project::tests::compact_brain_purge_invalidated_reclaims_space ... ok +test project::tests::detect_conflicts_env_off_writes_no_conflict_rows ... ok +test project::tests::edit_memory_changes_kind_only ... ok +test project::tests::list_memories_top_sorts_by_usefulness_ratio_and_drops_small_samples ... ok +test project::tests::list_proposals_filters_and_reject_records_reason ... ok +test project::tests::edit_memory_errors ... ok +test project::tests::edit_memory_updates_text_and_preserves_history ... ok +test project::tests::export_import_round_trip ... ok +test project::tests::export_redact_import_roundtrip_and_dedup ... ok +test project::tests::export_scope_kind_filter ... ok +test project::tests::fix2_search_excludes_superseded_rows ... ok +test project::tests::fix4_prune_excludes_superseded_rows ... ok +test project::tests::fix4_top_excludes_superseded_rows ... ok +test project::tests::import_dedup_on_second_import ... ok +test project::tests::import_pack_merge_replace_and_provenance ... ok +test project::tests::import_scope_override_global_user ... ok +test project::tests::import_skips_malformed_entries ... ok +test project::tests::invalidated_memory_is_excluded_from_broker_retrieval ... ok +test project::tests::load_project_rejects_future_config_version ... ok +test project::tests::redact_context_suffix_strips_trailing_context ... ok +test project::tests::redact_tags_prefix_strips_leading_tags ... ok +test project::tests::manual_regret_lowers_usefulness_and_confidence ... ok +test project::tests::memory_add_survives_projection_rebuild_from_trace ... ok +test project::tests::p0_global_user_add_memory_works_from_non_project_dir ... ok +test project::tests::repo_ingest_indexes_searchable_files_and_context_capsules ... ok +test project::tests::p0_global_user_honors_use_user_brain_false_when_start_is_project ... ok +test project::tests::run_aborted_does_not_update_usefulness ... ok +test project::tests::run_failed_decrements_usefulness_unless_gate ... ok +test project::tests::perf_tier1_structural_invariant_and_timing ... ok +test project::tests::prune_low_usefulness_dry_run_then_apply ... ok +test project::tests::quarantine_collapses_duplicates_within_a_pack ... ok +test project::tests::quarantine_does_not_re_propose_what_you_already_have ... ok +test project::tests::similar_ingested_correction_preserves_both_claims_and_rebuild ... ignored, requires a cached local embedding model +test project::tests::quarantine_does_not_reach_back_into_packs_already_installed ... ok +test project::tests::real_run_cite_does_not_bump_in_apply_memory_cited ... ok +test project::tests::rebuild_auto_fallback_imports_traces_when_events_table_empty ... ok +test project::tests::rebuild_from_events_table_restores_memories ... ok +test project::tests::rebuild_from_traces_flag_reimports_on_disk_traces ... ok +test project::tests::record_mcp_citation_writes_memory_citations_row ... ok +test project::tests::record_regret_writes_retrieval_regret_event ... ok +test project::tests::reindex_with_explicit_embedder_uses_that_model ... ok +test project::tests::retrieve_context_lexical_returns_fts_hits_without_embedder ... ok +test project::tests::w3_1_config_enabled_default_does_not_regress ... ok +test project::tests::retrieve_proactive_returns_actionable_kind_and_excludes_others ... ok +test project::tests::retrieve_with_injected_embedder_returns_fts_hits ... ok +test projector::correction_regressions::corpus_revision_observes_existing_embedding_updates_from_another_connection ... ok +test projector::correction_regressions::correction_history_separates_known_and_effective_time_and_replays ... ok +test projector::correction_regressions::correction_validation_rolls_back_events_text_and_fts ... ok +test projector::correction_regressions::delayed_run_evidence_stays_on_the_retiring_claim ... ok +test projector::correction_regressions::explicit_unbound_exposure_never_credits_a_claim ... ok +test projector::tests::add_memory_edges_writes_and_survives_rebuild ... ok +test project::tests::run_finished_gives_weak_signal_to_silent_passenger_memories ... ok +test projector::tests::confidence_calibration_rewards_success_and_survives_rebuild ... ok +test projector::tests::empty_payload_memory_accepted ... ok +test projector::tests::empty_payload_memory_cited ... ok +test projector::tests::empty_payload_memory_invalidated ... ok +test projector::tests::empty_payload_memory_proposed ... ok +test projector::tests::empty_payload_memory_rejected ... ok +test projector::tests::empty_payload_memory_temporal ... ok +test projector::tests::empty_payload_run_aborted ... ok +test projector::tests::empty_payload_run_failed ... ok +test projector::tests::empty_payload_run_finished ... ok +test projector::tests::empty_payload_run_started ... ok +test projector::tests::empty_payload_work_episode ... ok +test projector::tests::event_carries_and_roundtrips_origin ... ok +test projector::tests::concurrent_manual_regrets_lose_no_updates ... ok +test projector::tests::failure_penalty_scales_with_prior_citations ... ok +test projector::tests::initial_usefulness_seeds_score_and_survives_rebuild ... ok +test projector::tests::memory_cited_redacts_event_and_projection_rationale ... ok +test projector::tests::memory_proposed_redacts_event_and_projection_payloads ... ok +test projector::tests::memory_temporal_stamps_validity_and_survives_rebuild ... ok +test projector::tests::rebuild_import_failure_preserves_existing_projection ... ok +test projector::tests::rebuild_import_keeps_durable_events_missing_from_trace ... ok +test projector::tests::rebuild_in_place_no_dup_events ... ok +test projector::tests::rebuild_in_place_payload_fidelity ... ok +test projector::tests::rebuild_in_place_reconstructs_citations ... ok +test projector::tests::rebuild_refuses_to_erase_unlogged_legacy_user_memory ... ok +test projector::tests::reset_projection_keeps_events ... ok +test projector::tests::trace_import_binds_historical_exposure_before_later_correction ... ok +test projector::tests::trace_import_replays_missing_correction_before_later_invalidation ... ok +test projector::tests::upcast_is_identity_at_v1 ... ok +test projector::tests::well_formed_run_started_projects_correctly ... ok +test redact::tests::anthropic_oauth_token_is_redacted ... ok +test redact::tests::bearer_token_in_curl_log_is_redacted ... ok +test redact::tests::clean_text_round_trips_untouched ... ok +test redact::tests::generic_assignments_match_only_with_secret_looking_value ... ok +test redact::tests::github_pat_classic_and_fine_grained_redacted ... ok +test redact::tests::luhn_check ... ok +test redact::tests::match_offsets_point_into_original_text ... ok +test redact::tests::openai_key_is_redacted_without_shadowing_anthropic_prefix ... ok +test redact::tests::overlapping_matches_keep_first_only ... ok +test redact::tests::redaction_preserves_non_secret_surroundings ... ok +test redact::tests::scrub_for_export_leaves_technical_text_alone ... ok +test redact::tests::scrub_for_export_luhn_gates_credit_cards ... ok +test redact::tests::scrub_for_export_redacts_pii_and_credentials ... ok +test redact::tests::slack_aws_jwt_pem_google_all_redact ... ok +test redact::tests::summary_lists_unique_kinds ... ok +test redact::tests::url_embedded_credentials_are_redacted ... ok +test projector::tests::rebuild_reads_events_after_acquiring_writer_lock ... ok +test project::tests::run_finished_increments_usefulness_for_injected_memories ... ok +test project::tests::search_memories_paginates_and_filters_by_kind ... ok +test project::tests::set_age_backdates_created_at_and_survives_rebuild ... ok +test project::tests::standalone_cite_records_reliance_without_outcome_credit ... ok +test project::tests::undo_last_memory_invalidates_newest_first ... ok +test project::tests::undo_last_memory_on_empty_brain_returns_none ... ok +test reindex::tests::reindex_scope_parser_accepts_aliases ... ok +test project::tests::w1_4_add_memory_creates_no_run_dir_but_memory_and_runs_row_exist ... ok +test project::tests::w1_4_dedup_hit_creates_no_orphan_run_dir ... ok +test reinforce::tests::hardening_semantic_routes_ignore_unrelated_candidate_ids ... ok +test project::tests::w1_4_memory_ops_create_no_run_dirs ... ok +test project::tests::w1_4_memory_survives_rebuild_from_events_table_no_trace ... ok +test project::tests::w1_5_init_creates_kimetsu_dir_but_no_runs_dir ... ok +test roi::tests::estimate_output_tokens_quarter_ratio ... ok +test roi::tests::estimate_savings_all_kinds_covered ... ok +test roi::tests::estimate_savings_multi_kind ... ok +test roi::tests::estimate_savings_single_kind ... ok +test roi::tests::estimate_savings_zero_when_empty ... ok +test roi::tests::format_tokens_below_1000 ... ok +test roi::tests::format_tokens_thousands ... ok +test project::tests::w3_1_config_disabled_writes_null_embedding ... ok +test project::tests::w3_1_open_embedder_for_resolver ... ok +test roi::tests::resolve_price_known_model ... ok +test roi::tests::resolve_price_longest_prefix_wins ... ok +test roi::tests::resolve_price_override_wins ... ok +test roi::tests::resolve_price_unknown_model_none ... ok +test project::tests::w3_1_retrieval_fts_only_when_embedder_disabled ... ok +test reindex::tests::reindex_one_conn_backfills_null_embeddings ... ok +test reindex::tests::reindex_one_conn_batches_more_than_chunk_rows ... ok +test reindex::tests::reindex_one_conn_dry_run_does_not_mutate ... ok +test reindex::tests::reindex_one_conn_force_reembeds_current_model_rows ... ok +test reindex::tests::reindex_one_conn_limit_smaller_than_chunk_is_faithful ... ok +test reindex::tests::reindex_one_conn_skips_superseded_rows ... ok +test reindex::tests::reindex_one_conn_with_noop_embedder_returns_zero_candidates ... ok +test roi::tests::roi_window_parse ... ok +test roi::tests::savings_sentence_positive_no_usd ... ok +test roi::tests::savings_sentence_positive_with_usd ... ok +test reinforce::tests::co_cited_pair_staples_once_and_keeps_originals ... ok +test schema::tests::apply_pragmas_does_not_error_on_in_memory_conn ... ok +test schema::tests::apply_pragmas_sets_cache_size_on_rw_connection ... ok +test schema::tests::fresh_init_reaches_current_version_with_full_shape ... ok +test schema::tests::idempotent_initialize_twice ... ok +test schema::tests::idempotent_rerun_preserves_data ... ok +test schema::tests::v2_to_v3_migration_adds_superseded_by ... ok +test schema::tests::v3_to_v4_migration_adds_memory_edges ... ok +test schema::tests::v4_to_v5_migration_adds_work_episodes ... ok +test schema::tests::v5_to_v6_migration_adds_skill_proposals ... ok +test schema::tests::v6_to_v7_migration_adds_temporal_validity_columns ... ok +test schema::tests::validate_hard_errors_for_newer_db ... ok +test schema::tests::validate_ok_at_target ... ok +test schema::tests::validate_returns_needs_migration_for_older_db ... ok +test serving::conflict_carry_tests::capsule_cap_does_not_turn_conflicting_evidence_into_support ... ok +test serving::conflict_carry_tests::guard_reserves_a_bounded_pool_before_the_final_cap ... ok +test serving::tests::compression_keeps_the_value_that_justified_admission ... ok +test serving::tests::explicit_fact_guard_excludes_topic_match_before_output_cap ... ok +test reinforce::tests::grouped_citations_share_run_and_persist_query ... ok +test serving::tests::reranker_floor_and_final_serialization_reject_candidates_before_measurement ... ok +test skill_synthesis::tests::a_drafted_candidate_is_reported_as_pending_not_as_a_candidate ... ok +test skill_synthesis::tests::a_quiet_brain_gets_no_nudge ... ok +test skill_synthesis::tests::accept_already_decided_proposal_errors ... ok +test skill_synthesis::tests::accept_proposal_records_installed_path ... ok +test skill_synthesis::tests::an_accepted_proposal_ends_the_nudge ... ok +test skill_synthesis::tests::an_undrafted_candidate_is_surfaced_with_its_command ... ok +test skill_synthesis::tests::below_threshold_not_a_candidate ... ok +test skill_synthesis::tests::candidate_detected_at_citation_threshold ... ok +test skill_synthesis::tests::insert_and_list_pending_proposals ... ok +test skill_synthesis::tests::reject_proposal_marks_rejected ... ok +test skill_synthesis::tests::report_only_proposal_has_no_draft_content ... ok +test skill_synthesis::tests::staleness_check_flags_superseded_source ... ok +test skill_synthesis::tests::staleness_check_ok_for_live_source ... ok +test skill_synthesis::tests::superseded_memory_excluded_from_candidates ... ok +test sync::tests::cursor_advances_correctly ... ok +test sync::tests::directory_protocol_push_pull ... ok +test sync::tests::dry_run_import_does_not_write ... ok +test sync::tests::export_excludes_local_only_kinds ... ok +test sync::tests::export_redacts_secrets ... ok +test sync::tests::import_is_idempotent ... ok +test sync::tests::round_trip_export_import ... ok +test sync::tests::sync_archive_restore_round_trip ... ok +test sync::tests::sync_directory_failure_preserves_projection_and_pull_cursors ... ok +test sync::tests::sync_directory_merges_peer_dependencies_before_replay ... ok +test sync::tests::sync_import_counts_duplicate_lines_in_dry_run_and_commit ... ok +test sync::tests::sync_import_failure_rolls_back_entire_batch ... ok +test reinforce::tests::route_below_min_support_does_not_fire ... ok +test sync::tests::sync_import_refuses_to_erase_unlogged_memory ... ok +test sync::tests::two_brains_converge_after_exchange ... ok +test trace::tests::gc_env_zero_disables_gc ... ok +test trace::tests::gc_fresh_dirs_all_survive ... ok +test trace::tests::select_empty_returns_empty ... ok +test trace::tests::select_keep_larger_than_slice_selects_nothing ... ok +test trace::tests::select_keep_protects_newest ... ok +test trace::tests::select_mixed_age_and_keep ... ok +test trace::tests::select_newer_than_cutoff_not_selected ... ok +test trace::tests::select_older_than_cutoff_selected ... ok +test sync::tests::sync_replays_historical_correction_before_local_retirement ... ok +test trace::tests::trace_writer_create_env_zero_skips_gc ... ok +test trust::tests::audit_counts_the_unvetted_external_population ... ok +test trust::tests::audit_flags_a_write_burst_and_ignores_ordinary_writing ... ok +test trust::tests::audit_of_an_empty_brain_is_empty_not_an_error ... ok +test trust::tests::hardening_citation_retains_origin_penalty ... ok +test trust::tests::known_sources_classify ... ok +test trust::tests::the_enum_ordering_is_the_trust_ordering ... ok +test trust::tests::trust_never_exceeds_one ... ok +test trust::tests::unknown_provenance_reads_as_local ... ok +test tune::tests::all_combos_covers_the_full_grid ... ok +test tune::tests::compute_objective_formula ... ok +test tune::tests::compute_objective_with_regret_zero_rate_matches_base ... ok +test tune::tests::compute_objective_zero_cost_weight_is_just_mrr ... ok +test trace::tests::trace_writer_create_new_run_survives_gc ... ok +test tune::tests::even_full_historical_regret_is_diagnostic_only ... ok +test tune::tests::explicit_default_cost_policy_uses_budget_fraction_units ... ok +test tune::tests::historical_regret_cannot_change_candidate_objective ... ok +test tune::tests::model_advisor_no_recommendation_below_milestone ... ok +test tune::tests::model_advisor_recommends_at_milestone ... ok +test tune::tests::overlapping_aliases_and_task_families_never_leak_into_holdout ... ok +test reinforce::tests::routes_build_and_boost_is_bounded ... ok +test reinforce::tests::single_co_cite_does_not_staple ... ok +test roi::tests::per_memory_roi_respects_top_limit ... ok +test tune::tests::select_winner_picks_highest_objective ... ok +test tune::tests::single_family_cannot_supply_an_independent_holdout ... ok +test tune::tests::split_membership_is_independent_of_input_order ... ok +test tune::tests::train_holdout_split_80_20 ... ok +test tune::tests::train_holdout_split_empty ... ok +test tune::tests::tune_history_empty_when_no_file ... ok +test tune::tests::tune_history_entry_memory_count_roundtrip ... ok +test tune::tests::tune_history_roundtrip ... ok +test roi::tests::per_memory_roi_top_entries_sorted_by_savings ... ok +test roi::tests::roi_report_digest_served_adds_savings ... ok +test roi::tests::roi_report_empty_db_returns_zeros ... ok +test roi::tests::roi_report_negative_net_when_overhead_exceeds_savings ... ok +test roi::tests::roi_report_output_token_estimate_is_quarter_of_input ... ok +test roi::tests::roi_report_unknown_model_no_usd ... ok +test roi::tests::roi_report_usd_with_known_model ... ok +test roi::tests::roi_report_usd_with_override ... ok +test roi::tests::roi_report_with_citations_computes_savings ... ok +test roi::tests::session_roi_returns_none_when_no_citations ... ok +test serving::tests::production_and_eval_use_same_final_budget_and_arbitration_with_injected_models ... ok +test tune::tests::count_regret_events_zero_in_empty_db ... ok +test tune::tests::retune_trigger_corpus_milestone_when_enough_memories ... ok +test tune::tests::retune_trigger_drift_when_regret_rate_high ... ok +test tune::tests::retune_trigger_no_history_no_events ... ok +test tuneset::tests::build_personal_eval_deduplicates_same_query ... ok +test tuneset::tests::build_personal_eval_empty_when_no_queries_stored ... ok +test user_profile::tests::a_global_duplicate_is_not_repeated ... ok +test user_profile::tests::a_global_preference_is_marked_as_such ... ok +test user_profile::tests::an_empty_profile_renders_nothing ... ok +test user_profile::tests::hardening_profile_checks_numeric_start_and_expiry ... ok +test user_profile::tests::only_preference_memories_make_the_profile ... ok +test user_profile::tests::overlong_preferences_are_skipped_and_the_block_is_budgeted ... ok +test user_profile::tests::project_preferences_lead_and_globals_fill_the_remainder ... ok +test user_profile::tests::proven_preferences_come_first ... ok +test user_profile::tests::retired_preferences_are_excluded ... ok +test user_profile::tests::the_profile_is_capped ... ok +test user_profile::tests::the_profile_reads_as_instructions ... ok +test tuneset::tests::build_personal_eval_noise_when_no_citation_in_window ... ok +test tuneset::tests::build_personal_eval_positive_case_from_time_window ... ok +test tuneset::tests::exact_exposure_citation_labels_only_its_query_and_current_claim ... ok +test user_brain::tests::add_user_memory_persists_and_dedups ... ok +test user_brain::tests::fix5_dedup_does_not_collapse_onto_superseded_row ... ok +test user_brain::tests::migration_upgrades_user_brain_creates_backup_and_preserves_data ... ok +test user_brain::tests::open_user_brain_creates_db_on_first_call ... ok +test user_brain::tests::open_user_brain_readonly_returns_none_before_first_write ... ok +test user_brain::tests::open_user_brain_returns_none_when_disabled ... ok +test user_brain::tests::readonly_degrades_to_none_on_stale_schema ... ok +test user_brain::tests::user_brain_path_resolves_from_override_env ... ok +test user_brain::tests::w3_open_user_brain_env_disable_overrides_config_true ... ok +test user_brain::tests::w3_open_user_brain_env_enable_overrides_config_false ... ok +test user_brain::tests::w3_open_user_brain_for_config_false_returns_none ... ok +test user_brain::tests::w3_open_user_brain_for_config_true_opens_normally ... ok + +test result: ok. 775 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 95.48s + + Running unittests src\lib.rs (E:/Kimetsu/target\debug\deps\kimetsu_chat-03711f11f092810b.exe) + +running 135 tests +test ask::tests::is_command_query_matches_how_do_i ... ok +test ask::tests::is_command_query_no_match_for_general_questions ... ok +test ask::tests::record_helpful_mark_no_panic_on_file_handles ... ok +test ask::tests::reorder_puts_command_first ... ok +test ask::tests::reorder_noop_for_non_command_query ... ok +test ask::tests::verbatim_answer_labels_command_prominently ... ok +test bridge::tests::aggregate_state_extension_counts_as_core ... ok +test ask::tests::compose_answer_refusal_when_no_brain ... ok +test bridge::tests::b1_claude_hooks_golden_shared_pretooluse_event ... ok +test bridge::tests::b1_codex_hooks_golden_with_user_content ... ok +test bridge::tests::claude_hooks_install_session_end ... ok +test bridge::tests::b1_mcp_config_golden_preserves_user_server ... ok +test bridge::tests::claude_hooks_include_sessionstart_warm ... ok +test bridge::tests::claude_hooks_merge_tolerates_utf8_bom ... ok +test bridge::tests::claude_hooks_merge_preserves_user_hooks ... ok +test bridge::tests::codex_hooks_merge_preserves_user_hooks ... ok +test bridge::tests::copy_dir_with_replace_refuses_symlink_destination ... ok +test bridge::tests::install_scope_parses_aliases ... ok +test bridge::tests::cursor_global_install_writes_to_home ... ok +test bridge::tests::b2_install_codex_workspace_preserves_user_content ... ok +test bridge::tests::cursor_workspace_install_writes_mcp_and_rules ... ok +test bridge::tests::merge_claude_md_fresh_file ... ok +test bridge::tests::install_preserves_existing_user_claude_md ... ok +test bridge::tests::cursor_uninstall_removes_mcp_entry ... ok +test bridge::tests::cursor_workspace_install_preserves_user_server ... ok +test bridge::tests::b2_install_codex_global_preserves_user_content ... ok +test bridge::tests::mcp_config_is_idempotent_and_scopes_keys ... ok +test bridge::tests::cursor_workspace_install_is_idempotent ... ok +test bridge::tests::cursor_status_detects_installed_workspace ... ok +test bridge::tests::merge_claude_md_idempotent ... ok +test bridge::tests::merge_claude_md_preserves_user_content ... ok +test bridge::tests::b2_install_claudecode_global_preserves_user_content ... ok +test bridge::tests::merge_claude_md_tolerates_bom ... ok +test bridge::tests::merge_claude_md_upgrades_in_place ... ok +test bridge::tests::b2_install_claudecode_workspace_preserves_user_content ... ok +test bridge::tests::merge_claude_md_repairs_begin_without_end ... ok +test bridge::tests::b3_upgrade_idempotency_claudecode_workspace ... ok +test bridge::tests::remote_install_rejects_unsupported_host ... ok +test bridge::tests::qq1_status_fresh_workspace_all_absent ... ok +test bridge::tests::plugin_install_no_proactive_skips_tool_hooks ... ok +test bridge::tests::remote_install_literal_token_is_written ... ok +test bridge::tests::u1_idempotent_on_clean_host ... ok +test bridge::tests::qq1_status_user_content_not_detected_as_kimetsu ... ok +test bridge::tests::qq1_status_partial_claude_code_workspace ... ok +test bridge::tests::upsert_kimetsu_hook_preserves_user_groups_and_is_idempotent ... ok +test commands::tests::non_slash_input_is_not_a_command ... ok +test commands::tests::parses_argument_commands ... ok +test commands::tests::parses_known_commands ... ok +test bridge::tests::plugin_install_refreshes_generated_files_without_force ... ok +test commands::tests::parses_memory_and_skills_arguments ... ok +test commands::tests::parses_strict_truthy_and_falsy ... ok +test cost::tests::budget_clamp_prevents_negative_budget ... ok +test commands::tests::unknown_slash_falls_through_to_agent ... ok +test cost::tests::new_meter_is_zeroed ... ok +test bridge::tests::remote_install_claude_writes_http_mcp_entry ... ok +test cost::tests::over_budget_triggers_when_crossed ... ok +test cost::tests::record_turn_accumulates_and_tracks_max ... ok +test cost::tests::record_turn_clamps_negative_input ... ok +test bridge::tests::plugin_install_writes_optional_and_required_modes ... ok +test bridge::tests::write_cursor_mcp_config_fresh_and_idempotent ... ok +test bridge::tests::qq1_status_after_claude_code_workspace_install ... ok +test bridge::tests::qq1_status_codex_workspace_install_and_partial ... ok +test bridge::tests::imports_and_exports_skill_bundle ... ok +test mcp_server::tests::brain_insights_appears_in_tool_definitions ... ok +test bridge::tests::plugin_install_global_writes_to_home_not_workspace ... ok +test bridge::tests::u1_roundtrip_codex_workspace ... ok +test mcp_server::tests::cite_tool_is_write_gated ... ok +test mcp_server::tests::cite_tool_listed_and_write_gated ... ok +test bridge::tests::u1_preserves_user_hook_on_shared_event ... ok +test bridge::tests::u1_roundtrip_claude_global ... ok +test bridge::tests::u1_roundtrip_claude_workspace ... ok +test mcp_server::tests::context_tool_catalog_advertises_episode_identity_lanes ... ok +test mcp_server::tests::dispatch_allowlist_blocks_unlisted_tool_call ... ok +test mcp_server::tests::dispatch_allowlist_filters_tools_list ... ok +test bridge::tests::qq1_status_install_then_uninstall_flips_to_absent ... ok +test mcp_server::tests::dispatch_no_allowlist_returns_full_catalog ... ok +test mcp_server::tests::brain_insights_reports_missing_project_without_error ... ok +test mcp_server::tests::global_plugin_install_is_not_available_through_mcp_helper ... ok +test mcp_server::tests::brain_status_reports_missing_project_without_error ... ok +test mcp_server::tests::dispatch_allowlist_permits_listed_tool_call ... ok +test mcp_server::tests::dispatch_remote_ignores_config_for_write_tools ... ok +test mcp_server::tests::dispatch_blocks_writes_when_config_disables_them ... ok +test mcp_server::tests::benchmark_context_required_reports_missing_task_memory ... ok +test mcp_server::tests::benchmark_record_outcome_writes_retrievable_memory ... ok +test mcp_server::tests::initialize_explains_kimetsu_workflow ... ok +test mcp_server::tests::lists_tools ... ok +test mcp_server::tests::benchmark_record_outcome_creates_pending_generalized_memory_proposal ... ok +test mcp_server::tests::benchmark_context_returns_playbook_and_enforces_task_memory ... ok +test mcp_server::tests::tool_required_arguments_are_declared_in_their_schema ... ok +test mcp_server::tests::write_tools_decision_precedence ... ok +test repl::tests::agent_registry_loads_markdown_agent ... ok +test repl::tests::build_chat_brain_context_keeps_only_tail_when_history_is_long ... ok +test repl::tests::build_chat_brain_context_renders_transcript_alone ... ok +test repl::tests::build_chat_brain_context_returns_none_when_no_state ... ok +test repl::tests::build_chat_brain_context_truncates_long_turns ... ok +test repl::tests::chat_route_distinguishes_conversation_from_workspace_work ... ok +test repl::tests::file_mentions_expand_existing_files ... ok +test repl::tests::file_mentions_reject_paths_outside_workspace ... ok +test repl::tests::greeting_is_handled_without_provider_or_cost ... ok +test repl::tests::hook_registry_ignores_workspace_hooks_by_default ... ok +test repl::tests::image_generation_is_model_gated ... ok +test repl::tests::input_history_moves_backward_and_forward ... ok +test repl::tests::mcp_registry_loads_json_servers ... ok +test repl::tests::non_slash_input_attempts_agent_round_trip ... ok +test repl::tests::quit_command_ends_session_cleanly ... ok +test repl::tests::rich_ui_renders_dragon_banner ... ok +test repl::tests::run_command_request_parses_terminal_flags ... ok +test repl::tests::skill_prefix_parses_name_and_prompt ... ok +test repl::tests::slash_clear_redraws_banner_and_keeps_session_alive ... ok +test repl::tests::slash_cost_shows_zero_at_start ... ok +test repl::tests::slash_goal_sets_and_recalls ... ok +test repl::tests::slash_help_prints_command_list ... ok +test repl::tests::slash_palette_filters_and_completes_commands ... ok +test repl::tests::slash_skills_lists_and_loads_workspace_skill ... ok +test repl::tests::slash_strict_toggles ... ok +test repl::tests::terminal_run_requires_interactive_terminal ... ok +test skills::tests::installs_external_skill_as_kimetsu_bundle ... ok +test skills::tests::loads_selected_skill_and_renders_context ... ok +test skills::tests::parses_codex_and_claude_skill_frontmatter ... ok +test skills::tests::resolve_contained_rejects_paths_outside_workspace_and_roots ... ok +test mcp_server::tests::brain_context_returns_memory_capsules ... ok +test mcp_server::tests::brain_context_tool_with_stub_reranker_reorders_and_caps ... ok +test mcp_server::tests::brain_insights_returns_well_formed_report ... ok +test mcp_server::tests::cite_tool_writes_memory_citations_row ... ok +test mcp_server::tests::configured_rerank_cutoff_controls_actual_mcp_admission ... ok +test ask::tests::compose_answer_refusal_with_empty_brain ... ok +test ask::tests::compose_answer_verbatim_or_composed_with_memory ... ok +test mcp_server::tests::hardening_benchmark_budget_recomputes_required_evidence ... ok +test mcp_server::tests::hardening_context_final_payload_excludes_rejected_and_is_bounded ... ok +test mcp_server::tests::hardening_failed_embedder_load_is_bounded_and_has_no_success_exposure ... ok +test mcp_server::tests::hardening_mcp_warm_identity_and_retry_after_budget_omission ... ok +test mcp_server::tests::hardening_normal_output_does_not_grow_with_rejected_summary_size ... ok +test mcp_server::tests::hardening_served_ids_and_revisions_match_final_mcp_payload ... ok +test mcp_server::tests::mcp_fact_guard_rejects_high_scoring_topic_and_respects_opt_out ... ok +test mcp_server::tests::stdio_uses_configured_reranker_off_and_initialization_error_explicitly ... ok + +test result: ok. 135 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 19.41s + + Running unittests src\main.rs (E:/Kimetsu/target\debug\deps\kimetsu-1f656e727ac9da73.exe) + +running 284 tests +test ask::tests::is_command_query_delegated_correctly ... ok +test distiller::tests::normalize_distiller_provider_aws_alias ... ok +test distiller::tests::distill_lessons_empty_on_model_error ... ok +test distiller::tests::normalize_distiller_provider_bedrock_alias ... ok +test distiller::tests::normalize_distiller_provider_existing_aliases_unchanged ... ok +test distiller::tests::parse_lessons_extracts_array_and_defaults ... ok +test distiller::tests::parse_lessons_handles_brackets_inside_strings ... ok +test distiller::tests::distill_lessons_uses_model_text ... ok +test distiller::tests::normalize_distiller_provider_ollama ... ok +test distiller::tests::parse_lessons_temporal_does_not_break_caps ... ok +test distiller::tests::parse_lessons_ignores_trailing_prose_and_brackets ... ok +test distiller::tests::parse_lessons_tolerates_garbage ... ok +test distiller::tests::parse_lessons_with_temporal_tags ... ok +test distiller::tests::quality_gate_drops_too_long ... ok +test distiller::tests::quality_gate_drops_too_short ... ok +test distiller::tests::quality_gate_drops_transient_phrasing ... ok +test distiller::tests::quality_gate_passes_durable_lesson_without_embedder ... ok +test ask::tests::compose_answer_graceful_for_missing_workspace ... ok +test doctor::tests::doctor_report_aggregates_counts ... ok +test doctor::tests::outcome_glyphs_distinct ... ok +test distiller::tests::build_transcript_view_streams_text_and_bounds ... ok +test doctor::tests::schema_mismatch_detection ... ok +test doctor::tests::doctor_detects_current_codex_hooks_json ... ok +test doctor::tests::skew_fresh_server_is_pass ... ok +test doctor::tests::skew_mixed_stale_and_fresh_is_warn ... ok +test doctor::tests::skew_multiple_servers_no_start_time_is_warn ... ok +test doctor::tests::doctor_rejects_legacy_pre_turn_scripts ... ok +test doctor::tests::skew_no_binary_mtime_single_server_is_pass ... ok +test doctor::tests::skew_no_servers_is_pass ... ok +test doctor::tests::skew_single_server_no_start_time_is_pass ... ok +test doctor::tests::skew_stale_server_is_warn_with_restart_guidance ... ok +test doctor::tests::skew_wrong_exe_path_is_warn ... ok +test embed_daemon::ipc::tests::connect_with_no_listener_errors ... ok +test embed_daemon::client::tests::request_returns_none_when_no_daemon ... ok +test embed_daemon::proto::conflict_wire_tests::response_preserves_conflicts_after_capsules_have_been_trimmed ... ok +test embed_daemon::proto::legacy_conflict_wire_tests::legacy_response_without_conflicts_still_roundtrips_without_new_fields ... ok +test embed_daemon::ipc::tests::listen_then_connect_round_trips ... ok +test embed_daemon::proto::tests::capsule_wire_preserves_optional_structured_evidence ... ok +test embed_daemon::proto::tests::legacy_capsule_is_explicitly_unbound ... ok +test embed_daemon::proto::tests::read_line_on_empty_is_eof ... ok +test embed_daemon::proto::tests::request_round_trips_through_a_line ... ok +test embed_daemon::proto::tests::response_round_trips ... ok +test distiller::tests::quality_gate_drops_near_duplicate_passes_novel ... ok +test distiller::tests::quality_gate_preserves_similar_corrections_and_bounded_workarounds ... ok +test harvest_setup::tests::upsert_env_var_replaces_existing ... ok +test harvest_setup::tests::wizard_accepts_codex_harness ... ok +test harvest_setup::tests::wizard_declined_writes_nothing ... ok +test harvest_setup::tests::wizard_accepts_openai_custom_model ... ok +test harvest_setup::tests::wizard_unrecognized_provider_aborts ... ok +test harvest_setup::tests::wizard_unrecognized_harness_aborts ... ok +test proactive_state::tests::dedupe_filter_mixed_keeps_new_only ... ok +test proactive_state::tests::dedupe_filter_never_empties_injection ... ok +test proactive_state::tests::dedupe_filter_passes_empty_handle_always ... ok +test proactive_state::tests::dedupe_filter_passes_unsurfaced_handles ... ok +test doctor::tests::redact_smoke_passes_against_known_secret_string ... ok +test proactive_state::tests::dedupe_filter_removes_surfaced_handles ... ok +test proactive_state::tests::dedupe_marks_once ... ok +test proactive_state::tests::error_signature_picks_the_error_line ... ok +test proactive_state::tests::failure_then_resolution_is_detectable ... ok +test proactive_state::tests::harvest_cue_throttle ... ok +test proactive_state::tests::loop_counter_reaches_threshold ... ok +test proactive_state::tests::dedupe_filter_survives_state_roundtrip ... ok +test proactive_state::tests::refractory_window ... ok +test proactive_state::tests::ring_buffer_is_bounded ... ok +test proactive_state::tests::proactive_state_lands_in_cache_not_in_kimetsu ... ok +test harvest_setup::tests::wizard_writes_env_and_config ... ok +test proactive_state::tests::session_path_sanitizes ... ok +test process::tests::classify_chat ... ok +test process::tests::classify_cli_fallback ... ok +test process::tests::classify_hook_variants ... ok +test process::tests::classify_mcp_serve_various_forms ... ok +test process::tests::csv_row_escaped_quote ... ok +test process::tests::csv_row_quoted_comma ... ok +test process::tests::locking_filter_case_insensitive ... ok +test process::tests::locking_filter_empty_list_returns_empty ... ok +test process::tests::locking_filter_excludes_procs_with_no_exe ... ok +test process::tests::locking_filter_no_match_returns_empty ... ok +test proactive_state::tests::save_is_atomic_no_tmp_leftover ... ok +test process::tests::locking_filter_returns_matching_procs ... ok +test process::tests::stop_processes_never_kills_self ... ok +test process::tests::unix_ps_empty_input ... ok +test process::tests::unix_ps_count_excludes_current_and_non_kimetsu ... ok +test process::tests::unix_ps_kinds ... ok +test process::tests::unix_ps_with_etimes_parses_started_at ... ok +test process::tests::unix_ps_without_etimes_falls_back_gracefully ... ok +test process::tests::unix_ps_workspace_extraction ... ok +test process::tests::windows_csv_empty_input ... ok +test process::tests::windows_csv_count_excludes_current_pid ... ok +test process::tests::windows_csv_header_only ... ok +test process::tests::windows_csv_kinds ... ok +test process::tests::windows_csv_malformed_rows_skipped ... ok +test process::tests::windows_csv_with_creation_date_parses_timestamp ... ok +test process::tests::windows_csv_workspace_extraction ... ok +test process::tests::wmi_datetime_epoch ... ok +test process::tests::wmi_datetime_handles_no_offset_suffix ... ok +test process::tests::wmi_datetime_negative_offset ... ok +test process::tests::wmi_datetime_positive_offset ... ok +test process::tests::wmi_datetime_returns_none_for_empty ... ok +test process::tests::wmi_datetime_returns_none_for_malformed ... ok +test process::tests::wmi_datetime_utc_zero_offset ... ok +test process::tests::workspace_flag_at_end_no_value ... ok +test process::tests::workspace_no_flag ... ok +test process::tests::workspace_quoted_with_spaces ... ok +test process::tests::workspace_simple ... ok +test remote_client::tests::render_result_extracts_text_content ... ok +test remote_client::tests::resolve_token_prefers_explicit ... ok +test skill_synth::tests::derive_skill_meta_fallback_when_no_draft ... ok +test skill_synth::tests::derive_skill_meta_uses_frontmatter_when_present ... ok +test skill_synth::tests::extract_frontmatter_parses_name_and_description ... ok +test skill_synth::tests::extract_frontmatter_returns_none_for_no_frontmatter ... ok +test skill_synth::tests::slugify_normalizes_whitespace_and_special_chars ... ok +test tests::an_ordering_query_declines_the_daemon ... ok +test skill_synth::tests::write_skill_provenance_creates_valid_json ... ok +test tests::cli_smoke_config_get_help ... ok +test tests::cli_smoke_config_get_parses_key ... ok +test tests::cli_smoke_config_set_help ... ok +test tests::cli_smoke_config_set_parses_key_value ... ok +test tests::cli_smoke_runs_prune_help ... ok +test tests::cli_smoke_runs_prune_parses_flags ... ok +test tests::cli_smoke_setup_flags_parse ... ok +test tests::cli_smoke_setup_help_parses ... ok +test tests::cli_version_flag_contains_flavor ... ok +test commands::brain::conflict_carry_tests::daemon_conversion_keeps_conflict_notice_after_one_source_is_trimmed ... ok +test embed_daemon::server::tests::serve_answers_ping_then_shuts_down ... ok +test tests::config_set_text_drops_to_custom_only_for_managed_keys_under_a_preset ... ok +test tests::context_hook_output_is_user_prompt_submit_json ... ok +test tests::count_brain_record_calls_handles_both_shapes ... ok +test tests::count_transcript_jsonl_streams_counts ... ok +test tests::daemon_capsules_to_bundle_preserves_fields ... ok +test tests::daemon_skipped_bundle_reports_zero_coverage ... ok +test tests::fmt_bytes_kb ... ok +test tests::fmt_bytes_mb ... ok +test tests::fmt_bytes_sub_kb ... ok +test tests::get_toml_path_missing_returns_none ... ok +test tests::get_toml_path_nested_bool ... ok +test tests::get_toml_path_nested_string ... ok +test tests::get_toml_path_returns_table ... ok +test tests::hardening_free_never_requests_host_harvesting ... ok +test distiller::tests::resolve_distiller_workspace_wins ... ok +test distiller::tests::resolve_distiller_global_when_no_workspace ... ok +test tests::kimetsu_on_path_with_returns_false_for_empty_path ... ok +test tests::kimetsu_on_path_with_returns_false_for_none ... ok +test tests::kimetsu_on_path_with_returns_true_when_exe_dir_on_path ... ok +test tests::normalize_repo_id_handles_url_forms ... ok +test tests::parse_duration_bad_number ... ok +test tests::parse_duration_bad_unit ... ok +test tests::parse_duration_days ... ok +test tests::parse_duration_empty ... ok +test tests::parse_duration_hours ... ok +test tests::parse_duration_minutes ... ok +test tests::parse_duration_seconds ... ok +test tests::parse_openclaw_without_feature_returns_helpful_error ... ok +test tests::parse_pi_without_feature_returns_helpful_error ... ok +test tests::parse_scalar_coerce_to_integer_fails_on_non_numeric ... ok +test tests::parse_scalar_coerces_to_existing_bool ... ok +test tests::parse_scalar_coerces_to_existing_integer ... ok +test tests::parse_scalar_false_infers_bool ... ok +test tests::parse_scalar_float_infers_float ... ok +test tests::parse_scalar_integer_infers_integer ... ok +test tests::parse_scalar_negative_integer ... ok +test tests::parse_scalar_plain_string ... ok +test tests::parse_scalar_string_when_existing_is_string ... ok +test tests::parse_scalar_true_infers_bool ... ok +test tests::resolve_setup_hosts_auto_both_present ... ok +test tests::resolve_setup_hosts_auto_only_claude_present ... ok +test tests::resolve_setup_hosts_auto_only_codex_present ... ok +test tests::resolve_setup_hosts_bad_host_arg_returns_error ... ok +test tests::resolve_setup_hosts_explicit_both ... ok +test tests::resolve_setup_hosts_explicit_claude_code ... ok +test tests::resolve_setup_hosts_neither_present_non_tty_defaults_claude ... ok +test tests::resolve_setup_hosts_neither_present_tty_scripted_both ... ok +test tests::resolve_setup_hosts_neither_present_tty_scripted_codex ... ok +test tests::roundtrip_invalid_type_rejected_by_validation ... ok +test tests::roundtrip_set_embedder_enabled_false ... ok +test distiller::tests::resolve_distiller_openai_workspace ... ok +test tests::select_both_keep_protects_even_old_runs ... ok +test tests::select_both_older_than_and_keep ... ok +test tests::select_empty_runs_list ... ok +test tests::select_keep_all_protected ... ok +test tests::select_keep_only ... ok +test tests::select_neither_flag_selects_nothing ... ok +test tests::select_older_than_exact_boundary ... ok +test tests::select_older_than_only ... ok +test doctor::tests::ambient_collect_handles_non_git_dir_gracefully ... ok +test tests::served_event_payload_always_includes_query_hash ... ok +test tests::served_event_payload_has_required_fields ... ok +test tests::served_event_payload_hash_is_stable_for_same_query ... ok +test tests::served_event_payload_includes_raw_query_when_store_queries_true ... ok +test tests::served_event_payload_includes_session_id_when_present ... ok +test tests::served_event_payload_omits_session_id_when_absent ... ok +test tests::set_toml_edit_path_preserves_comments_and_unknown_keys ... ok +test tests::set_toml_path_creates_intermediate_tables ... ok +test tests::set_toml_path_replaces_existing_bool ... ok +test tests::set_toml_path_replaces_existing_integer ... ok +test tests::self_check_sees_installed_after_plugin_install ... ok +test tests::stop_cue_suppressed_when_distiller_enabled ... ok +test tests::stop_harvest_cue_blocks_so_it_reaches_the_model ... ok +test tests::stop_hook_outputs_are_valid_json_objects ... ok +test tests::stop_hook_with_savings_outputs_are_valid_json_objects ... ok +test tests::stop_lessons_recorded_pluralizes ... ok +test tests::stop_lessons_recorded_with_savings_appends_sentence ... ok +test tests::stop_lessons_recorded_without_savings_unchanged ... ok +test tests::stop_no_lessons_with_savings_appends_sentence ... ok +test tests::stop_no_lessons_without_savings_unchanged ... ok +test tests::ulid_timestamp_ms_known_ulid ... ok +test tests::ulid_timestamp_ms_non_ulid ... ok +test tests::ulid_timestamp_ms_roundtrip ... ok +test tests::update_current_version_is_bare_semver ... ok +test tests::version_constant_contains_known_flavor ... ok +test tests::version_constant_starts_with_cargo_pkg_version ... ok +test tool_outcome::tests::a_cargo_compile_error_is_a_failure_and_reports_the_diagnostic ... ok +test tool_outcome::tests::a_failing_jest_run_is_a_failure ... ok +test tool_outcome::tests::a_failing_pytest_run_is_a_failure ... ok +test tool_outcome::tests::a_failing_rust_test_run_is_a_failure_with_a_signature ... ok +test tool_outcome::tests::a_nonzero_exit_still_borrows_the_toolchain_signature ... ok +test tool_outcome::tests::a_passing_jest_run_is_not_a_failure ... ok +test tool_outcome::tests::a_passing_pytest_run_is_not_a_failure ... ok +test tool_outcome::tests::a_passing_rust_test_run_is_not_a_failure ... ok +test tool_outcome::tests::a_test_named_error_handling_passing_is_not_a_failure ... ok +test tool_outcome::tests::an_explicit_success_marker_vetoes_the_substring_scan ... ok +test tool_outcome::tests::compiling_a_crate_named_error_chain_is_not_a_failure ... ok +test tool_outcome::tests::empty_output_without_an_exit_code_is_not_a_failure ... ok +test tool_outcome::tests::evidence_is_ordered_weakest_to_strongest ... ok +test tool_outcome::tests::exit_zero_beats_any_amount_of_scary_output ... ok +test tool_outcome::tests::go_test_failures_and_successes_are_distinguished ... ok +test tool_outcome::tests::nonzero_exit_is_a_failure_even_with_silent_output ... ok +test tool_outcome::tests::npm_and_make_failures_are_recognised ... ok +test tool_outcome::tests::tsc_diagnostics_are_recognised ... ok +test tool_outcome::tests::tsc_reporting_zero_errors_is_not_a_failure ... ok +test tool_outcome::tests::unstructured_failures_still_fall_through_to_the_substring_scan ... ok +test distiller::tests::distill_and_record_global_writes_to_user_brain ... ok +test tests::setup_init_and_install_claude_code_workspace ... ok +test distiller::tests::temporary_global_fallback_keeps_expiry_and_duplicates_do_not_renew_it ... ok +test update::tests::auto_flavor_falls_back_to_lean_for_intel_macos ... ok +test update::tests::checksum_manifest_parses_common_formats ... ok +test update::tests::error_message_does_not_say_elevated_shell ... ok +test update::tests::parse_locking_pids_excludes_current_pid ... ok +test update::tests::parse_locking_pids_extracts_matching_pid ... ok +test update::tests::parse_locking_pids_filters_different_path ... ok +test update::tests::parse_locking_pids_handles_multiple_rows ... ok +test update::tests::parse_locking_pids_returns_empty_on_empty_output ... ok +test update::tests::parse_locking_pids_returns_empty_on_header_only ... ok +test update::tests::preflight_empty_locking_list_returns_defer ... ok +test update::tests::preflight_force_flag_returns_defer_not_silent_stop ... ok +test update::tests::preflight_interactive_empty_line_returns_stop_default ... ok +test update::tests::preflight_interactive_n_returns_defer ... ok +test update::tests::preflight_interactive_no_returns_defer ... ok +test update::tests::preflight_interactive_yes_capital_returns_stop ... ok +test update::tests::preflight_interactive_yes_full_word_returns_stop ... ok +test update::tests::preflight_interactive_yes_returns_stop ... ok +test update::tests::preflight_multiple_procs_all_listed ... ok +test update::tests::preflight_non_tty_returns_defer ... ok +test update::tests::select_asset_matches_target_and_flavor ... ok +test update::tests::select_asset_requires_exact_project_release_asset ... ok +test update::tests::tier_interactive_choice_1_is_binary_only ... ok +test update::tests::tier_interactive_choice_2_is_with_plugins ... ok +test update::tests::tier_interactive_choice_3_empty_confirm_falls_back_to_with_plugins ... ok +test update::tests::tier_interactive_choice_3_with_correct_confirm_is_with_brains ... ok +test update::tests::tier_interactive_choice_3_wrong_confirm_falls_back_to_with_plugins ... ok +test update::tests::tier_interactive_delete_user_data_flag_preselects_3_and_needs_confirm ... ok +test update::tests::tier_interactive_empty_line_is_with_plugins ... ok +test update::tests::tier_interactive_keep_plugins_flag_preselects_1_on_empty_input ... ok +test update::tests::tier_interactive_unknown_choice_defaults_to_with_plugins ... ok +test update::tests::tier_non_interactive_default_is_with_plugins ... ok +test update::tests::tier_non_interactive_delete_user_data_is_with_brains ... ok +test update::tests::tier_non_interactive_keep_plugins_is_binary_only ... ok +test update::tests::tier_non_tty_without_yes_uses_flags ... ok +test update::tests::tier_ordering_is_correct ... ok +test update::tests::version_compare_handles_multi_digit_minor ... ok +test distiller::tests::temporary_proposal_keeps_expiry_on_acceptance_and_rebuild ... ok +test distiller::tests::temporary_user_brain_duplicates_do_not_renew_expiry ... ok +test tests::config_edit_with_broken_toml_returns_err ... ok +test tests::config_edit_with_valid_edit_is_accepted ... ok +test distiller::tests::distill_and_record_writes_to_a_temp_brain ... ok +test tests::config_set_and_get_integration ... ok +test tests::interactive_loop_accepts_rejects_and_skips_from_scripted_input ... ok +test doctor::tests::selftest_passes_on_healthy_setup ... ok +test embed_daemon::server::tests::retrieve_with_stub_reranker_reorders_and_caps ... ok +test skill_synth::tests::cited_ge3_candidate_to_install_provenance_round_trip ... ok +test tests::interactive_loop_quit_preserves_partial_decisions ... ok +test tests::run_abort_cli_stamps_terminal_kind ... ok +test tune_tests::brain_tune_dry_run_does_not_modify_config ... ok +test tune_tests::brain_tune_status_shows_zero_cases_when_empty ... ok +test tune_tests::fix3_apply_in_fixture_mode_leaves_config_untouched ... ok + +test result: ok. 284 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 8.90s + + Running tests\cli_smoke.rs (E:/Kimetsu/target\debug\deps\cli_smoke-47b17745ceeaf409.exe) + +running 30 tests +test concurrent_processes_lose_no_cites ... ignored, spawns many processes; run on demand +test brain_status_reports_the_free_tier_by_default ... ok +test a_passing_test_run_does_not_trigger_a_proactive_interruption ... ok +test configuring_a_cheap_model_resolves_to_the_deep_tier ... ok +test explicit_free_tier_overrides_a_configured_model ... ok +test kimetsu_brain_help_lists_brain_subcommands ... ok +test kimetsu_brain_insights_help_lists_args ... ok +test deep_without_a_model_downgrades_and_is_reported ... ok +test kimetsu_brain_memory_help_lists_v05_subcommands ... ok +test kimetsu_help_lists_top_level_subcommands ... ok +test kimetsu_uninstall_help_lists_confirmation_flags ... ok +test kimetsu_unknown_subcommand_exits_nonzero_with_helpful_message ... ok +test a_real_failure_surfaces_a_matching_memory ... ok +test kimetsu_update_help_lists_check_mode ... ok +test kimetsu_version_prints_a_version_string_and_exits_clean ... ok +test context_hook_suppressed_when_env_var_zero ... ok +test context_hook_miss_logs_context_served_event ... ok +test context_hook_warm_starts_even_on_a_short_prompt ... ok +test context_hook_frames_memory_as_a_prior_conclusion_not_ground_truth ... ok +test context_hook_without_the_flag_never_warm_starts ... ok +test as_of_reports_what_the_brain_believed_at_a_point_in_time ... ok +test brain_import_quarantines_by_source_and_honours_the_overrides ... ok +test context_hook_dates_and_orders_capsules_for_an_ordering_question ... ok +test the_injection_policy_starts_as_the_legacy_rule_and_records_its_decisions ... ok +test standing_preferences_reach_the_agent_without_being_retrieved ... ok +test context_hook_warm_starts_once_per_session ... ok +test hardening_free_hooks_never_cue_host_after_resolution_or_stop ... ok +test maintenance_runs_what_is_due_and_then_stops ... ok +test hardening_episode_cli_identity_and_archive_restore ... ok +test fusion_mode_is_wired_and_is_a_no_op_on_the_lean_path ... ok + +test result: ok. 29 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 10.10s + + Running unittests src\lib.rs (E:/Kimetsu/target\debug\deps\kimetsu_core-35aab1def8317b02.exe) + +running 57 tests +test clock::tests::now_is_strictly_increasing ... ok +test clock::tests::canonical_sorts_chronologically_and_breaks_ties_by_node ... ok +test clock::tests::observe_advances_past_far_future_remote ... ok +test clock::tests::parse_roundtrips_including_dotted_node ... ok +test config::tests::deep_without_a_model_downgrades_and_is_flagged ... ok +test config::tests::default_config_uses_config_version_not_schema_version ... ok +test config::tests::default_for_project_uses_the_benchmarked_backend ... ok +test config::tests::explicit_free_overrides_a_configured_model ... ok +test config::tests::f3_adaptive_budget_huge_size_clamped_to_run_cap ... ok +test config::tests::f3_adaptive_budget_is_sublinear ... ok +test config::tests::f3_adaptive_budget_respects_floor ... ok +test config::tests::f3_adaptive_budget_respects_run_cap ... ok +test config::tests::f3_adaptive_budget_typical_task_near_historical_default ... ok +test config::tests::f3_adaptive_budget_zero_size_returns_floor ... ok +test config::tests::f3b_default_for_project_uses_conservative_defaults ... ok +test config::tests::explicit_fact_guard_is_opt_in_and_round_trips ... ok +test config::tests::hardening_automatic_harvest_policy_matrix ... ok +test config::tests::embedder_survives_toml_round_trip ... ok +test config::tests::broker_v1_5_fields_round_trip_as_false ... ok +test config::tests::pre_s1_2_config_without_cheap_model_loads_cleanly ... ok +test config::tests::pre_v0_8_config_without_embedder_loads_with_default ... ok +test config::tests::pre_v1_5_config_without_price_per_mtok_loads_with_none ... ok +test config::tests::f3b_new_broker_fields_round_trip ... ok +test config::tests::retrieval_level_never_overrides_embedder_off_switch ... ok +test config::tests::f3b_proactive_prefetch_default_false_round_trips ... ok +test config::tests::retrieval_level_never_reenables_explicit_reranker_off ... ok +test config::tests::retrieval_level_resolves_embedder_and_reranker ... ok +test config::tests::missing_tier_field_loads_cleanly ... ok +test config::tests::rerank_cutoff_survives_configuration_roundtrip_and_rejects_invalid_values ... ok +test config::tests::s1_2_a_learning_distiller_back_compat ... ok +test config::tests::s1_2_b_cheap_model_takes_precedence ... ok +test config::tests::price_per_mtok_round_trips ... ok +test config::tests::s1_2_d_absent_disabled_returns_none ... ok +test config::tests::s3_default_for_project_sync_unconfigured ... ok +test config::tests::tier_auto_follows_the_legacy_distiller_alias ... ok +test config::tests::s3_pre_s3_config_without_sync_loads_cleanly ... ok +test config::tests::tier_auto_resolves_to_deep_when_a_model_is_configured ... ok +test config::tests::tier_defaults_to_free_without_a_model ... ok +test event::tests::origin_scope_empty_is_no_override ... ok +test config::tests::s1_2_c_ollama_default_base_url ... ok +test paths::tests::display_path_strips_extended_prefix ... ok +test event::tests::origin_scope_overrides_and_restores ... ok +test paths::tests::slug_is_filesystem_safe ... ok +test config::tests::s3_sync_section_round_trips ... ok +test paths::tests::user_cache_dir_for_falls_back_to_temp_when_no_home ... ok +test secret::tests::debug_format_never_includes_inner_value ... ok +test paths::tests::user_cache_dir_for_lands_under_user_home ... ok +test config::tests::w3_off_switch_fields_round_trip_as_false ... ok +test secret::tests::display_emits_redaction_marker ... ok +test paths::tests::pin_discover_to_root_skips_git_climb ... ok +test secret::tests::empty_and_len_helpers ... ok +test config::tests::tier_round_trips_and_auto_stays_unwritten ... ok +test secret::tests::expose_secret_returns_cleartext ... ok +test secret::tests::parent_struct_derive_debug_does_not_leak ... ok +test secret::tests::serialize_emits_redaction_marker ... ok +test config::tests::s5_1_storage_backend_round_trips ... ok +test paths::tests::validate_state_dir_rejects_symlinked_kimetsu_dir ... ok + +test result: ok. 57 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s + + Running unittests src\lib.rs (E:/Kimetsu/target\debug\deps\kimetsu_e2e-84ccffbea5cf0b75.exe) + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + + Running tests\citations.rs (E:/Kimetsu/target\debug\deps\citations-197c1a11846a1672.exe) + +running 2 tests +test cite_memory_tool_call_lands_in_report_context_with_turn_index ... ok +test cited_memory_earns_strong_signal_after_run_finished_projection ... ok + +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.98s + + Running tests\conflicts.rs (E:/Kimetsu/target\debug\deps\conflicts-9fc2df8bd04ef049.exe) + +running 2 tests +test list_and_resolve_conflict_wrappers_compose_against_a_real_project ... ok +test re_resolving_same_conflict_is_idempotent_through_project_wrapper ... ok + +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.31s + + Running tests\decay.rs (E:/Kimetsu/target\debug\deps\decay-75a255e08a1fbe62.exe) + +running 2 tests +test aged_cited_memory_ranks_below_recently_cited_under_default_half_life ... ok +test decay_can_be_disabled_via_broker_weights ... ok + +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.72s + + Running tests\golden_path.rs (E:/Kimetsu/target\debug\deps\golden_path-79592df7acb30cb7.exe) + +running 2 tests +test agent_loop_produces_structured_context_field ... ok +test agent_loop_completes_a_simple_scripted_run ... ok + +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.45s + + Running tests\insights.rs (E:/Kimetsu/target\debug\deps\insights-eb0536cc0d52b9fc.exe) + +running 4 tests +test insights_all_hits_gives_full_hit_rate_and_zero_skip_rate ... ok +test insights_all_skips_gives_zero_hit_rate_and_full_skip_rate ... ok +test insights_no_context_served_returns_zero_served_and_none_rates ... ok +test insights_hit_rate_reflects_seeded_context_served_events ... ok + +test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.39s + + Running tests\migration.rs (E:/Kimetsu/target\debug\deps\migration-e3b6fec65a0b8141.exe) + +running 1 test +test project_brain_v1_to_v2_migration_creates_backup_and_preserves_data ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.81s + + Running tests\pipeline_events_survive_rebuild.rs (E:/Kimetsu/target\debug\deps\pipeline_events_survive_rebuild-f75bc5ae05a30c91.exe) + +running 1 test +test agent_run_events_survive_rebuild_from_events_table ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.39s + + Running unittests src\lib.rs (E:/Kimetsu/target\debug\deps\kimetsu_remote-69c354a12d32479e.exe) + +running 33 tests +test auth::tests::debug_does_not_expose_tokens ... ok +test auth::tests::global_token_works_for_any_repo ... ok +test auth::tests::global_token_detection_distinguishes_per_repo_tokens ... ok +test auth::tests::missing_or_unknown_is_unauthorized ... ok +test auth::tests::per_repo_token_scoped ... ok +test auth::tests::user_for_token_uses_name_then_stable_anon_fingerprint ... ok +test git::tests::leaves_plain_urls_unchanged ... ok +test git::tests::redacts_credentials_in_git_urls ... ok +test ingest::tests::empty_file_is_ok ... ok +test ingest::tests::rejects_invalid_repo_keys ... ok +test ingest::tests::rejects_duplicate_canonical_repo_keys ... ok +test ingest::tests::parses_both_forms ... ok +test metrics::tests::counts_and_renders ... ok +test ratelimit::tests::burst_then_block_then_refill ... ok +test ratelimit::tests::disabled_always_allows ... ok +test ratelimit::tests::tokens_are_independent ... ok +test repo::tests::accepts_reasonable_ids ... ok +test repo::tests::rejects_traversal_and_separators ... ok +test repo::tests::resolved_root_stays_in_data_dir ... ok +test config::tests::duplicate_canonical_per_repo_tokens_fail ... ok +test app::tests::per_repo_token_wrong_repo_is_403 ... ok +test app::tests::healthz_needs_no_auth ... ok +test config::tests::per_repo_tokens_are_canonicalized ... ok +test app::tests::per_repo_token_cannot_write_shared_user_memory ... ok +test app::tests::missing_token_is_401 ... ok +test app::tests::metrics_endpoint_counts_outcomes ... ok +test app::tests::rate_limit_returns_429 ... ok +test app::tests::excluded_tool_call_errors ... ok +test app::tests::bearer_scheme_is_case_insensitive ... ok +test app::tests::initialize_advertises_protocol ... ok +test repo::tests::ensure_initialized_repairs_partial_state_dir ... ok +test app::tests::tools_list_filtered_to_remote_catalog ... ok +test app::tests::remote_write_is_attributed_to_the_token_user ... ok + +test result: ok. 33 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.70s + + Running unittests src\main.rs (E:/Kimetsu/target\debug\deps\kimetsu_remote-ffd0f62781b49c2c.exe) + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + + Running tests\http_roundtrip.rs (E:/Kimetsu/target\debug\deps\http_roundtrip-bcd2e27d23bee19a.exe) + +running 5 tests +test hardening_remote_reranker_empty_reply_obeys_final_budget ... ok +test hardening_remote_reranker_escaped_capsule_obeys_final_budget ... ok +test reranker_in_appstate_intercepts_brain_context ... ok +test record_then_context_round_trips ... ok +test two_repos_are_isolated ... ok + +test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.43s + + Running tests\org_brain.rs (E:/Kimetsu/target\debug\deps\org_brain-b8014038469356e8.exe) + +running 1 test +test org_brain_shares_global_user_but_not_project ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.58s + + Running tests\server_ingest.rs (E:/Kimetsu/target\debug\deps\server_ingest-0c980c63b0afd619.exe) + +running 1 test +test registered_repo_ingests_and_files_are_retrievable ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.56s + + Doc-tests kimetsu_agent + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + + Doc-tests kimetsu_brain + +running 2 tests +test crates\kimetsu-brain\src\packs.rs - packs::redact_context_suffix (line 92) ... ok +test crates\kimetsu-brain\src\packs.rs - packs::redact_tags_prefix (line 126) ... ok + +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.04s + +all doctests ran in 4.46s; merged doctests compilation took 4.20s + Doc-tests kimetsu_chat + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + + Doc-tests kimetsu_core + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + + Doc-tests kimetsu_e2e + +running 3 tests +test crates\kimetsu-e2e\src\lib.rs - (line 27) ... ignored +test crates\kimetsu-e2e\src\lib.rs - prelude (line 50) ... ignored +test crates\kimetsu-e2e\src\scripted_provider.rs - scripted_provider (line 8) ... ignored + +test result: ok. 0 passed; 0 failed; 3 ignored; 0 measured; 0 filtered out; finished in 0.00s + +all doctests ran in 1.65s; merged doctests compilation took 0.66s + Doc-tests kimetsu_remote + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + diff --git a/docs/audits/2026-09-07-structured-facts/checks/structured-remote-green.log b/docs/audits/2026-09-07-structured-facts/checks/structured-remote-green.log new file mode 100644 index 0000000..0e7b45a --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/checks/structured-remote-green.log @@ -0,0 +1,56 @@ + Compiling tokio v1.52.3 + Compiling tokio-util v0.7.18 + Compiling h2 v0.4.16 + Compiling hyper v1.9.0 + Compiling hyper-util v0.1.20 + Compiling tower v0.5.3 + Compiling tokio-native-tls v0.3.1 + Compiling tokio-rustls v0.26.4 + Compiling hyper-rustls v0.27.9 + Compiling hyper-tls v0.6.0 + Compiling tower-http v0.6.10 + Compiling aws-smithy-async v1.2.14 + Compiling aws-smithy-runtime-api v1.12.3 + Compiling reqwest v0.12.28 + Compiling ureq v3.3.0 + Compiling hf-hub v0.5.0 + Compiling anstyle-wincon v3.0.11 + Compiling kimetsu-core v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-core) + Compiling fastembed v5.13.4 + Compiling ignore v0.4.25 + Compiling aws-credential-types v1.2.14 + Compiling aws-smithy-http v0.63.6 + Compiling anstyle-query v1.1.5 + Compiling anstream v1.0.0 + Compiling aws-sigv4 v1.4.5 + Compiling kimetsu-brain v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-brain) + Compiling kimetsu-agent v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-agent) + Compiling clap_builder v4.6.0 + Compiling nu-ansi-term v0.50.3 + Compiling axum v0.7.9 + Compiling tracing-subscriber v0.3.23 + Compiling kimetsu-chat v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-chat) + Compiling clap v4.6.1 + Compiling kimetsu-remote v2.7.0 (E:\tmp\kimetsu-brain-hardening\crates\kimetsu-remote) +warning: linker stdout: Creando biblioteca E:\Kimetsu\target\debug\deps\http_roundtrip-9d3bb9a239dd3067.lib y objeto E:\Kimetsu\target\debug\deps\http_roundtrip-9d3bb9a239dd3067.exp + | + = note: `#[warn(linker_messages)]` on by default + +warning: `kimetsu-remote` (test "http_roundtrip") generated 1 warning +warning: linker stdout: Creando biblioteca E:\Kimetsu\target\debug\deps\kimetsu_remote.lib y objeto E:\Kimetsu\target\debug\deps\kimetsu_remote.exp + | + = note: `#[warn(linker_messages)]` on by default + +warning: `kimetsu-remote` (bin "kimetsu-remote") generated 1 warning + Finished `test` profile [unoptimized + debuginfo] target(s) in 3m 13s + Running tests\http_roundtrip.rs (E:/Kimetsu/target\debug\deps\http_roundtrip-9d3bb9a239dd3067.exe) + +running 5 tests +test hardening_remote_reranker_empty_reply_obeys_final_budget ... ok +test hardening_remote_reranker_escaped_capsule_obeys_final_budget ... ok +test reranker_in_appstate_intercepts_brain_context ... ok +test record_then_context_round_trips ... ok +test two_repos_are_isolated ... ok + +test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.48s + diff --git a/docs/audits/2026-09-07-structured-facts/checks/structured-remote-isolated.log b/docs/audits/2026-09-07-structured-facts/checks/structured-remote-isolated.log new file mode 100644 index 0000000..c3bea59 --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/checks/structured-remote-isolated.log @@ -0,0 +1,17 @@ + +running 1 test + +thread 'record_then_context_round_trips' (20204) panicked at crates\kimetsu-remote\tests\http_roundtrip.rs:122:5: +assertion `left == right` failed: expected a hit: {"budget_tokens":6000,"capsule_count":0,"capsules":[],"excluded_count":0,"exposure_id":"01M1XYBZEWNRX059DBPD0PM7E2","ok":true,"partial_evidence":true,"skipped":true,"token_accounting":"utf8_byte_upper_bound","used_tokens":469,"warm_start":{"context":"## Repo context\nKey conventions and facts:\n[fact] [tags: alpha beta] The zephyrqux deployment requires flushing the wobblecache before restart"}} + left: Bool(true) + right: Bool(false) +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +test record_then_context_round_trips ... FAILED + +failures: + +failures: + record_then_context_round_trips + +test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 4 filtered out; finished in 0.55s + diff --git a/docs/audits/2026-09-07-structured-facts/independent-review.md b/docs/audits/2026-09-07-structured-facts/independent-review.md new file mode 100644 index 0000000..258f518 --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/independent-review.md @@ -0,0 +1,5 @@ +# Independent evidence review + +A read-only reviewer recomputed every repeat from raw observations and confirmed all counts, exact-match results, mismatch patterns, ranking/metadata stability, source-handle visibility, nearest-rank p95 and mean response bytes. The 80% reduction in unwanted injections, +2.6534% p95 and +6.1542% response bytes agree with raw results. The six compound-question omissions per repeat already lacked port evidence in the baseline; the Unknown-subject wrong-scope results also persist from baseline. + +The review found no substantive math or evidence errors. One wording correction was applied: displayed numeric values may normalize casing/whitespace; exact source excerpts are preserved. No code, parameters or fixture expectations changed after benchmark inference. diff --git a/docs/audits/2026-09-07-structured-facts/manifest.json b/docs/audits/2026-09-07-structured-facts/manifest.json new file mode 100644 index 0000000..0edc40e --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/manifest.json @@ -0,0 +1,225 @@ +{ + "schema_version": 1, + "files": { + ".gitattributes": { + "sha256": "50524bc54be6323af4266e11f2ca0b4d574bad39253c8c5293eca7d3173fa822", + "bytes": 43 + }, + "answerability-gold.json": { + "sha256": "d4d8b3f84282ea52515a1e9dc4fe7fe20f1e26ef45c4905cefcd984db0540445", + "bytes": 8989 + }, + "check-delivery.py": { + "sha256": "e455abe2ee5f11b9b2d180ab688a1fde15364527f394770bd142384035cfb5b9", + "bytes": 4714 + }, + "checks/conflict-carry-cli-green.log": { + "sha256": "64c0fd5960d17d4b8219df74b37a0357bc75b1fc81417afff3e43e3324a57703", + "bytes": 1303 + }, + "checks/conflict-carry-wire-red.log": { + "sha256": "baa1d76162e3383da1a833efe27b93b5b9598d4b5c48fa52163aea90b41809dc", + "bytes": 1466 + }, + "checks/deferred-fact-budget-green.log": { + "sha256": "05555c75b4357534b61a6fa0fb2be2438f6a67095fe2c938668990d2ce38e5c5", + "bytes": 799 + }, + "checks/deferred-fact-budget-red.log": { + "sha256": "c7e26f1fb3ea57a5831bad09ad90e7aa6abb3f20e5e588243a3d38adb087fe3f", + "bytes": 1604 + }, + "checks/deferred-fact-focused-green.log": { + "sha256": "bbd5f31613924526b89913cbe2b68809c996284cb75bde7e9503c0b6fdeea653", + "bytes": 3641 + }, + "checks/deferred-hook-cli-green.log": { + "sha256": "73835c53c0163c0f1bac2b7a4a769c66910a154bdbed90f9066a77f12e59796e", + "bytes": 1303 + }, + "checks/fact-benchmark-observation-red.log": { + "sha256": "edeca01c7f38feb0bb60af1152b668831ef6c8f7f397f4b35c58d94582daa28d", + "bytes": 1211 + }, + "checks/structured-facts-agent-ingress-red.log": { + "sha256": "5306257839d87acf39fa2d01588d6cb569b411ba2e3865fee5e89a1555c5c811", + "bytes": 937 + }, + "checks/structured-facts-benchmark-tests.log": { + "sha256": "e53a531b18da32b8505bd16c8c81901f18b01e02bf53fb7c65e3866b546e5349", + "bytes": 11215 + }, + "checks/structured-facts-delivery-probes.log": { + "sha256": "ca2dc697f5c526a4149ea1e99abf63dc4a99c9d31c4e5674966f2c33fe621f2b", + "bytes": 4014 + }, + "checks/structured-facts-harness-build.log": { + "sha256": "2e8a9c16a767f498e669855e703cab4b9a69d833516cfba04b97a89eaff7455a", + "bytes": 403 + }, + "checks/structured-facts-python-tests.log": { + "sha256": "3dcf6142fd395106e5f2689586a3535cdaa7e0ac6c5e662e3a893f05d30e9aa9", + "bytes": 344 + }, + "checks/structured-facts-release.log": { + "sha256": "e5424ce3e2ef14a6d112de5818a56be4ffb52673a29cc03970af0d69ddaa921c", + "bytes": 759 + }, + "checks/structured-facts-workspace.log": { + "sha256": "12f47e26ee347c35dd835c0336a7c8668807f468342df1f955b591f8b4dad851", + "bytes": 116471 + }, + "checks/structured-remote-green.log": { + "sha256": "b008f218ce2cf1889211058e216d107b9aaae05c979b7157f868fd050beb18a3", + "bytes": 2567 + }, + "checks/structured-remote-isolated.log": { + "sha256": "124515e93f0f9b45ff7f5cc56c896b82c6a1d49a3a545bba03343c0ac38a4c7e", + "bytes": 920 + }, + "independent-review.md": { + "sha256": "4ee686c75e33217c4d4b75e77a8e9b60aab136c1ff6b47eabbe6c4bf3b3fc6b4", + "bytes": 801 + }, + "provenance.json": { + "sha256": "2a995f34b9cd5704ceee2a6ad823c2df24e59e6f09a9e15664d5fe9fbf164080", + "bytes": 1120 + }, + "results/answerability-regression/1-baseline.json": { + "sha256": "a94a2bf6653689314cac71b5054e7f65dd86ac6ed5bff0d4ad648a57a71ceb15", + "bytes": 39246 + }, + "results/answerability-regression/1-baseline.stderr.log": { + "sha256": "6b8b720b7616823615c0a7bb355a0cd71304a690beb2d1508742e2e1b11cee2d", + "bytes": 601 + }, + "results/answerability-regression/1-baseline.stdout.log": { + "sha256": "2c92756749a844d23ae967eb57f6059c2bddd8f366ab842136f6cb39ddbd477d", + "bytes": 39160 + }, + "results/answerability-regression/1-candidate.json": { + "sha256": "39c5ca344ea7c8bd1ba3352ac16ffe7663ff21094359bd9a3c4c8bed9a1e676f", + "bytes": 43171 + }, + "results/answerability-regression/1-candidate.stderr.log": { + "sha256": "871037faa5953961155a5f39d80379744960c82a4195629b6257d756d606d92f", + "bytes": 601 + }, + "results/answerability-regression/1-candidate.stdout.log": { + "sha256": "e3ae24d979afaf51c7016d982cdd017595a24ab5ba0e7517218a40e8c4130b6c", + "bytes": 43085 + }, + "results/answerability-regression/comparison.json": { + "sha256": "7333dc91520b562f9b684009d199f67753e55fcae32e5ffa03d17d9d1e958a04", + "bytes": 5820 + }, + "results/answerability-regression/comparison.md": { + "sha256": "bc7feb6e3987ffaa315e2ad8ab245f02db73e8b1cb1f184126df1f8d2e3cbea4", + "bytes": 1380 + }, + "results/development/1-baseline.json": { + "sha256": "273d3cffb9aaa38ce2c700c2397e277665ef1c0749705ff596a33dad86375bd3", + "bytes": 397044 + }, + "results/development/1-baseline.stderr.log": { + "sha256": "7b9b7e0628645cad09f96ca063ba03547633a9b0309112148fb903ffbc20c0d9", + "bytes": 381 + }, + "results/development/1-baseline.stdout.log": { + "sha256": "af37f32e8d12e3220494e086b043c47af3063fe49d736bad73c102c65bb78fb1", + "bytes": 396459 + }, + "results/development/1-candidate.json": { + "sha256": "371e0825236f46b1e3b17e8cc2cb7a3f48fea356c242e1dc6e643643385fe890", + "bytes": 397165 + }, + "results/development/1-candidate.stderr.log": { + "sha256": "575044083af1243419cfe2d775ca063a3e05a0702207525ed003c80866c1e2c2", + "bytes": 381 + }, + "results/development/1-candidate.stdout.log": { + "sha256": "bef76104675eb380b8809887ca1c76854b065de3ac4e8d9d35b4c7c636c76581", + "bytes": 396580 + }, + "results/development/comparison.json": { + "sha256": "a8e86236f433475603f2cfb323c076a27188c8ed2e28ae25d8943d6a60d5bb81", + "bytes": 5795 + }, + "results/development/comparison.md": { + "sha256": "9580955c34b6c77aa215f6fb5b613f173da2ffc8ee53eac3c62baf1a80c6f25b", + "bytes": 1374 + }, + "results/validation/1-baseline.json": { + "sha256": "d60fd8cdcb6b6a7d2a479ccd4331c4e982850f661d3604ea63827c71a0fec272", + "bytes": 55254 + }, + "results/validation/1-baseline.stderr.log": { + "sha256": "06d8e266d13b3cf0108c4573ae2c857c95b2fd029e077a774794362d89941c28", + "bytes": 797 + }, + "results/validation/1-baseline.stdout.log": { + "sha256": "4f1e22fed5439073c1d4bbd6eccea9ba3b62fe588338dc07d6090952d7d369ea", + "bytes": 55256 + }, + "results/validation/1-candidate.json": { + "sha256": "869fb7227ab9dd77948a45d7e1bcf6ffcb437c72b1e6f3ac7c77f66973d3741b", + "bytes": 59428 + }, + "results/validation/1-candidate.stderr.log": { + "sha256": "695fdcf2b75fcd2c9af4c96463c4560b9ab171fb0b6ca587c65bcbe1090359e9", + "bytes": 797 + }, + "results/validation/1-candidate.stdout.log": { + "sha256": "55dc4b69a602a87d6cb4f7d41a2f8581e03f8404eee3d92fdceaba98acd278da", + "bytes": 59430 + }, + "results/validation/2-baseline.json": { + "sha256": "86fb9228d52135a157a56cba275065136aaca1d61ffa419296b4a37f4668fd62", + "bytes": 55143 + }, + "results/validation/2-baseline.stderr.log": { + "sha256": "08ddfe6c1d3ad8c7d0c75c8010d35a7298638a4e43a76917260289cff9b6c1dc", + "bytes": 797 + }, + "results/validation/2-baseline.stdout.log": { + "sha256": "97b963b56c82ef4ea430aa797ca23097a5d322b0f214f59a9fd1d679e14d570d", + "bytes": 55145 + }, + "results/validation/2-candidate.json": { + "sha256": "9ee37b8a5f6d45a539a2212f0f6f51a9bfa2f0e4a5f01fd7fe0988261fb490cb", + "bytes": 59541 + }, + "results/validation/2-candidate.stderr.log": { + "sha256": "ec97e51b11c571b6e3fbeec2b6c484b7d1d7fc7745fbd8232cd7e4da628a1419", + "bytes": 797 + }, + "results/validation/2-candidate.stdout.log": { + "sha256": "78525d1ad106d64d9d76d8b2999f84ca849e1716dc5e753188819c92cac8a7a8", + "bytes": 59543 + }, + "results/validation/comparison.json": { + "sha256": "150395414c132785e297061064bb110049e56016a140448b6f718037d11ec298", + "bytes": 7027 + }, + "results/validation/comparison.md": { + "sha256": "d75c099e661e3d480dff1c05e61cfb398aaff40fe47a4a4e02b9d349db4c2369", + "bytes": 1380 + }, + "run-comparisons.ps1": { + "sha256": "4173a91415f874843dd7cba8317ede14371c5c6c48955158234604dad120be04", + "bytes": 2736 + }, + "summarize.py": { + "sha256": "ad2b6412171faa706493cae612dccbe50119350f0bce82eeaf2ed34e21e907ab", + "bytes": 5139 + }, + "summary.json": { + "sha256": "627308ce1baf9c4106157461c7b0929e3666382bec80c9066e344d036d1530df", + "bytes": 56387 + }, + "validation-frozen.json": { + "sha256": "6b90f328e989fa0addf7c9a6d2468114fa1b233800f6e979a83830250dd4e469", + "bytes": 16318 + } + } +} diff --git a/docs/audits/2026-09-07-structured-facts/provenance.json b/docs/audits/2026-09-07-structured-facts/provenance.json new file mode 100644 index 0000000..13c4844 --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/provenance.json @@ -0,0 +1,31 @@ +{ + "main_commit": "3ae8329c43660ada6e9cce4502c4ca89002bbcc4", + "benchmark_commit": "2c74dad135d3fe611ada259e9f94cf229bacb5b7", + "schema_version": 15, + "guard_default": false, + "installed": false, + "verification": { + "workspace_passed": 1470, + "workspace_ignored": 6, + "benchmark_rust_passed": 132, + "benchmark_python_passed": 18, + "delivery_probes_passed": 6 + }, + "binaries": { + "baseline_binary": { + "path": "E:\\tmp\\kimetsu-brain-hardening\\tmp-tests\\kimetsu-answerability-candidate.exe", + "sha256": "405d3483fe320e76b0ec776bf9ada3b7771852b04a73f70f3b5da377a43d31c3", + "bytes": 47151104 + }, + "candidate_binary": { + "path": "E:\\tmp\\kimetsu-brain-hardening\\tmp-tests\\kimetsu-structured-facts-candidate.exe", + "sha256": "b5672cfed1da5bbd03fdbc20b954582ad5216f4fb579c8e463914c0839126ee7", + "bytes": 47382016 + }, + "harness_binary": { + "path": "E:\\Kimetsu\\bench\\target\\release\\kbench.exe", + "sha256": "5ba5065f9aaa28ced75a091bb43e01c8b995751e0e5cd3f9842ac6c822e844ff", + "bytes": 9405952 + } + } +} diff --git a/docs/audits/2026-09-07-structured-facts/results/answerability-regression/1-baseline.json b/docs/audits/2026-09-07-structured-facts/results/answerability-regression/1-baseline.json new file mode 100644 index 0000000..3cd2019 --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/results/answerability-regression/1-baseline.json @@ -0,0 +1,1101 @@ +{ + "generated_at": "2026-09-07T13:23:34.8173369Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-answerability\\validation-frozen.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "What is the Orchid gateway timeout?", + "ranked": [ + "timeout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0JZJXQSGP3C4KR2QA0BX0", + "id": "01M1Y0K21T5Q9T0RCJZ2XK133X", + "kind": "memory", + "score": 0.999908208847046, + "summary": "project:fact - Orchid gateway timeout is 45 seconds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2484.3363, + "first_query": true, + "server_startup_ms": 73.2588, + "model_text_bytes": 426, + "mcp_result_bytes": 507, + "wire_bytes": 542, + "reported_used_tokens": 507, + "working_set_bytes": 634064896, + "peak_working_set_bytes": 685092864 + }, + { + "query": "How many retries does the Orchid client use?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0JZKAY86MT9YJSPHR3CTJ", + "id": "01M1Y0K2DJ3VYKSRRFV7KXEZHZ", + "kind": "memory", + "score": 0.9976400136947632, + "summary": "project:fact - Orchid client retry count is 5." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 338.1447, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 420, + "mcp_result_bytes": 501, + "wire_bytes": 536, + "reported_used_tokens": 501, + "working_set_bytes": 636198912, + "peak_working_set_bytes": 685092864 + }, + { + "query": "What is the Orchid worker memory limit?", + "ranked": [ + "memory" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0JZKJARQ3SZ39YN5DZZVD", + "id": "01M1Y0K2R9G1X395T0S76M4FBT", + "kind": "memory", + "score": 0.9999785423278807, + "summary": "project:fact - Orchid worker memory limit is 768 MiB." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 346.0867, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 543, + "reported_used_tokens": 508, + "working_set_bytes": 641261568, + "peak_working_set_bytes": 685092864 + }, + { + "query": "What version does the Orchid worker run?", + "ranked": [ + "version" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0JZKVRVSCQVVH74Z6QYK2", + "id": "01M1Y0K337G9PNA66EQ59V01S1", + "kind": "memory", + "score": 0.9998592138290404, + "summary": "project:fact - Orchid worker version 8.2 is installed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 352.0508, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 543, + "reported_used_tokens": 508, + "working_set_bytes": 641449984, + "peak_working_set_bytes": 685092864 + }, + { + "query": "What is `storage.page_bytes` in Orchid?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0JZM4H6BGT7KXQPAGGAYA", + "id": "01M1Y0K3E6KRA1GPJG6N6YY3JP", + "kind": "memory", + "score": 0.9999752044677734, + "summary": "project:fact - Orchid storage.page_bytes = 8192." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 351.87239999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 422, + "mcp_result_bytes": 503, + "wire_bytes": 538, + "reported_used_tokens": 503, + "working_set_bytes": 641900544, + "peak_working_set_bytes": 685092864 + }, + { + "query": "What password does the Orchid gateway require?", + "ranked": [ + "password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0JZME5NPAR4XDN8NBJ15H", + "id": "01M1Y0K3SC5FNCAXZGQ0CD3C2F", + "kind": "memory", + "score": 0.999871015548706, + "summary": "project:fact - No password is required for the Orchid gateway." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 355.4096, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 435, + "mcp_result_bytes": 516, + "wire_bytes": 551, + "reported_used_tokens": 516, + "working_set_bytes": 642048000, + "peak_working_set_bytes": 685092864 + }, + { + "query": "How long are Orchid backups retained?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0JZMRCNGWMH0JM0M39GBE", + "id": "01M1Y0K447KN2DHJ0R0PDG64MR", + "kind": "memory", + "score": 0.9999786615371704, + "summary": "project:fact - Orchid backups are retained for 36 hours." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 347.1146, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 429, + "mcp_result_bytes": 510, + "wire_bytes": 545, + "reported_used_tokens": 510, + "working_set_bytes": 642461696, + "peak_working_set_bytes": 685092864 + }, + { + "query": "Which files configure the port for Orchid?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0JZN02SWAQZ72BMA2FRWY", + "id": "01M1Y0K4F516MWQYVRA4QFJR2Z", + "kind": "memory", + "score": 0.9955846667289734, + "summary": "project:fact - Orchid listener binds TCP port 7321. Configure its port in listener.toml." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 358.921, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 462, + "mcp_result_bytes": 543, + "wire_bytes": 578, + "reported_used_tokens": 543, + "working_set_bytes": 642588672, + "peak_working_set_bytes": 685092864 + }, + { + "query": "What causes a version conflict in Orchid?", + "ranked": [ + "advice" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0JZNCET80GTZ11B61X4E9", + "id": "01M1Y0K4TABK0MR9ZAD8B2872X", + "kind": "memory", + "score": 0.9998210072517396, + "summary": "project:fact - Orchid version conflicts occur when lockfiles disagree. Regenerate the lockfile and check dependency constraints." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 354.5775, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 501, + "mcp_result_bytes": 582, + "wire_bytes": 618, + "reported_used_tokens": 582, + "working_set_bytes": 642719744, + "peak_working_set_bytes": 685092864 + }, + { + "query": "\u00bfQu\u00e9 versi\u00f3n usa el worker de Orchid?", + "ranked": [ + "version" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0JZKVRVSCQVVH74Z6QYK2", + "id": "01M1Y0K55NQTNHXW9RKA9A0J1R", + "kind": "memory", + "score": 0.999970316886902, + "summary": "project:fact - Orchid worker version 8.2 is installed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 370.8093, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 544, + "reported_used_tokens": 508, + "working_set_bytes": 642887680, + "peak_working_set_bytes": 685092864 + }, + { + "query": "\u00bfCu\u00e1nto tiempo se conservan las copias de seguridad de Orchid?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0JZMRCNGWMH0JM0M39GBE", + "id": "01M1Y0K5HJ2C2841XVE7638NSE", + "kind": "memory", + "score": 0.9995601773262024, + "summary": "project:fact - Orchid backups are retained for 36 hours." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 369.0906, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 429, + "mcp_result_bytes": 510, + "wire_bytes": 546, + "reported_used_tokens": 510, + "working_set_bytes": 643358720, + "peak_working_set_bytes": 685092864 + }, + { + "query": "What is the timeout in seconds for the gateway in Orchid?", + "ranked": [ + "timeout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0JZJXQSGP3C4KR2QA0BX0", + "id": "01M1Y0K5WN0W5WD4Q5WKMHV9BJ", + "kind": "memory", + "score": 0.9999822378158568, + "summary": "project:fact - Orchid gateway timeout is 45 seconds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 353.3127, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 426, + "mcp_result_bytes": 507, + "wire_bytes": 543, + "reported_used_tokens": 507, + "working_set_bytes": 643424256, + "peak_working_set_bytes": 685092864 + }, + { + "query": "What is the database timeout for Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 347.45140000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643514368, + "peak_working_set_bytes": 685092864 + }, + { + "query": "What encryption key does Orchid use?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 362.4002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643518464, + "peak_working_set_bytes": 685092864 + }, + { + "query": "What is `storage.cache_bytes` in Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 363.81629999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643575808, + "peak_working_set_bytes": 685092864 + }, + { + "query": "How many production replicas does Orchid run?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 346.3175, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643579904, + "peak_working_set_bytes": 685092864 + }, + { + "query": "What is the OpenSSL version for Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 383.8569, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643629056, + "peak_working_set_bytes": 685092864 + }, + { + "query": "What is the database password for Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 392.5581, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643686400, + "peak_working_set_bytes": 685092864 + }, + { + "query": "How long are logs retained for Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 414.51460000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643792896, + "peak_working_set_bytes": 685092864 + }, + { + "query": "\u00bfQu\u00e9 contrase\u00f1a usa la base de datos de Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 367.7007, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643907584, + "peak_working_set_bytes": 685092864 + }, + { + "query": "\u00bfCu\u00e1ntas replicas de producci\u00f3n tiene Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 349.09299999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644038656, + "peak_working_set_bytes": 685092864 + }, + { + "query": "Which region hosts Orchid production?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 345.6126, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644087808, + "peak_working_set_bytes": 685092864 + } + ], + "id": "orchid-answerability-frozen", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.000 (n=10) positive-n=12 negative-n=10 (22 queries)" + }, + { + "observations": [ + { + "query": "What is the Quartz gateway timeout?", + "ranked": [ + "timeout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KBP35VFTRQBFRP7M8VY6", + "id": "01M1Y0KE2NRZCZDR26VTBXBW1H", + "kind": "memory", + "score": 0.9999661445617676, + "summary": "project:fact - Quartz gateway timeout is 45 seconds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2380.3179999999998, + "first_query": true, + "server_startup_ms": 74.9194, + "model_text_bytes": 426, + "mcp_result_bytes": 507, + "wire_bytes": 542, + "reported_used_tokens": 507, + "working_set_bytes": 636616704, + "peak_working_set_bytes": 684908544 + }, + { + "query": "How many retries does the Quartz client use?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KBPGMBZR9GD3DFY892N6", + "id": "01M1Y0KEDW84347E4RJBB5D6C5", + "kind": "memory", + "score": 0.9913354516029358, + "summary": "project:fact - Quartz client retry count is 5." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 345.7407, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 420, + "mcp_result_bytes": 501, + "wire_bytes": 536, + "reported_used_tokens": 501, + "working_set_bytes": 637116416, + "peak_working_set_bytes": 684908544 + }, + { + "query": "What is the Quartz worker memory limit?", + "ranked": [ + "memory" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KBPSRMF19JK653F40E1H", + "id": "01M1Y0KES81TPH2XAJ3XRFWM0F", + "kind": "memory", + "score": 0.9999793767929076, + "summary": "project:fact - Quartz worker memory limit is 768 MiB." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 375.5176, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 543, + "reported_used_tokens": 508, + "working_set_bytes": 642048000, + "peak_working_set_bytes": 684908544 + }, + { + "query": "What version does the Quartz worker run?", + "ranked": [ + "version" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KBQ3YMW97J2WPGTR6PZ7", + "id": "01M1Y0KF4K9AA6XARW7SSHX029", + "kind": "memory", + "score": 0.9998953342437744, + "summary": "project:fact - Quartz worker version 8.2 is installed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 349.4647, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 543, + "reported_used_tokens": 508, + "working_set_bytes": 642084864, + "peak_working_set_bytes": 684908544 + }, + { + "query": "What is `storage.page_bytes` in Quartz?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KBQC613F05C2ZR7C084G", + "id": "01M1Y0KFFNG8RYBDMP0WRCYF4H", + "kind": "memory", + "score": 0.9999722242355348, + "summary": "project:fact - Quartz storage.page_bytes = 8192." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.7609, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 422, + "mcp_result_bytes": 503, + "wire_bytes": 538, + "reported_used_tokens": 503, + "working_set_bytes": 644485120, + "peak_working_set_bytes": 684908544 + }, + { + "query": "What password does the Quartz gateway require?", + "ranked": [ + "password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KBQRXRSX788FC83VM0NV", + "id": "01M1Y0KFTMCXK6TWHMT7N6F21B", + "kind": "memory", + "score": 0.9998206496238708, + "summary": "project:fact - No password is required for the Quartz gateway." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 346.35880000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 436, + "mcp_result_bytes": 517, + "wire_bytes": 552, + "reported_used_tokens": 517, + "working_set_bytes": 644567040, + "peak_working_set_bytes": 684908544 + }, + { + "query": "How long are Quartz backups retained?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KBR1SZJ958NM6Q38TCRP", + "id": "01M1Y0KG5F8X2H4F197FP5F8SB", + "kind": "memory", + "score": 0.999981164932251, + "summary": "project:fact - Quartz backups are retained for 36 hours." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 347.0177, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 428, + "mcp_result_bytes": 509, + "wire_bytes": 544, + "reported_used_tokens": 509, + "working_set_bytes": 644894720, + "peak_working_set_bytes": 684908544 + }, + { + "query": "Which files configure the port for Quartz?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KBRA1RZX6GXARRQCGYMZ", + "id": "01M1Y0KGGPBM3HMQSKXT77VGFA", + "kind": "memory", + "score": 0.9969274401664734, + "summary": "project:fact - Quartz listener binds TCP port 8452. Configure its port in listener.toml." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 358.27979999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 462, + "mcp_result_bytes": 543, + "wire_bytes": 578, + "reported_used_tokens": 543, + "working_set_bytes": 644947968, + "peak_working_set_bytes": 684908544 + }, + { + "query": "What causes a version conflict in Quartz?", + "ranked": [ + "advice" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KBRPXB0VGABV8TVNAJVV", + "id": "01M1Y0KGVFA9DP267ZSRHW000S", + "kind": "memory", + "score": 0.9998512268066406, + "summary": "project:fact - Quartz version conflicts occur when lockfiles disagree. Regenerate the lockfile and check dependency constraints." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 409.1152, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 501, + "mcp_result_bytes": 582, + "wire_bytes": 618, + "reported_used_tokens": 582, + "working_set_bytes": 645013504, + "peak_working_set_bytes": 684908544 + }, + { + "query": "\u00bfQu\u00e9 versi\u00f3n usa el worker de Quartz?", + "ranked": [ + "version" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KBQ3YMW97J2WPGTR6PZ7", + "id": "01M1Y0KH98EKENW3HXEQAHRKFK", + "kind": "memory", + "score": 0.9999598264694214, + "summary": "project:fact - Quartz worker version 8.2 is installed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 394.2516, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 544, + "reported_used_tokens": 508, + "working_set_bytes": 645099520, + "peak_working_set_bytes": 684908544 + }, + { + "query": "\u00bfCu\u00e1nto tiempo se conservan las copias de seguridad de Quartz?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KBR1SZJ958NM6Q38TCRP", + "id": "01M1Y0KHNM2CQYNV92501DWW59", + "kind": "memory", + "score": 0.9996949434280396, + "summary": "project:fact - Quartz backups are retained for 36 hours." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 395.7953, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 429, + "mcp_result_bytes": 510, + "wire_bytes": 546, + "reported_used_tokens": 510, + "working_set_bytes": 645566464, + "peak_working_set_bytes": 684908544 + }, + { + "query": "What is the timeout in seconds for the gateway in Quartz?", + "ranked": [ + "timeout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KBP35VFTRQBFRP7M8VY6", + "id": "01M1Y0KJ3ZTY22Z2864G4959AZ", + "kind": "memory", + "score": 0.9999818801879884, + "summary": "project:fact - Quartz gateway timeout is 45 seconds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 445.4115, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 426, + "mcp_result_bytes": 507, + "wire_bytes": 543, + "reported_used_tokens": 507, + "working_set_bytes": 645640192, + "peak_working_set_bytes": 684908544 + }, + { + "query": "What is the database timeout for Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 353.13640000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645664768, + "peak_working_set_bytes": 684908544 + }, + { + "query": "What encryption key does Quartz use?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 346.48089999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645828608, + "peak_working_set_bytes": 684908544 + }, + { + "query": "What is `storage.cache_bytes` in Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 350.09220000000005, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645976064, + "peak_working_set_bytes": 684908544 + }, + { + "query": "How many production replicas does Quartz run?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 346.1329, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 646152192, + "peak_working_set_bytes": 684908544 + }, + { + "query": "What is the OpenSSL version for Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 357.39979999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 646160384, + "peak_working_set_bytes": 684908544 + }, + { + "query": "What is the database password for Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 345.7433, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 646168576, + "peak_working_set_bytes": 684908544 + }, + { + "query": "How long are logs retained for Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 357.19050000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 646172672, + "peak_working_set_bytes": 684908544 + }, + { + "query": "\u00bfQu\u00e9 contrase\u00f1a usa la base de datos de Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 357.78720000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 646217728, + "peak_working_set_bytes": 684908544 + }, + { + "query": "\u00bfCu\u00e1ntas replicas de producci\u00f3n tiene Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 389.2233, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 646283264, + "peak_working_set_bytes": 684908544 + }, + { + "query": "Which region hosts Quartz production?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 353.0308, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 646303744, + "peak_working_set_bytes": 684908544 + } + ], + "id": "quartz-answerability-frozen", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.000 (n=10) positive-n=12 negative-n=10 (22 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 2.0, + 2 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 1.0, + "n": 2, + "ci95": 0.0 + } + }, + "overall_index": 1.0, + "scenario_weighted_index": 1.0 +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-structured-facts/results/answerability-regression/1-baseline.stderr.log b/docs/audits/2026-09-07-structured-facts/results/answerability-regression/1-baseline.stderr.log new file mode 100644 index 0000000..382a4c6 --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/results/answerability-regression/1-baseline.stderr.log @@ -0,0 +1,6 @@ +brainbench: 2 scenario(s) to run + [1/2] orchid-answerability-frozen | dim=retrieval tier=hard ... + -> score=1.00 | positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.000 (n=10) positive-n=12 negative-n=10 (22 queries) + [2/2] quartz-answerability-frozen | dim=retrieval tier=hard ... + -> score=1.00 | positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.000 (n=10) positive-n=12 negative-n=10 (22 queries) +kbench brainbench: report saved -> E:\tmp\kimetsu-brain-hardening\bench\local\runs\brainbench\2026-09-07T13-23-34.8176877Z.json diff --git a/docs/audits/2026-09-07-structured-facts/results/answerability-regression/1-baseline.stdout.log b/docs/audits/2026-09-07-structured-facts/results/answerability-regression/1-baseline.stdout.log new file mode 100644 index 0000000..f4f4c56 --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/results/answerability-regression/1-baseline.stdout.log @@ -0,0 +1,1101 @@ +{ + "generated_at": "2026-09-07T13:23:34.8173369Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-answerability\\validation-frozen.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "What is the Orchid gateway timeout?", + "ranked": [ + "timeout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0JZJXQSGP3C4KR2QA0BX0", + "id": "01M1Y0K21T5Q9T0RCJZ2XK133X", + "kind": "memory", + "score": 0.999908208847046, + "summary": "project:fact - Orchid gateway timeout is 45 seconds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2484.3363, + "first_query": true, + "server_startup_ms": 73.2588, + "model_text_bytes": 426, + "mcp_result_bytes": 507, + "wire_bytes": 542, + "reported_used_tokens": 507, + "working_set_bytes": 634064896, + "peak_working_set_bytes": 685092864 + }, + { + "query": "How many retries does the Orchid client use?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0JZKAY86MT9YJSPHR3CTJ", + "id": "01M1Y0K2DJ3VYKSRRFV7KXEZHZ", + "kind": "memory", + "score": 0.9976400136947632, + "summary": "project:fact - Orchid client retry count is 5." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 338.1447, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 420, + "mcp_result_bytes": 501, + "wire_bytes": 536, + "reported_used_tokens": 501, + "working_set_bytes": 636198912, + "peak_working_set_bytes": 685092864 + }, + { + "query": "What is the Orchid worker memory limit?", + "ranked": [ + "memory" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0JZKJARQ3SZ39YN5DZZVD", + "id": "01M1Y0K2R9G1X395T0S76M4FBT", + "kind": "memory", + "score": 0.9999785423278807, + "summary": "project:fact - Orchid worker memory limit is 768 MiB." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 346.0867, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 543, + "reported_used_tokens": 508, + "working_set_bytes": 641261568, + "peak_working_set_bytes": 685092864 + }, + { + "query": "What version does the Orchid worker run?", + "ranked": [ + "version" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0JZKVRVSCQVVH74Z6QYK2", + "id": "01M1Y0K337G9PNA66EQ59V01S1", + "kind": "memory", + "score": 0.9998592138290404, + "summary": "project:fact - Orchid worker version 8.2 is installed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 352.0508, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 543, + "reported_used_tokens": 508, + "working_set_bytes": 641449984, + "peak_working_set_bytes": 685092864 + }, + { + "query": "What is `storage.page_bytes` in Orchid?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0JZM4H6BGT7KXQPAGGAYA", + "id": "01M1Y0K3E6KRA1GPJG6N6YY3JP", + "kind": "memory", + "score": 0.9999752044677734, + "summary": "project:fact - Orchid storage.page_bytes = 8192." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 351.87239999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 422, + "mcp_result_bytes": 503, + "wire_bytes": 538, + "reported_used_tokens": 503, + "working_set_bytes": 641900544, + "peak_working_set_bytes": 685092864 + }, + { + "query": "What password does the Orchid gateway require?", + "ranked": [ + "password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0JZME5NPAR4XDN8NBJ15H", + "id": "01M1Y0K3SC5FNCAXZGQ0CD3C2F", + "kind": "memory", + "score": 0.999871015548706, + "summary": "project:fact - No password is required for the Orchid gateway." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 355.4096, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 435, + "mcp_result_bytes": 516, + "wire_bytes": 551, + "reported_used_tokens": 516, + "working_set_bytes": 642048000, + "peak_working_set_bytes": 685092864 + }, + { + "query": "How long are Orchid backups retained?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0JZMRCNGWMH0JM0M39GBE", + "id": "01M1Y0K447KN2DHJ0R0PDG64MR", + "kind": "memory", + "score": 0.9999786615371704, + "summary": "project:fact - Orchid backups are retained for 36 hours." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 347.1146, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 429, + "mcp_result_bytes": 510, + "wire_bytes": 545, + "reported_used_tokens": 510, + "working_set_bytes": 642461696, + "peak_working_set_bytes": 685092864 + }, + { + "query": "Which files configure the port for Orchid?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0JZN02SWAQZ72BMA2FRWY", + "id": "01M1Y0K4F516MWQYVRA4QFJR2Z", + "kind": "memory", + "score": 0.9955846667289734, + "summary": "project:fact - Orchid listener binds TCP port 7321. Configure its port in listener.toml." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 358.921, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 462, + "mcp_result_bytes": 543, + "wire_bytes": 578, + "reported_used_tokens": 543, + "working_set_bytes": 642588672, + "peak_working_set_bytes": 685092864 + }, + { + "query": "What causes a version conflict in Orchid?", + "ranked": [ + "advice" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0JZNCET80GTZ11B61X4E9", + "id": "01M1Y0K4TABK0MR9ZAD8B2872X", + "kind": "memory", + "score": 0.9998210072517396, + "summary": "project:fact - Orchid version conflicts occur when lockfiles disagree. Regenerate the lockfile and check dependency constraints." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 354.5775, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 501, + "mcp_result_bytes": 582, + "wire_bytes": 618, + "reported_used_tokens": 582, + "working_set_bytes": 642719744, + "peak_working_set_bytes": 685092864 + }, + { + "query": "¿Qué versión usa el worker de Orchid?", + "ranked": [ + "version" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0JZKVRVSCQVVH74Z6QYK2", + "id": "01M1Y0K55NQTNHXW9RKA9A0J1R", + "kind": "memory", + "score": 0.999970316886902, + "summary": "project:fact - Orchid worker version 8.2 is installed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 370.8093, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 544, + "reported_used_tokens": 508, + "working_set_bytes": 642887680, + "peak_working_set_bytes": 685092864 + }, + { + "query": "¿Cuánto tiempo se conservan las copias de seguridad de Orchid?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0JZMRCNGWMH0JM0M39GBE", + "id": "01M1Y0K5HJ2C2841XVE7638NSE", + "kind": "memory", + "score": 0.9995601773262024, + "summary": "project:fact - Orchid backups are retained for 36 hours." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 369.0906, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 429, + "mcp_result_bytes": 510, + "wire_bytes": 546, + "reported_used_tokens": 510, + "working_set_bytes": 643358720, + "peak_working_set_bytes": 685092864 + }, + { + "query": "What is the timeout in seconds for the gateway in Orchid?", + "ranked": [ + "timeout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0JZJXQSGP3C4KR2QA0BX0", + "id": "01M1Y0K5WN0W5WD4Q5WKMHV9BJ", + "kind": "memory", + "score": 0.9999822378158568, + "summary": "project:fact - Orchid gateway timeout is 45 seconds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 353.3127, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 426, + "mcp_result_bytes": 507, + "wire_bytes": 543, + "reported_used_tokens": 507, + "working_set_bytes": 643424256, + "peak_working_set_bytes": 685092864 + }, + { + "query": "What is the database timeout for Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 347.45140000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643514368, + "peak_working_set_bytes": 685092864 + }, + { + "query": "What encryption key does Orchid use?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 362.4002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643518464, + "peak_working_set_bytes": 685092864 + }, + { + "query": "What is `storage.cache_bytes` in Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 363.81629999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643575808, + "peak_working_set_bytes": 685092864 + }, + { + "query": "How many production replicas does Orchid run?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 346.3175, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643579904, + "peak_working_set_bytes": 685092864 + }, + { + "query": "What is the OpenSSL version for Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 383.8569, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643629056, + "peak_working_set_bytes": 685092864 + }, + { + "query": "What is the database password for Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 392.5581, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643686400, + "peak_working_set_bytes": 685092864 + }, + { + "query": "How long are logs retained for Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 414.51460000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643792896, + "peak_working_set_bytes": 685092864 + }, + { + "query": "¿Qué contraseña usa la base de datos de Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 367.7007, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643907584, + "peak_working_set_bytes": 685092864 + }, + { + "query": "¿Cuántas replicas de producción tiene Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 349.09299999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644038656, + "peak_working_set_bytes": 685092864 + }, + { + "query": "Which region hosts Orchid production?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 345.6126, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644087808, + "peak_working_set_bytes": 685092864 + } + ], + "id": "orchid-answerability-frozen", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.000 (n=10) positive-n=12 negative-n=10 (22 queries)" + }, + { + "observations": [ + { + "query": "What is the Quartz gateway timeout?", + "ranked": [ + "timeout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KBP35VFTRQBFRP7M8VY6", + "id": "01M1Y0KE2NRZCZDR26VTBXBW1H", + "kind": "memory", + "score": 0.9999661445617676, + "summary": "project:fact - Quartz gateway timeout is 45 seconds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2380.3179999999998, + "first_query": true, + "server_startup_ms": 74.9194, + "model_text_bytes": 426, + "mcp_result_bytes": 507, + "wire_bytes": 542, + "reported_used_tokens": 507, + "working_set_bytes": 636616704, + "peak_working_set_bytes": 684908544 + }, + { + "query": "How many retries does the Quartz client use?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KBPGMBZR9GD3DFY892N6", + "id": "01M1Y0KEDW84347E4RJBB5D6C5", + "kind": "memory", + "score": 0.9913354516029358, + "summary": "project:fact - Quartz client retry count is 5." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 345.7407, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 420, + "mcp_result_bytes": 501, + "wire_bytes": 536, + "reported_used_tokens": 501, + "working_set_bytes": 637116416, + "peak_working_set_bytes": 684908544 + }, + { + "query": "What is the Quartz worker memory limit?", + "ranked": [ + "memory" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KBPSRMF19JK653F40E1H", + "id": "01M1Y0KES81TPH2XAJ3XRFWM0F", + "kind": "memory", + "score": 0.9999793767929076, + "summary": "project:fact - Quartz worker memory limit is 768 MiB." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 375.5176, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 543, + "reported_used_tokens": 508, + "working_set_bytes": 642048000, + "peak_working_set_bytes": 684908544 + }, + { + "query": "What version does the Quartz worker run?", + "ranked": [ + "version" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KBQ3YMW97J2WPGTR6PZ7", + "id": "01M1Y0KF4K9AA6XARW7SSHX029", + "kind": "memory", + "score": 0.9998953342437744, + "summary": "project:fact - Quartz worker version 8.2 is installed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 349.4647, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 543, + "reported_used_tokens": 508, + "working_set_bytes": 642084864, + "peak_working_set_bytes": 684908544 + }, + { + "query": "What is `storage.page_bytes` in Quartz?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KBQC613F05C2ZR7C084G", + "id": "01M1Y0KFFNG8RYBDMP0WRCYF4H", + "kind": "memory", + "score": 0.9999722242355348, + "summary": "project:fact - Quartz storage.page_bytes = 8192." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.7609, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 422, + "mcp_result_bytes": 503, + "wire_bytes": 538, + "reported_used_tokens": 503, + "working_set_bytes": 644485120, + "peak_working_set_bytes": 684908544 + }, + { + "query": "What password does the Quartz gateway require?", + "ranked": [ + "password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KBQRXRSX788FC83VM0NV", + "id": "01M1Y0KFTMCXK6TWHMT7N6F21B", + "kind": "memory", + "score": 0.9998206496238708, + "summary": "project:fact - No password is required for the Quartz gateway." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 346.35880000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 436, + "mcp_result_bytes": 517, + "wire_bytes": 552, + "reported_used_tokens": 517, + "working_set_bytes": 644567040, + "peak_working_set_bytes": 684908544 + }, + { + "query": "How long are Quartz backups retained?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KBR1SZJ958NM6Q38TCRP", + "id": "01M1Y0KG5F8X2H4F197FP5F8SB", + "kind": "memory", + "score": 0.999981164932251, + "summary": "project:fact - Quartz backups are retained for 36 hours." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 347.0177, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 428, + "mcp_result_bytes": 509, + "wire_bytes": 544, + "reported_used_tokens": 509, + "working_set_bytes": 644894720, + "peak_working_set_bytes": 684908544 + }, + { + "query": "Which files configure the port for Quartz?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KBRA1RZX6GXARRQCGYMZ", + "id": "01M1Y0KGGPBM3HMQSKXT77VGFA", + "kind": "memory", + "score": 0.9969274401664734, + "summary": "project:fact - Quartz listener binds TCP port 8452. Configure its port in listener.toml." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 358.27979999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 462, + "mcp_result_bytes": 543, + "wire_bytes": 578, + "reported_used_tokens": 543, + "working_set_bytes": 644947968, + "peak_working_set_bytes": 684908544 + }, + { + "query": "What causes a version conflict in Quartz?", + "ranked": [ + "advice" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KBRPXB0VGABV8TVNAJVV", + "id": "01M1Y0KGVFA9DP267ZSRHW000S", + "kind": "memory", + "score": 0.9998512268066406, + "summary": "project:fact - Quartz version conflicts occur when lockfiles disagree. Regenerate the lockfile and check dependency constraints." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 409.1152, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 501, + "mcp_result_bytes": 582, + "wire_bytes": 618, + "reported_used_tokens": 582, + "working_set_bytes": 645013504, + "peak_working_set_bytes": 684908544 + }, + { + "query": "¿Qué versión usa el worker de Quartz?", + "ranked": [ + "version" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KBQ3YMW97J2WPGTR6PZ7", + "id": "01M1Y0KH98EKENW3HXEQAHRKFK", + "kind": "memory", + "score": 0.9999598264694214, + "summary": "project:fact - Quartz worker version 8.2 is installed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 394.2516, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 544, + "reported_used_tokens": 508, + "working_set_bytes": 645099520, + "peak_working_set_bytes": 684908544 + }, + { + "query": "¿Cuánto tiempo se conservan las copias de seguridad de Quartz?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KBR1SZJ958NM6Q38TCRP", + "id": "01M1Y0KHNM2CQYNV92501DWW59", + "kind": "memory", + "score": 0.9996949434280396, + "summary": "project:fact - Quartz backups are retained for 36 hours." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 395.7953, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 429, + "mcp_result_bytes": 510, + "wire_bytes": 546, + "reported_used_tokens": 510, + "working_set_bytes": 645566464, + "peak_working_set_bytes": 684908544 + }, + { + "query": "What is the timeout in seconds for the gateway in Quartz?", + "ranked": [ + "timeout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KBP35VFTRQBFRP7M8VY6", + "id": "01M1Y0KJ3ZTY22Z2864G4959AZ", + "kind": "memory", + "score": 0.9999818801879884, + "summary": "project:fact - Quartz gateway timeout is 45 seconds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 445.4115, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 426, + "mcp_result_bytes": 507, + "wire_bytes": 543, + "reported_used_tokens": 507, + "working_set_bytes": 645640192, + "peak_working_set_bytes": 684908544 + }, + { + "query": "What is the database timeout for Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 353.13640000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645664768, + "peak_working_set_bytes": 684908544 + }, + { + "query": "What encryption key does Quartz use?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 346.48089999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645828608, + "peak_working_set_bytes": 684908544 + }, + { + "query": "What is `storage.cache_bytes` in Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 350.09220000000005, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645976064, + "peak_working_set_bytes": 684908544 + }, + { + "query": "How many production replicas does Quartz run?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 346.1329, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 646152192, + "peak_working_set_bytes": 684908544 + }, + { + "query": "What is the OpenSSL version for Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 357.39979999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 646160384, + "peak_working_set_bytes": 684908544 + }, + { + "query": "What is the database password for Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 345.7433, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 646168576, + "peak_working_set_bytes": 684908544 + }, + { + "query": "How long are logs retained for Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 357.19050000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 646172672, + "peak_working_set_bytes": 684908544 + }, + { + "query": "¿Qué contraseña usa la base de datos de Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 357.78720000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 646217728, + "peak_working_set_bytes": 684908544 + }, + { + "query": "¿Cuántas replicas de producción tiene Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 389.2233, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 646283264, + "peak_working_set_bytes": 684908544 + }, + { + "query": "Which region hosts Quartz production?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 353.0308, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 646303744, + "peak_working_set_bytes": 684908544 + } + ], + "id": "quartz-answerability-frozen", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.000 (n=10) positive-n=12 negative-n=10 (22 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 2.0, + 2 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 1.0, + "n": 2, + "ci95": 0.0 + } + }, + "overall_index": 1.0, + "scenario_weighted_index": 1.0 +} diff --git a/docs/audits/2026-09-07-structured-facts/results/answerability-regression/1-candidate.json b/docs/audits/2026-09-07-structured-facts/results/answerability-regression/1-candidate.json new file mode 100644 index 0000000..326522d --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/results/answerability-regression/1-candidate.json @@ -0,0 +1,1237 @@ +{ + "generated_at": "2026-09-07T13:23:58.317696Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-answerability\\validation-frozen.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "What is the Orchid gateway timeout?", + "ranked": [ + "timeout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KQCH3RETYTG59JCNR1KK", + "id": "01M1Y0KSYG55W6Q4DV48W4N4P0", + "kind": "memory", + "score": 0.999908208847046, + "summary": "project:fact - Orchid gateway timeout is 45 seconds." + } + ], + "answerability": { + "conflicting": [], + "environment": null, + "missing": [], + "status": "supported", + "subject": "orchid gateway", + "supported": [ + { + "attribute": "timeout", + "sources": [ + "memory:01M1Y0KQCH3RETYTG59JCNR1KK" + ], + "value": "45 seconds" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2447.6951, + "first_query": true, + "server_startup_ms": 71.7255, + "model_text_bytes": 648, + "mcp_result_bytes": 759, + "wire_bytes": 794, + "reported_used_tokens": 759, + "working_set_bytes": 635170816, + "peak_working_set_bytes": 684916736 + }, + { + "query": "How many retries does the Orchid client use?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KQD1TGW17QPPA766RVX7", + "id": "01M1Y0KT9GYE8K14G0B2NQQSWP", + "kind": "memory", + "score": 0.9976400136947632, + "summary": "project:fact - Orchid client retry count is 5." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 339.9678, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 420, + "mcp_result_bytes": 501, + "wire_bytes": 536, + "reported_used_tokens": 501, + "working_set_bytes": 637341696, + "peak_working_set_bytes": 684916736 + }, + { + "query": "What is the Orchid worker memory limit?", + "ranked": [ + "memory" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KQDA3PETEDPFWT0FKBM5", + "id": "01M1Y0KTMEG7QTTAZEV5X6NYGW", + "kind": "memory", + "score": 0.9999785423278807, + "summary": "project:fact - Orchid worker memory limit is 768 MiB." + } + ], + "answerability": { + "conflicting": [], + "environment": null, + "missing": [], + "status": "supported", + "subject": "orchid worker", + "supported": [ + { + "attribute": "memory_limit", + "sources": [ + "memory:01M1Y0KQDA3PETEDPFWT0FKBM5" + ], + "value": "768 mib" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 353.5858, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 650, + "mcp_result_bytes": 761, + "wire_bytes": 796, + "reported_used_tokens": 761, + "working_set_bytes": 642465792, + "peak_working_set_bytes": 684916736 + }, + { + "query": "What version does the Orchid worker run?", + "ranked": [ + "version" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KQDK7ZB06G0NP39MZJ8F", + "id": "01M1Y0KTZJKJQ02D0QFTHZEDCG", + "kind": "memory", + "score": 0.9998592138290404, + "summary": "project:fact - Orchid worker version 8.2 is installed." + } + ], + "answerability": { + "conflicting": [], + "environment": null, + "missing": [ + "version" + ], + "status": "missing", + "subject": "orchid worker", + "supported": [] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 363.6697, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 563, + "mcp_result_bytes": 664, + "wire_bytes": 699, + "reported_used_tokens": 664, + "working_set_bytes": 642523136, + "peak_working_set_bytes": 684916736 + }, + { + "query": "What is `storage.page_bytes` in Orchid?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KQDW0PQ1060C7M16EKFW", + "id": "01M1Y0KVBF298SYKRKXGGCN90G", + "kind": "memory", + "score": 0.9999752044677734, + "summary": "project:fact - Orchid storage.page_bytes = 8192." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 375.4471, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 422, + "mcp_result_bytes": 503, + "wire_bytes": 538, + "reported_used_tokens": 503, + "working_set_bytes": 642904064, + "peak_working_set_bytes": 684916736 + }, + { + "query": "What password does the Orchid gateway require?", + "ranked": [ + "password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KQE71VFXH0M77MCKCJBP", + "id": "01M1Y0KVPDG24XF1GX5SJG66NF", + "kind": "memory", + "score": 0.999871015548706, + "summary": "project:fact - No password is required for the Orchid gateway." + } + ], + "answerability": { + "conflicting": [], + "environment": null, + "missing": [], + "status": "supported", + "subject": "orchid gateway", + "supported": [ + { + "attribute": "password", + "sources": [ + "memory:01M1Y0KQE71VFXH0M77MCKCJBP" + ], + "value": "not required" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 348.4848, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 660, + "mcp_result_bytes": 771, + "wire_bytes": 806, + "reported_used_tokens": 771, + "working_set_bytes": 642981888, + "peak_working_set_bytes": 684916736 + }, + { + "query": "How long are Orchid backups retained?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KQEHJJVEER85HPN9HJH8", + "id": "01M1Y0KW1BM0BPRREK862JPZ8Y", + "kind": "memory", + "score": 0.9999786615371704, + "summary": "project:fact - Orchid backups are retained for 36 hours." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.7613, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 429, + "mcp_result_bytes": 510, + "wire_bytes": 545, + "reported_used_tokens": 510, + "working_set_bytes": 643313664, + "peak_working_set_bytes": 684916736 + }, + { + "query": "Which files configure the port for Orchid?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KQETSWKSZEJ1EZVZGHSB", + "id": "01M1Y0KWCJX6KA8X7FYG80CWZB", + "kind": "memory", + "score": 0.9955846667289734, + "summary": "project:fact - Orchid listener binds TCP port 7321. Configure its port in listener.toml." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 349.8548, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 462, + "mcp_result_bytes": 543, + "wire_bytes": 578, + "reported_used_tokens": 543, + "working_set_bytes": 643411968, + "peak_working_set_bytes": 684916736 + }, + { + "query": "What causes a version conflict in Orchid?", + "ranked": [ + "advice" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KQF6V84VGA5B5Q5B1WD0", + "id": "01M1Y0KWQDXG4KTJE0EAAYXBW8", + "kind": "memory", + "score": 0.9998210072517396, + "summary": "project:fact - Orchid version conflicts occur when lockfiles disagree. Regenerate the lockfile and check dependency constraints." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 353.7158, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 501, + "mcp_result_bytes": 582, + "wire_bytes": 618, + "reported_used_tokens": 582, + "working_set_bytes": 643493888, + "peak_working_set_bytes": 684916736 + }, + { + "query": "\u00bfQu\u00e9 versi\u00f3n usa el worker de Orchid?", + "ranked": [ + "version" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KQDK7ZB06G0NP39MZJ8F", + "id": "01M1Y0KX2J858SZ8MZHBS3FTAX", + "kind": "memory", + "score": 0.999970316886902, + "summary": "project:fact - Orchid worker version 8.2 is installed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 350.5779, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 544, + "reported_used_tokens": 508, + "working_set_bytes": 643604480, + "peak_working_set_bytes": 684916736 + }, + { + "query": "\u00bfCu\u00e1nto tiempo se conservan las copias de seguridad de Orchid?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KQEHJJVEER85HPN9HJH8", + "id": "01M1Y0KXDG67GQ0MYTHS99ANMZ", + "kind": "memory", + "score": 0.9995601773262024, + "summary": "project:fact - Orchid backups are retained for 36 hours." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.4911, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 429, + "mcp_result_bytes": 510, + "wire_bytes": 546, + "reported_used_tokens": 510, + "working_set_bytes": 644124672, + "peak_working_set_bytes": 684916736 + }, + { + "query": "What is the timeout in seconds for the gateway in Orchid?", + "ranked": [ + "timeout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KQCH3RETYTG59JCNR1KK", + "id": "01M1Y0KXRWQC6E5JD2B4CDNW8W", + "kind": "memory", + "score": 0.9999822378158568, + "summary": "project:fact - Orchid gateway timeout is 45 seconds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 360.088, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 426, + "mcp_result_bytes": 507, + "wire_bytes": 543, + "reported_used_tokens": 507, + "working_set_bytes": 644161536, + "peak_working_set_bytes": 684916736 + }, + { + "query": "What is the database timeout for Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 360.1187, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644263936, + "peak_working_set_bytes": 684916736 + }, + { + "query": "What encryption key does Orchid use?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": null, + "missing": [ + "encryption_key" + ], + "status": "missing", + "subject": "orchid", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 387.1739, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 362, + "mcp_result_bytes": 445, + "wire_bytes": 481, + "reported_used_tokens": 445, + "working_set_bytes": 644317184, + "peak_working_set_bytes": 684916736 + }, + { + "query": "What is `storage.cache_bytes` in Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 360.0568, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644333568, + "peak_working_set_bytes": 684916736 + }, + { + "query": "How many production replicas does Orchid run?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 352.1326, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644349952, + "peak_working_set_bytes": 684916736 + }, + { + "query": "What is the OpenSSL version for Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 349.5252, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644354048, + "peak_working_set_bytes": 684916736 + }, + { + "query": "What is the database password for Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 346.1954, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644423680, + "peak_working_set_bytes": 684916736 + }, + { + "query": "How long are logs retained for Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 362.8365, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644485120, + "peak_working_set_bytes": 684916736 + }, + { + "query": "\u00bfQu\u00e9 contrase\u00f1a usa la base de datos de Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 348.383, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644521984, + "peak_working_set_bytes": 684916736 + }, + { + "query": "\u00bfCu\u00e1ntas replicas de producci\u00f3n tiene Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 363.78450000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644653056, + "peak_working_set_bytes": 684916736 + }, + { + "query": "Which region hosts Orchid production?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 356.24, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644747264, + "peak_working_set_bytes": 684916736 + } + ], + "id": "orchid-answerability-frozen", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.000 (n=10) positive-n=12 negative-n=10 (22 queries)" + }, + { + "observations": [ + { + "query": "What is the Quartz gateway timeout?", + "ranked": [ + "timeout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0M2VP5VSANS7519PBJ1Q7", + "id": "01M1Y0M589PNER0CCBJA9VTFEE", + "kind": "memory", + "score": 0.9999661445617676, + "summary": "project:fact - Quartz gateway timeout is 45 seconds." + } + ], + "answerability": { + "conflicting": [], + "environment": null, + "missing": [], + "status": "supported", + "subject": "quartz gateway", + "supported": [ + { + "attribute": "timeout", + "sources": [ + "memory:01M1Y0M2VP5VSANS7519PBJ1Q7" + ], + "value": "45 seconds" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2346.6744999999996, + "first_query": true, + "server_startup_ms": 75.2195, + "model_text_bytes": 648, + "mcp_result_bytes": 759, + "wire_bytes": 794, + "reported_used_tokens": 759, + "working_set_bytes": 636063744, + "peak_working_set_bytes": 684875776 + }, + { + "query": "How many retries does the Quartz client use?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0M2W7CW9PKX1FGV1WQ55B", + "id": "01M1Y0M5K75YNR280RR5C13B5D", + "kind": "memory", + "score": 0.9913354516029358, + "summary": "project:fact - Quartz client retry count is 5." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 337.9665, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 420, + "mcp_result_bytes": 501, + "wire_bytes": 536, + "reported_used_tokens": 501, + "working_set_bytes": 636534784, + "peak_working_set_bytes": 684875776 + }, + { + "query": "What is the Quartz worker memory limit?", + "ranked": [ + "memory" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0M2WGHE3XV716SPJXH930", + "id": "01M1Y0M5XZ36KBHM3WM4XEPYV5", + "kind": "memory", + "score": 0.9999793767929076, + "summary": "project:fact - Quartz worker memory limit is 768 MiB." + } + ], + "answerability": { + "conflicting": [], + "environment": null, + "missing": [], + "status": "supported", + "subject": "quartz worker", + "supported": [ + { + "attribute": "memory_limit", + "sources": [ + "memory:01M1Y0M2WGHE3XV716SPJXH930" + ], + "value": "768 mib" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 347.45369999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 650, + "mcp_result_bytes": 761, + "wire_bytes": 796, + "reported_used_tokens": 761, + "working_set_bytes": 641527808, + "peak_working_set_bytes": 684875776 + }, + { + "query": "What version does the Quartz worker run?", + "ranked": [ + "version" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0M2WT695F5BY4AH0GFC69", + "id": "01M1Y0M697JZ27FGQD4G4T6WQF", + "kind": "memory", + "score": 0.9998953342437744, + "summary": "project:fact - Quartz worker version 8.2 is installed." + } + ], + "answerability": { + "conflicting": [], + "environment": null, + "missing": [ + "version" + ], + "status": "missing", + "subject": "quartz worker", + "supported": [] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 358.3561, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 563, + "mcp_result_bytes": 664, + "wire_bytes": 699, + "reported_used_tokens": 664, + "working_set_bytes": 641761280, + "peak_working_set_bytes": 684875776 + }, + { + "query": "What is `storage.page_bytes` in Quartz?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0M2X3XMB2AQCQQHD62QRP", + "id": "01M1Y0M6MA5157XVANR65QSFW6", + "kind": "memory", + "score": 0.9999722242355348, + "summary": "project:fact - Quartz storage.page_bytes = 8192." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 361.8323, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 422, + "mcp_result_bytes": 503, + "wire_bytes": 538, + "reported_used_tokens": 503, + "working_set_bytes": 644091904, + "peak_working_set_bytes": 684875776 + }, + { + "query": "What password does the Quartz gateway require?", + "ranked": [ + "password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0M2XD8QRYH86NNXSFJD3J", + "id": "01M1Y0M6ZJHDAPH2TFE73D7H5N", + "kind": "memory", + "score": 0.9998206496238708, + "summary": "project:fact - No password is required for the Quartz gateway." + } + ], + "answerability": { + "conflicting": [], + "environment": null, + "missing": [], + "status": "supported", + "subject": "quartz gateway", + "supported": [ + { + "attribute": "password", + "sources": [ + "memory:01M1Y0M2XD8QRYH86NNXSFJD3J" + ], + "value": "not required" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 353.576, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 661, + "mcp_result_bytes": 772, + "wire_bytes": 807, + "reported_used_tokens": 772, + "working_set_bytes": 644218880, + "peak_working_set_bytes": 684875776 + }, + { + "query": "How long are Quartz backups retained?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0M2XPVZMY3GF0BZ6JV0R4", + "id": "01M1Y0M7BBKZP2JZBMFPN0C9ND", + "kind": "memory", + "score": 0.999981164932251, + "summary": "project:fact - Quartz backups are retained for 36 hours." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 380.6524, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 428, + "mcp_result_bytes": 509, + "wire_bytes": 544, + "reported_used_tokens": 509, + "working_set_bytes": 644554752, + "peak_working_set_bytes": 684875776 + }, + { + "query": "Which files configure the port for Quartz?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0M2XZG1RCERMMA78EJ2FY", + "id": "01M1Y0M7P96CDV0AJTRWJ0QH9T", + "kind": "memory", + "score": 0.9969274401664734, + "summary": "project:fact - Quartz listener binds TCP port 8452. Configure its port in listener.toml." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 346.0613, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 462, + "mcp_result_bytes": 543, + "wire_bytes": 578, + "reported_used_tokens": 543, + "working_set_bytes": 644624384, + "peak_working_set_bytes": 684875776 + }, + { + "query": "What causes a version conflict in Quartz?", + "ranked": [ + "advice" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0M2YA0Y903D4K3MDB7K67", + "id": "01M1Y0M8121R0QSXQQ9E4RTKF4", + "kind": "memory", + "score": 0.9998512268066406, + "summary": "project:fact - Quartz version conflicts occur when lockfiles disagree. Regenerate the lockfile and check dependency constraints." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 348.21180000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 501, + "mcp_result_bytes": 582, + "wire_bytes": 618, + "reported_used_tokens": 582, + "working_set_bytes": 644669440, + "peak_working_set_bytes": 684875776 + }, + { + "query": "\u00bfQu\u00e9 versi\u00f3n usa el worker de Quartz?", + "ranked": [ + "version" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0M2WT695F5BY4AH0GFC69", + "id": "01M1Y0M8C0XP7KF1AK9XSSKDZP", + "kind": "memory", + "score": 0.9999598264694214, + "summary": "project:fact - Quartz worker version 8.2 is installed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 351.8111, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 544, + "reported_used_tokens": 508, + "working_set_bytes": 644763648, + "peak_working_set_bytes": 684875776 + }, + { + "query": "\u00bfCu\u00e1nto tiempo se conservan las copias de seguridad de Quartz?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0M2XPVZMY3GF0BZ6JV0R4", + "id": "01M1Y0M8Q1A0QQSCC4FE9RQWRE", + "kind": "memory", + "score": 0.9996949434280396, + "summary": "project:fact - Quartz backups are retained for 36 hours." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 352.6783, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 429, + "mcp_result_bytes": 510, + "wire_bytes": 546, + "reported_used_tokens": 510, + "working_set_bytes": 645173248, + "peak_working_set_bytes": 684875776 + }, + { + "query": "What is the timeout in seconds for the gateway in Quartz?", + "ranked": [ + "timeout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0M2VP5VSANS7519PBJ1Q7", + "id": "01M1Y0M92A54F2TBK48W26W5TP", + "kind": "memory", + "score": 0.9999818801879884, + "summary": "project:fact - Quartz gateway timeout is 45 seconds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 361.8693, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 426, + "mcp_result_bytes": 507, + "wire_bytes": 543, + "reported_used_tokens": 507, + "working_set_bytes": 645251072, + "peak_working_set_bytes": 684875776 + }, + { + "query": "What is the database timeout for Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 362.0381, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645304320, + "peak_working_set_bytes": 684875776 + }, + { + "query": "What encryption key does Quartz use?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": null, + "missing": [ + "encryption_key" + ], + "status": "missing", + "subject": "quartz", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 353.5151, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 362, + "mcp_result_bytes": 445, + "wire_bytes": 481, + "reported_used_tokens": 445, + "working_set_bytes": 645488640, + "peak_working_set_bytes": 684875776 + }, + { + "query": "What is `storage.cache_bytes` in Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 359.9656, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645582848, + "peak_working_set_bytes": 684875776 + }, + { + "query": "How many production replicas does Quartz run?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 387.61469999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645615616, + "peak_working_set_bytes": 684875776 + }, + { + "query": "What is the OpenSSL version for Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 347.73789999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645636096, + "peak_working_set_bytes": 684875776 + }, + { + "query": "What is the database password for Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 346.7149, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645726208, + "peak_working_set_bytes": 684875776 + }, + { + "query": "How long are logs retained for Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 348.5952, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645758976, + "peak_working_set_bytes": 684875776 + }, + { + "query": "\u00bfQu\u00e9 contrase\u00f1a usa la base de datos de Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 350.0013, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645820416, + "peak_working_set_bytes": 684875776 + }, + { + "query": "\u00bfCu\u00e1ntas replicas de producci\u00f3n tiene Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 356.94259999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645840896, + "peak_working_set_bytes": 684875776 + }, + { + "query": "Which region hosts Quartz production?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 346.3307, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645885952, + "peak_working_set_bytes": 684875776 + } + ], + "id": "quartz-answerability-frozen", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.000 (n=10) positive-n=12 negative-n=10 (22 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 2.0, + 2 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 1.0, + "n": 2, + "ci95": 0.0 + } + }, + "overall_index": 1.0, + "scenario_weighted_index": 1.0 +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-structured-facts/results/answerability-regression/1-candidate.stderr.log b/docs/audits/2026-09-07-structured-facts/results/answerability-regression/1-candidate.stderr.log new file mode 100644 index 0000000..6f72c24 --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/results/answerability-regression/1-candidate.stderr.log @@ -0,0 +1,6 @@ +brainbench: 2 scenario(s) to run + [1/2] orchid-answerability-frozen | dim=retrieval tier=hard ... + -> score=1.00 | positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.000 (n=10) positive-n=12 negative-n=10 (22 queries) + [2/2] quartz-answerability-frozen | dim=retrieval tier=hard ... + -> score=1.00 | positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.000 (n=10) positive-n=12 negative-n=10 (22 queries) +kbench brainbench: report saved -> E:\tmp\kimetsu-brain-hardening\bench\local\runs\brainbench\2026-09-07T13-23-58.3179908Z.json diff --git a/docs/audits/2026-09-07-structured-facts/results/answerability-regression/1-candidate.stdout.log b/docs/audits/2026-09-07-structured-facts/results/answerability-regression/1-candidate.stdout.log new file mode 100644 index 0000000..2ad4370 --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/results/answerability-regression/1-candidate.stdout.log @@ -0,0 +1,1237 @@ +{ + "generated_at": "2026-09-07T13:23:58.317696Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-answerability\\validation-frozen.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "What is the Orchid gateway timeout?", + "ranked": [ + "timeout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KQCH3RETYTG59JCNR1KK", + "id": "01M1Y0KSYG55W6Q4DV48W4N4P0", + "kind": "memory", + "score": 0.999908208847046, + "summary": "project:fact - Orchid gateway timeout is 45 seconds." + } + ], + "answerability": { + "conflicting": [], + "environment": null, + "missing": [], + "status": "supported", + "subject": "orchid gateway", + "supported": [ + { + "attribute": "timeout", + "sources": [ + "memory:01M1Y0KQCH3RETYTG59JCNR1KK" + ], + "value": "45 seconds" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2447.6951, + "first_query": true, + "server_startup_ms": 71.7255, + "model_text_bytes": 648, + "mcp_result_bytes": 759, + "wire_bytes": 794, + "reported_used_tokens": 759, + "working_set_bytes": 635170816, + "peak_working_set_bytes": 684916736 + }, + { + "query": "How many retries does the Orchid client use?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KQD1TGW17QPPA766RVX7", + "id": "01M1Y0KT9GYE8K14G0B2NQQSWP", + "kind": "memory", + "score": 0.9976400136947632, + "summary": "project:fact - Orchid client retry count is 5." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 339.9678, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 420, + "mcp_result_bytes": 501, + "wire_bytes": 536, + "reported_used_tokens": 501, + "working_set_bytes": 637341696, + "peak_working_set_bytes": 684916736 + }, + { + "query": "What is the Orchid worker memory limit?", + "ranked": [ + "memory" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KQDA3PETEDPFWT0FKBM5", + "id": "01M1Y0KTMEG7QTTAZEV5X6NYGW", + "kind": "memory", + "score": 0.9999785423278807, + "summary": "project:fact - Orchid worker memory limit is 768 MiB." + } + ], + "answerability": { + "conflicting": [], + "environment": null, + "missing": [], + "status": "supported", + "subject": "orchid worker", + "supported": [ + { + "attribute": "memory_limit", + "sources": [ + "memory:01M1Y0KQDA3PETEDPFWT0FKBM5" + ], + "value": "768 mib" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 353.5858, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 650, + "mcp_result_bytes": 761, + "wire_bytes": 796, + "reported_used_tokens": 761, + "working_set_bytes": 642465792, + "peak_working_set_bytes": 684916736 + }, + { + "query": "What version does the Orchid worker run?", + "ranked": [ + "version" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KQDK7ZB06G0NP39MZJ8F", + "id": "01M1Y0KTZJKJQ02D0QFTHZEDCG", + "kind": "memory", + "score": 0.9998592138290404, + "summary": "project:fact - Orchid worker version 8.2 is installed." + } + ], + "answerability": { + "conflicting": [], + "environment": null, + "missing": [ + "version" + ], + "status": "missing", + "subject": "orchid worker", + "supported": [] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 363.6697, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 563, + "mcp_result_bytes": 664, + "wire_bytes": 699, + "reported_used_tokens": 664, + "working_set_bytes": 642523136, + "peak_working_set_bytes": 684916736 + }, + { + "query": "What is `storage.page_bytes` in Orchid?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KQDW0PQ1060C7M16EKFW", + "id": "01M1Y0KVBF298SYKRKXGGCN90G", + "kind": "memory", + "score": 0.9999752044677734, + "summary": "project:fact - Orchid storage.page_bytes = 8192." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 375.4471, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 422, + "mcp_result_bytes": 503, + "wire_bytes": 538, + "reported_used_tokens": 503, + "working_set_bytes": 642904064, + "peak_working_set_bytes": 684916736 + }, + { + "query": "What password does the Orchid gateway require?", + "ranked": [ + "password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KQE71VFXH0M77MCKCJBP", + "id": "01M1Y0KVPDG24XF1GX5SJG66NF", + "kind": "memory", + "score": 0.999871015548706, + "summary": "project:fact - No password is required for the Orchid gateway." + } + ], + "answerability": { + "conflicting": [], + "environment": null, + "missing": [], + "status": "supported", + "subject": "orchid gateway", + "supported": [ + { + "attribute": "password", + "sources": [ + "memory:01M1Y0KQE71VFXH0M77MCKCJBP" + ], + "value": "not required" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 348.4848, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 660, + "mcp_result_bytes": 771, + "wire_bytes": 806, + "reported_used_tokens": 771, + "working_set_bytes": 642981888, + "peak_working_set_bytes": 684916736 + }, + { + "query": "How long are Orchid backups retained?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KQEHJJVEER85HPN9HJH8", + "id": "01M1Y0KW1BM0BPRREK862JPZ8Y", + "kind": "memory", + "score": 0.9999786615371704, + "summary": "project:fact - Orchid backups are retained for 36 hours." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.7613, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 429, + "mcp_result_bytes": 510, + "wire_bytes": 545, + "reported_used_tokens": 510, + "working_set_bytes": 643313664, + "peak_working_set_bytes": 684916736 + }, + { + "query": "Which files configure the port for Orchid?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KQETSWKSZEJ1EZVZGHSB", + "id": "01M1Y0KWCJX6KA8X7FYG80CWZB", + "kind": "memory", + "score": 0.9955846667289734, + "summary": "project:fact - Orchid listener binds TCP port 7321. Configure its port in listener.toml." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 349.8548, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 462, + "mcp_result_bytes": 543, + "wire_bytes": 578, + "reported_used_tokens": 543, + "working_set_bytes": 643411968, + "peak_working_set_bytes": 684916736 + }, + { + "query": "What causes a version conflict in Orchid?", + "ranked": [ + "advice" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KQF6V84VGA5B5Q5B1WD0", + "id": "01M1Y0KWQDXG4KTJE0EAAYXBW8", + "kind": "memory", + "score": 0.9998210072517396, + "summary": "project:fact - Orchid version conflicts occur when lockfiles disagree. Regenerate the lockfile and check dependency constraints." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 353.7158, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 501, + "mcp_result_bytes": 582, + "wire_bytes": 618, + "reported_used_tokens": 582, + "working_set_bytes": 643493888, + "peak_working_set_bytes": 684916736 + }, + { + "query": "¿Qué versión usa el worker de Orchid?", + "ranked": [ + "version" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KQDK7ZB06G0NP39MZJ8F", + "id": "01M1Y0KX2J858SZ8MZHBS3FTAX", + "kind": "memory", + "score": 0.999970316886902, + "summary": "project:fact - Orchid worker version 8.2 is installed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 350.5779, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 544, + "reported_used_tokens": 508, + "working_set_bytes": 643604480, + "peak_working_set_bytes": 684916736 + }, + { + "query": "¿Cuánto tiempo se conservan las copias de seguridad de Orchid?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KQEHJJVEER85HPN9HJH8", + "id": "01M1Y0KXDG67GQ0MYTHS99ANMZ", + "kind": "memory", + "score": 0.9995601773262024, + "summary": "project:fact - Orchid backups are retained for 36 hours." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.4911, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 429, + "mcp_result_bytes": 510, + "wire_bytes": 546, + "reported_used_tokens": 510, + "working_set_bytes": 644124672, + "peak_working_set_bytes": 684916736 + }, + { + "query": "What is the timeout in seconds for the gateway in Orchid?", + "ranked": [ + "timeout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0KQCH3RETYTG59JCNR1KK", + "id": "01M1Y0KXRWQC6E5JD2B4CDNW8W", + "kind": "memory", + "score": 0.9999822378158568, + "summary": "project:fact - Orchid gateway timeout is 45 seconds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 360.088, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 426, + "mcp_result_bytes": 507, + "wire_bytes": 543, + "reported_used_tokens": 507, + "working_set_bytes": 644161536, + "peak_working_set_bytes": 684916736 + }, + { + "query": "What is the database timeout for Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 360.1187, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644263936, + "peak_working_set_bytes": 684916736 + }, + { + "query": "What encryption key does Orchid use?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": null, + "missing": [ + "encryption_key" + ], + "status": "missing", + "subject": "orchid", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 387.1739, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 362, + "mcp_result_bytes": 445, + "wire_bytes": 481, + "reported_used_tokens": 445, + "working_set_bytes": 644317184, + "peak_working_set_bytes": 684916736 + }, + { + "query": "What is `storage.cache_bytes` in Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 360.0568, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644333568, + "peak_working_set_bytes": 684916736 + }, + { + "query": "How many production replicas does Orchid run?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 352.1326, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644349952, + "peak_working_set_bytes": 684916736 + }, + { + "query": "What is the OpenSSL version for Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 349.5252, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644354048, + "peak_working_set_bytes": 684916736 + }, + { + "query": "What is the database password for Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 346.1954, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644423680, + "peak_working_set_bytes": 684916736 + }, + { + "query": "How long are logs retained for Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 362.8365, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644485120, + "peak_working_set_bytes": 684916736 + }, + { + "query": "¿Qué contraseña usa la base de datos de Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 348.383, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644521984, + "peak_working_set_bytes": 684916736 + }, + { + "query": "¿Cuántas replicas de producción tiene Orchid?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 363.78450000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644653056, + "peak_working_set_bytes": 684916736 + }, + { + "query": "Which region hosts Orchid production?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 356.24, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644747264, + "peak_working_set_bytes": 684916736 + } + ], + "id": "orchid-answerability-frozen", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.000 (n=10) positive-n=12 negative-n=10 (22 queries)" + }, + { + "observations": [ + { + "query": "What is the Quartz gateway timeout?", + "ranked": [ + "timeout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0M2VP5VSANS7519PBJ1Q7", + "id": "01M1Y0M589PNER0CCBJA9VTFEE", + "kind": "memory", + "score": 0.9999661445617676, + "summary": "project:fact - Quartz gateway timeout is 45 seconds." + } + ], + "answerability": { + "conflicting": [], + "environment": null, + "missing": [], + "status": "supported", + "subject": "quartz gateway", + "supported": [ + { + "attribute": "timeout", + "sources": [ + "memory:01M1Y0M2VP5VSANS7519PBJ1Q7" + ], + "value": "45 seconds" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2346.6744999999996, + "first_query": true, + "server_startup_ms": 75.2195, + "model_text_bytes": 648, + "mcp_result_bytes": 759, + "wire_bytes": 794, + "reported_used_tokens": 759, + "working_set_bytes": 636063744, + "peak_working_set_bytes": 684875776 + }, + { + "query": "How many retries does the Quartz client use?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0M2W7CW9PKX1FGV1WQ55B", + "id": "01M1Y0M5K75YNR280RR5C13B5D", + "kind": "memory", + "score": 0.9913354516029358, + "summary": "project:fact - Quartz client retry count is 5." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 337.9665, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 420, + "mcp_result_bytes": 501, + "wire_bytes": 536, + "reported_used_tokens": 501, + "working_set_bytes": 636534784, + "peak_working_set_bytes": 684875776 + }, + { + "query": "What is the Quartz worker memory limit?", + "ranked": [ + "memory" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0M2WGHE3XV716SPJXH930", + "id": "01M1Y0M5XZ36KBHM3WM4XEPYV5", + "kind": "memory", + "score": 0.9999793767929076, + "summary": "project:fact - Quartz worker memory limit is 768 MiB." + } + ], + "answerability": { + "conflicting": [], + "environment": null, + "missing": [], + "status": "supported", + "subject": "quartz worker", + "supported": [ + { + "attribute": "memory_limit", + "sources": [ + "memory:01M1Y0M2WGHE3XV716SPJXH930" + ], + "value": "768 mib" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 347.45369999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 650, + "mcp_result_bytes": 761, + "wire_bytes": 796, + "reported_used_tokens": 761, + "working_set_bytes": 641527808, + "peak_working_set_bytes": 684875776 + }, + { + "query": "What version does the Quartz worker run?", + "ranked": [ + "version" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0M2WT695F5BY4AH0GFC69", + "id": "01M1Y0M697JZ27FGQD4G4T6WQF", + "kind": "memory", + "score": 0.9998953342437744, + "summary": "project:fact - Quartz worker version 8.2 is installed." + } + ], + "answerability": { + "conflicting": [], + "environment": null, + "missing": [ + "version" + ], + "status": "missing", + "subject": "quartz worker", + "supported": [] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 358.3561, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 563, + "mcp_result_bytes": 664, + "wire_bytes": 699, + "reported_used_tokens": 664, + "working_set_bytes": 641761280, + "peak_working_set_bytes": 684875776 + }, + { + "query": "What is `storage.page_bytes` in Quartz?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0M2X3XMB2AQCQQHD62QRP", + "id": "01M1Y0M6MA5157XVANR65QSFW6", + "kind": "memory", + "score": 0.9999722242355348, + "summary": "project:fact - Quartz storage.page_bytes = 8192." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 361.8323, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 422, + "mcp_result_bytes": 503, + "wire_bytes": 538, + "reported_used_tokens": 503, + "working_set_bytes": 644091904, + "peak_working_set_bytes": 684875776 + }, + { + "query": "What password does the Quartz gateway require?", + "ranked": [ + "password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0M2XD8QRYH86NNXSFJD3J", + "id": "01M1Y0M6ZJHDAPH2TFE73D7H5N", + "kind": "memory", + "score": 0.9998206496238708, + "summary": "project:fact - No password is required for the Quartz gateway." + } + ], + "answerability": { + "conflicting": [], + "environment": null, + "missing": [], + "status": "supported", + "subject": "quartz gateway", + "supported": [ + { + "attribute": "password", + "sources": [ + "memory:01M1Y0M2XD8QRYH86NNXSFJD3J" + ], + "value": "not required" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 353.576, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 661, + "mcp_result_bytes": 772, + "wire_bytes": 807, + "reported_used_tokens": 772, + "working_set_bytes": 644218880, + "peak_working_set_bytes": 684875776 + }, + { + "query": "How long are Quartz backups retained?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0M2XPVZMY3GF0BZ6JV0R4", + "id": "01M1Y0M7BBKZP2JZBMFPN0C9ND", + "kind": "memory", + "score": 0.999981164932251, + "summary": "project:fact - Quartz backups are retained for 36 hours." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 380.6524, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 428, + "mcp_result_bytes": 509, + "wire_bytes": 544, + "reported_used_tokens": 509, + "working_set_bytes": 644554752, + "peak_working_set_bytes": 684875776 + }, + { + "query": "Which files configure the port for Quartz?", + "ranked": [ + "port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0M2XZG1RCERMMA78EJ2FY", + "id": "01M1Y0M7P96CDV0AJTRWJ0QH9T", + "kind": "memory", + "score": 0.9969274401664734, + "summary": "project:fact - Quartz listener binds TCP port 8452. Configure its port in listener.toml." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 346.0613, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 462, + "mcp_result_bytes": 543, + "wire_bytes": 578, + "reported_used_tokens": 543, + "working_set_bytes": 644624384, + "peak_working_set_bytes": 684875776 + }, + { + "query": "What causes a version conflict in Quartz?", + "ranked": [ + "advice" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0M2YA0Y903D4K3MDB7K67", + "id": "01M1Y0M8121R0QSXQQ9E4RTKF4", + "kind": "memory", + "score": 0.9998512268066406, + "summary": "project:fact - Quartz version conflicts occur when lockfiles disagree. Regenerate the lockfile and check dependency constraints." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 348.21180000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 501, + "mcp_result_bytes": 582, + "wire_bytes": 618, + "reported_used_tokens": 582, + "working_set_bytes": 644669440, + "peak_working_set_bytes": 684875776 + }, + { + "query": "¿Qué versión usa el worker de Quartz?", + "ranked": [ + "version" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0M2WT695F5BY4AH0GFC69", + "id": "01M1Y0M8C0XP7KF1AK9XSSKDZP", + "kind": "memory", + "score": 0.9999598264694214, + "summary": "project:fact - Quartz worker version 8.2 is installed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 351.8111, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 544, + "reported_used_tokens": 508, + "working_set_bytes": 644763648, + "peak_working_set_bytes": 684875776 + }, + { + "query": "¿Cuánto tiempo se conservan las copias de seguridad de Quartz?", + "ranked": [ + "backup" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0M2XPVZMY3GF0BZ6JV0R4", + "id": "01M1Y0M8Q1A0QQSCC4FE9RQWRE", + "kind": "memory", + "score": 0.9996949434280396, + "summary": "project:fact - Quartz backups are retained for 36 hours." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 352.6783, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 429, + "mcp_result_bytes": 510, + "wire_bytes": 546, + "reported_used_tokens": 510, + "working_set_bytes": 645173248, + "peak_working_set_bytes": 684875776 + }, + { + "query": "What is the timeout in seconds for the gateway in Quartz?", + "ranked": [ + "timeout" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0M2VP5VSANS7519PBJ1Q7", + "id": "01M1Y0M92A54F2TBK48W26W5TP", + "kind": "memory", + "score": 0.9999818801879884, + "summary": "project:fact - Quartz gateway timeout is 45 seconds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 361.8693, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 426, + "mcp_result_bytes": 507, + "wire_bytes": 543, + "reported_used_tokens": 507, + "working_set_bytes": 645251072, + "peak_working_set_bytes": 684875776 + }, + { + "query": "What is the database timeout for Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 362.0381, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645304320, + "peak_working_set_bytes": 684875776 + }, + { + "query": "What encryption key does Quartz use?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": null, + "missing": [ + "encryption_key" + ], + "status": "missing", + "subject": "quartz", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 353.5151, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 362, + "mcp_result_bytes": 445, + "wire_bytes": 481, + "reported_used_tokens": 445, + "working_set_bytes": 645488640, + "peak_working_set_bytes": 684875776 + }, + { + "query": "What is `storage.cache_bytes` in Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 359.9656, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645582848, + "peak_working_set_bytes": 684875776 + }, + { + "query": "How many production replicas does Quartz run?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 387.61469999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645615616, + "peak_working_set_bytes": 684875776 + }, + { + "query": "What is the OpenSSL version for Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 347.73789999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645636096, + "peak_working_set_bytes": 684875776 + }, + { + "query": "What is the database password for Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 346.7149, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645726208, + "peak_working_set_bytes": 684875776 + }, + { + "query": "How long are logs retained for Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 348.5952, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645758976, + "peak_working_set_bytes": 684875776 + }, + { + "query": "¿Qué contraseña usa la base de datos de Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 350.0013, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645820416, + "peak_working_set_bytes": 684875776 + }, + { + "query": "¿Cuántas replicas de producción tiene Quartz?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 356.94259999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645840896, + "peak_working_set_bytes": 684875776 + }, + { + "query": "Which region hosts Quartz production?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 346.3307, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645885952, + "peak_working_set_bytes": 684875776 + } + ], + "id": "quartz-answerability-frozen", + "dimension": "retrieval", + "tier": "hard", + "score": 1.0, + "skipped": false, + "detail": "positive-recall@4=1.00 mrr=1.00 stale-hit=n/a resolution=n/a false-injection=0.000 (n=10) positive-n=12 negative-n=10 (22 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 2.0, + 2 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 1.0, + "n": 2, + "ci95": 0.0 + } + }, + "overall_index": 1.0, + "scenario_weighted_index": 1.0 +} diff --git a/docs/audits/2026-09-07-structured-facts/results/answerability-regression/comparison.json b/docs/audits/2026-09-07-structured-facts/results/answerability-regression/comparison.json new file mode 100644 index 0000000..a7a54ff --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/results/answerability-regression/comparison.json @@ -0,0 +1,165 @@ +{ + "schema_version": 1, + "status": "complete", + "harness": { + "path": "E:\\Kimetsu\\bench\\target\\release\\kbench.exe", + "sha256": "5ba5065f9aaa28ced75a091bb43e01c8b995751e0e5cd3f9842ac6c822e844ff", + "bytes": 9405952 + }, + "runner": { + "path": "E:\\tmp\\kimetsu-brain-hardening\\bench\\scripts\\compare_brainbench.py", + "sha256": "738bad9404a4ec2b911fff661967ca56f48b584dfeb22f83823c972a1498df37", + "bytes": 24527 + }, + "binaries": { + "baseline": { + "path": "E:\\tmp\\kimetsu-brain-hardening\\tmp-tests\\kimetsu-answerability-candidate.exe", + "sha256": "405d3483fe320e76b0ec776bf9ada3b7771852b04a73f70f3b5da377a43d31c3", + "bytes": 47151104 + }, + "candidate": { + "path": "E:\\tmp\\kimetsu-brain-hardening\\tmp-tests\\kimetsu-structured-facts-candidate.exe", + "sha256": "b5672cfed1da5bbd03fdbc20b954582ad5216f4fb579c8e463914c0839126ee7", + "bytes": 47382016 + } + }, + "datasets": [ + { + "path": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-answerability\\validation-frozen.json", + "sha256": "ea4452872956beed1030ec071572db89435780474180b17a7bf7fe8f481e5d7e", + "bytes": 10754 + } + ], + "settings": { + "budget_tokens": 6000, + "dimensions": [ + "poisoning", + "render-contract", + "retrieval", + "workflow" + ], + "jobs": 1, + "warm_start": false, + "include_ambient": false, + "overrides": { + "KIMETSU_BRAIN_EMBEDDER": "bge-small-en-v1.5", + "KIMETSU_DETECT_CONFLICTS": "0", + "KIMETSU_RESOLVE_CONFLICTS": "0", + "FASTEMBED_CACHE_DIR": "E:\\Kimetsu\\.fastembed_cache", + "HF_HOME": "E:\\tmp\\kimetsu-brain-hardening\\tmp-tests\\hf-home" + }, + "baseline_threads": 0, + "candidate_threads": 0, + "baseline_reranker": "mmarco-minilm-l12-v2-int8", + "candidate_reranker": "mmarco-minilm-l12-v2-int8", + "baseline_rerank_floor": 0.55, + "candidate_rerank_floor": 0.55 + }, + "runs": [ + { + "label": "baseline", + "repeat": 1, + "intra_threads_override": null, + "rerank_floor_override": "0.55", + "explicit_fact_guard_override": "true", + "reranker_override": "mmarco-minilm-l12-v2-int8", + "wall_seconds": 24.226267800026108, + "report_file": "1-baseline.json" + }, + { + "label": "candidate", + "repeat": 1, + "intra_threads_override": null, + "rerank_floor_override": "0.55", + "explicit_fact_guard_override": "true", + "reranker_override": "mmarco-minilm-l12-v2-int8", + "wall_seconds": 23.491178500000387, + "report_file": "1-candidate.json" + } + ], + "comparison": { + "measurement_summary": { + "baseline": { + "unique_queries": 44, + "query_observations": 44, + "positive_queries": 24, + "negative_queries": 20, + "stale_queries": 0, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": 1, + "positive_mrr": 1.0, + "negative_injection_rate": 0, + "stale_injection_rate": null, + "first_query_mean_ms": 2432.32715, + "subsequent_query_p50_ms": 354.5775, + "subsequent_query_p95_ms": 409.1152, + "subsequent_observations": 42, + "mean_model_text_bytes": 340.5, + "mean_mcp_result_bytes": 413.3181818181818, + "memory_observations": 44, + "mean_mcp_working_set_bytes": 643425093.8181819, + "max_mcp_peak_working_set_bytes": 685092864, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + }, + "candidate": { + "unique_queries": 44, + "query_observations": 44, + "positive_queries": 24, + "negative_queries": 20, + "stale_queries": 0, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": 1, + "positive_mrr": 1.0, + "negative_injection_rate": 0, + "stale_injection_rate": null, + "first_query_mean_ms": 2397.1848, + "subsequent_query_p50_ms": 353.576, + "subsequent_query_p95_ms": 380.6524, + "subsequent_observations": 42, + "mean_model_text_bytes": 383.3181818181818, + "mean_mcp_result_bytes": 462.04545454545456, + "memory_observations": 44, + "mean_mcp_working_set_bytes": 643632779.6363636, + "max_mcp_peak_working_set_bytes": 684916736, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + } + }, + "by_dimension": { + "retrieval": { + "n_scenarios": 2, + "baseline": 1.0, + "candidate": 1.0, + "mean_delta": 0.0, + "ci95": [ + 0.0, + 0.0 + ], + "wins": 0, + "ties": 2, + "losses": 0 + } + }, + "scenarios": [ + { + "identity": "retrieval/orchid-answerability-frozen", + "dimension": "retrieval", + "baseline": 1.0, + "candidate": 1.0, + "delta": 0.0 + }, + { + "identity": "retrieval/quartz-answerability-frozen", + "dimension": "retrieval", + "baseline": 1.0, + "candidate": 1.0, + "delta": 0.0 + } + ], + "unpaired_scenarios": [], + "unpaired_details": [], + "baseline_errors": 0, + "candidate_errors": 0, + "repeats": 1, + "uncertainty_note": "Exploratory paired bootstrap over scenario IDs after averaging repeats; correlated task families require a separate grouped holdout." + } +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-structured-facts/results/answerability-regression/comparison.md b/docs/audits/2026-09-07-structured-facts/results/answerability-regression/comparison.md new file mode 100644 index 0000000..ba329cb --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/results/answerability-regression/comparison.md @@ -0,0 +1,26 @@ +# Paired BrainBench comparison + +Same harness and fixture; run order alternates. Positive delta favors the candidate. + +| Dimension | Scenarios | Baseline | Candidate | Delta | Exploratory 95% interval | +|---|---:|---:|---:|---:|---| +| retrieval | 2 | 1.000 | 1.000 | +0.000 | [+0.000, +0.000] | + +Errors: baseline 0, candidate 0. +Unpaired/skipped scenarios: 0. + +Exploratory paired bootstrap over scenario IDs after averaging repeats; correlated task families require a separate grouped holdout. + +Wall times include process/model startup, corpus seeding and queries; they are not warm inference latency. + +baseline: mean complete-run time 24.23 s (1 repeats). +candidate: mean complete-run time 23.49 s (1 repeats). + +Query measurements through persistent MCP (subsequent queries reuse the process): + +| Build | Positive hit@4 | Positive recall@4 | False injection | Subsequent p50 / p95 ms | Mean MCP result bytes | Peak MCP working set MiB | +|---|---:|---:|---:|---:|---:|---:| +| baseline | 1.000 | 1.000 | 0.000 | 354.577 / 409.115 | 413.318 | 653.355 | +| candidate | 1.000 | 1.000 | 0.000 | 353.576 / 380.652 | 462.045 | 653.188 | + +Measured bytes include JSON escaping; reported token estimates are retained per query but may use different accounting rules across builds. Query timing excludes the separately recorded MCP initialization and corpus seeding. diff --git a/docs/audits/2026-09-07-structured-facts/results/development/1-baseline.json b/docs/audits/2026-09-07-structured-facts/results/development/1-baseline.json new file mode 100644 index 0000000..8f22731 --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/results/development/1-baseline.json @@ -0,0 +1,6811 @@ +{ + "generated_at": "2026-09-07T13:19:35.0771582Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-retrieval\\development-100.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "test_env_lock inside with_user_brain_disabled deadlock", + "ranked": [ + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YAM1J3BB2GHQRB5M0FP", + "id": "01M1Y063D0RMVQW7HADFVP5KFA", + "kind": "memory", + "score": 0.9999488592147828, + "summary": "project:fact - [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure \u2014 `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1075.8079, + "first_query": true, + "server_startup_ms": 79.9213, + "model_text_bytes": 796, + "mcp_result_bytes": 877, + "wire_bytes": 912, + "reported_used_tokens": 877, + "working_set_bytes": 227520512, + "peak_working_set_bytes": 248590336 + }, + { + "query": "why does my test hang after calling with_user_brain_disabled when I also lock test_env_lock?", + "ranked": [ + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YAM1J3BB2GHQRB5M0FP", + "id": "01M1Y064393KH3YHF2KCPMD5QE", + "kind": "memory", + "score": 0.9990190267562866, + "summary": "project:fact - [2026-09-07] [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure \u2014 `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 826.4118000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 808, + "mcp_result_bytes": 889, + "wire_bytes": 924, + "reported_used_tokens": 889, + "working_set_bytes": 229535744, + "peak_working_set_bytes": 248590336 + }, + { + "query": "ingest_repo_at_root brain_root files_root kimetsu remote", + "ranked": [ + "remote-ingest-split-roots", + "kimetsu-write-tools-gate", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YBPJ3QEY9DGV3Y39QED", + "id": "01M1Y064X3GJ6GMMCSWPMEZ6XV", + "kind": "memory", + "score": 0.999886393547058, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1Y062EEFBC57YJ58J3CSXFD", + "id": "01M1Y064X3FHARP6XQ47H02N4H", + "kind": "memory", + "score": 0.8439717888832092, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level \u2014 disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1Y05YDQZCEVNBN7TDX4XC9B", + "id": "01M1Y064X3DNTH66A6FPXE1XNB", + "kind": "memory", + "score": 0.8363722562789917, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 944.0074000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2726, + "mcp_result_bytes": 2891, + "wire_bytes": 2926, + "reported_used_tokens": 2891, + "working_set_bytes": 252657664, + "peak_working_set_bytes": 253562880 + }, + { + "query": "why does the remote server index the wrong directory when I run kimetsu brain ingest?", + "ranked": [ + "remote-ingest-split-roots", + "onnx-dim-mismatch" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YBPJ3QEY9DGV3Y39QED", + "id": "01M1Y065TVCNMMR7Y0RJ086YG0", + "kind": "memory", + "score": 0.9836117625236512, + "summary": "project:fact - [2026-09-07] [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1Y060H9Y1TBKN4BXG507K53", + "id": "01M1Y065TVW6SGR6XHG9W46DEG", + "kind": "memory", + "score": 0.3657674789428711, + "summary": "project:fact - [2026-09-07] [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results \u2014 the ANN index shape mismatch isn't always caught at runtime." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 933.9577999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1800, + "mcp_result_bytes": 1899, + "wire_bytes": 1934, + "reported_used_tokens": 1899, + "working_set_bytes": 258662400, + "peak_working_set_bytes": 259579904 + }, + { + "query": "kimetsu plugin install --remote mcp.json authorization bearer token", + "ranked": [ + "remote-mcp-host-wiring", + "mcp-stdout-protocol" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YDQZCEVNBN7TDX4XC9B", + "id": "01M1Y066R4WEA8HSJ2ZARWX2RV", + "kind": "memory", + "score": 0.999605119228363, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + }, + { + "expansion_handle": "memory:01M1Y061PEX980HR5500H8P7ZH", + "id": "01M1Y066R56PBJDVF3SCHJRVQQ", + "kind": "memory", + "score": 0.3375842869281769, + "summary": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 850.1499, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1472, + "mcp_result_bytes": 1619, + "wire_bytes": 1654, + "reported_used_tokens": 1619, + "working_set_bytes": 259514368, + "peak_working_set_bytes": 260427776 + }, + { + "query": "how do I wire a remote kimetsu brain into Claude Code without storing the token in the config file?", + "ranked": [ + "remote-mcp-host-wiring", + "mcp-tool-naming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YDQZCEVNBN7TDX4XC9B", + "id": "01M1Y067JK05KRFAPW1XEGBYVB", + "kind": "memory", + "score": 0.9963359832763672, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + }, + { + "expansion_handle": "memory:01M1Y061TPTKP8TGYS8ZZJBXTG", + "id": "01M1Y067JKJ1JV6T0D6YPXZC0S", + "kind": "memory", + "score": 0.831425666809082, + "summary": "project:fact - [tags: mcp tool naming convention kimetsu] MCP tool names must be valid identifiers for all host agents. Claude Code restricts tool names to `[a-zA-Z0-9_-]` and max 64 chars. Use `snake_case` (kimetsu_brain_context, kimetsu_brain_record) \u2014 hyphen is technically allowed but some hosts reject it." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 850.6700000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1454, + "mcp_result_bytes": 1601, + "wire_bytes": 1636, + "reported_used_tokens": 1601, + "working_set_bytes": 259895296, + "peak_working_set_bytes": 260816896 + }, + { + "query": "cargo feature unification kimetsu-brain embeddings fastembed test failure", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-profile-override", + "clap-version-build-flavor" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YFMXS61RMTQ16GE6735", + "id": "01M1Y068D38TZS9VY9MVTBHXB5", + "kind": "memory", + "score": 0.9996790885925292, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1Y05ZGMYJJMEZXJZA6WN6W3", + "id": "01M1Y068D3N8F1Q4XMQ2HY673K", + "kind": "memory", + "score": 0.9923595786094666, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1Y05YSZ7NPRA3AG5ES716WZ", + "id": "01M1Y068D3TQ0KJR972J66XJX2", + "kind": "memory", + "score": 0.585203230381012, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 850.2271999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2387, + "mcp_result_bytes": 2524, + "wire_bytes": 2559, + "reported_used_tokens": 2524, + "working_set_bytes": 261287936, + "peak_working_set_bytes": 262217728 + }, + { + "query": "my integration tests pass in isolation but break when I run cargo test --workspace \u2014 embedder changed?", + "ranked": [ + "cargo-feature-unification-embeddings", + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YFMXS61RMTQ16GE6735", + "id": "01M1Y0697PH5HXYFJYKGATZATE", + "kind": "memory", + "score": 0.9943140745162964, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1Y05YS4YPH8D67NSXJGT1SS", + "id": "01M1Y0697N61A1JJ4TY5TBME81", + "kind": "memory", + "score": 0.31398114562034607, + "summary": "project:fact - [2026-09-07] [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 890.0258, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1714, + "mcp_result_bytes": 1817, + "wire_bytes": 1852, + "reported_used_tokens": 1817, + "working_set_bytes": 261869568, + "peak_working_set_bytes": 262787072 + }, + { + "query": "build_anthropic_body bedrock-2023-05-31 InvokeModel blocking reqwest", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YH1NQKFMZ0R78E7Z8SP", + "id": "01M1Y06A413ZHYKF5VXSP4B3X7", + "kind": "memory", + "score": 0.9973788261413574, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1Y05YPKKNJWV8P7EZDPZJTG", + "id": "01M1Y06A41A7EJA8N19KN81AF6", + "kind": "memory", + "score": 0.6916899085044861, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 706.3015, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2193, + "mcp_result_bytes": 2320, + "wire_bytes": 2356, + "reported_used_tokens": 2320, + "working_set_bytes": 262266880, + "peak_working_set_bytes": 263172096 + }, + { + "query": "how do I add AWS Bedrock as a model provider in Kimetsu without pulling in the aws-sdk?", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-region-resolution", + "aws-credentials-chain", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YH1NQKFMZ0R78E7Z8SP", + "id": "01M1Y06ATDQCAPM18P1E0JSQ5S", + "kind": "memory", + "score": 0.9998898506164552, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1Y061Y6112PMFKN5GEJZE8M", + "id": "01M1Y06ATDXHY4T9N422SDPD0K", + "kind": "memory", + "score": 0.995676338672638, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1Y061WWY2XRNJE51QHGVRAM", + "id": "01M1Y06ATD8Y4TVX8WZNFB5Z5E", + "kind": "memory", + "score": 0.987064242362976, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + }, + { + "expansion_handle": "memory:01M1Y05YPKKNJWV8P7EZDPZJTG", + "id": "01M1Y06ATDPJPC2REACWKB3DM2", + "kind": "memory", + "score": 0.9493880867958068, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 869.6207999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3455, + "mcp_result_bytes": 3618, + "wire_bytes": 3654, + "reported_used_tokens": 3618, + "working_set_bytes": 270737408, + "peak_working_set_bytes": 271663104 + }, + { + "query": "BridgeTarget enum seams plugin_install_inner plugin_status_inner resolve_setup_hosts", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YJSTY39A6KQMMSK2DH6", + "id": "01M1Y06BMVT6DFXMKK227XQTS9", + "kind": "memory", + "score": 0.9997583031654358, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 712.6516, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1060, + "mcp_result_bytes": 1141, + "wire_bytes": 1177, + "reported_used_tokens": 1141, + "working_set_bytes": 280764416, + "peak_working_set_bytes": 281677824 + }, + { + "query": "I added a new host to the bridge enum but cargo gives me compile errors in five different match arms \u2014 what did I miss?", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YJSTY39A6KQMMSK2DH6", + "id": "01M1Y06CB7MX6JQ1KXSWXKSSSZ", + "kind": "memory", + "score": 0.9977060556411744, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 920.1981000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1059, + "mcp_result_bytes": 1140, + "wire_bytes": 1176, + "reported_used_tokens": 1140, + "working_set_bytes": 281092096, + "peak_working_set_bytes": 282001408 + }, + { + "query": "Pi extension factory defineExtension agent_end session_shutdown kimetsu.ts", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YKXXK0W4ER0PGC80N1C", + "id": "01M1Y06D7XA30PNDZJ6J3AY9AZ", + "kind": "memory", + "score": 0.9990354776382446, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 915.4213, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 804, + "mcp_result_bytes": 893, + "wire_bytes": 929, + "reported_used_tokens": 893, + "working_set_bytes": 281198592, + "peak_working_set_bytes": 282107904 + }, + { + "query": "how does Pi (earendil-works/pi) load plugins and what lifecycle hooks does it expose?", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YKXXK0W4ER0PGC80N1C", + "id": "01M1Y06E4HPSQPFZ2BWR98GW84", + "kind": "memory", + "score": 0.9934834837913512, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 937.7381, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 803, + "mcp_result_bytes": 892, + "wire_bytes": 928, + "reported_used_tokens": 892, + "working_set_bytes": 281620480, + "peak_working_set_bytes": 282525696 + }, + { + "query": "aws-sigv4 SigningParams apply_to_request_http1x reqwest sign-http", + "ranked": [ + "aws-sigv4-bedrock-blocking", + "aws-presigned-urls", + "bedrock-kimetsu-provider", + "aws-credentials-chain" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YPKKNJWV8P7EZDPZJTG", + "id": "01M1Y06F1YKKBZ4V911T24VZAN", + "kind": "memory", + "score": 0.9995608925819396, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1Y0620B2W192A9Y86X0ZB3D", + "id": "01M1Y06F1YFE0TM6K5M3TBCXXV", + "kind": "memory", + "score": 0.984916627407074, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + }, + { + "expansion_handle": "memory:01M1Y05YH1NQKFMZ0R78E7Z8SP", + "id": "01M1Y06F1YP4VHVFGFFXBD0D95", + "kind": "memory", + "score": 0.983895778656006, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1Y061WWY2XRNJE51QHGVRAM", + "id": "01M1Y06F1Y9HY0GBQFG6HB7VGC", + "kind": "memory", + "score": 0.8592692017555237, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 699.4605, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3507, + "mcp_result_bytes": 3670, + "wire_bytes": 3706, + "reported_used_tokens": 3670, + "working_set_bytes": 281686016, + "peak_working_set_bytes": 282587136 + }, + { + "query": "how do I sign a Bedrock InvokeModel request with aws-sigv4 in blocking Rust?", + "ranked": [ + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider", + "aws-region-resolution", + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YPKKNJWV8P7EZDPZJTG", + "id": "01M1Y06FQSMTF0D2KKK95FR9WB", + "kind": "memory", + "score": 0.9998323917388916, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1Y05YH1NQKFMZ0R78E7Z8SP", + "id": "01M1Y06FQSYC99NE0M26351B7Q", + "kind": "memory", + "score": 0.9970844388008118, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1Y061Y6112PMFKN5GEJZE8M", + "id": "01M1Y06FQSKSM3YKW9GWAZMK2N", + "kind": "memory", + "score": 0.9468621611595154, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1Y0620B2W192A9Y86X0ZB3D", + "id": "01M1Y06FQS30QXMMS0YKQDG1BA", + "kind": "memory", + "score": 0.9210098385810852, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 865.6446, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3434, + "mcp_result_bytes": 3597, + "wire_bytes": 3633, + "reported_used_tokens": 3597, + "working_set_bytes": 281763840, + "peak_working_set_bytes": 282677248 + }, + { + "query": "KIMETSU_RUNS_GC env opt-out TraceWriter create gc_old_runs caller", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YRBDY77MTARFTEY6ESW", + "id": "01M1Y06GK17W2340CFPP183C3S", + "kind": "memory", + "score": 0.999936580657959, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 819.4787, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 761, + "mcp_result_bytes": 842, + "wire_bytes": 878, + "reported_used_tokens": 842, + "working_set_bytes": 282001408, + "peak_working_set_bytes": 282910720 + }, + { + "query": "where should I put the KIMETSU_RUNS_GC=0 guard \u2014 inside the GC function or at the call site?", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YRBDY77MTARFTEY6ESW", + "id": "01M1Y06HCRHNCK3TP7ZSTDR6Z6", + "kind": "memory", + "score": 0.9971211552619934, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 933.4207, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 762, + "mcp_result_bytes": 843, + "wire_bytes": 879, + "reported_used_tokens": 843, + "working_set_bytes": 282267648, + "peak_working_set_bytes": 283185152 + }, + { + "query": "git_init_boundary ProjectPaths::discover temp dir user brain isolation", + "ranked": [ + "init-project-git-boundary", + "git-worktree-brain-isolation", + "testing-temp-dirs-ci", + "kimetsu-memory-scopes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YS4YPH8D67NSXJGT1SS", + "id": "01M1Y06JAKCM09DYT87VK3DRF9", + "kind": "memory", + "score": 0.9997712969779968, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + }, + { + "expansion_handle": "memory:01M1Y060PEBE9HMM62FJ922NWN", + "id": "01M1Y06JAK78CYH5YVF0RQ43QQ", + "kind": "memory", + "score": 0.9962491393089294, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root \u2014 if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + }, + { + "expansion_handle": "memory:01M1Y061EHA1QHS9A92RMVXS4T", + "id": "01M1Y06JAKX511CWK76MT4N4G6", + "kind": "memory", + "score": 0.9682154655456544, + "summary": "project:fact - [tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure." + }, + { + "expansion_handle": "memory:01M1Y062B89NB3SMAHX7SX830R", + "id": "01M1Y06JAKMPT3MSZG19JYQ4FT", + "kind": "memory", + "score": 0.3057229816913605, + "summary": "project:fact - [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available \u2014 if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 804.396, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2580, + "mcp_result_bytes": 2715, + "wire_bytes": 2751, + "reported_used_tokens": 2715, + "working_set_bytes": 282431488, + "peak_working_set_bytes": 283340800 + }, + { + "query": "my test calls init_project but it writes to the real ~/.kimetsu instead of the temp folder \u2014 why?", + "ranked": [ + "init-project-git-boundary", + "cargo-feature-unification-embeddings", + "testing-fixture-drift", + "tokio-runtime-in-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YS4YPH8D67NSXJGT1SS", + "id": "01M1Y06K2VGR31HWTH2GFP7YFJ", + "kind": "memory", + "score": 0.9995088577270508, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + }, + { + "expansion_handle": "memory:01M1Y05YFMXS61RMTQ16GE6735", + "id": "01M1Y06K2VRZ13BMK6T6Y1MWHQ", + "kind": "memory", + "score": 0.7287850975990295, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1Y061NJMXF06HD6WSDEN8JZ", + "id": "01M1Y06K2WK40JCDR057KA6KYT", + "kind": "memory", + "score": 0.6596062183380127, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + }, + { + "expansion_handle": "memory:01M1Y060XP0KSRSNNYT7NE7D4W", + "id": "01M1Y06K2WMD3DW4PPCY1DM18B", + "kind": "memory", + "score": 0.3297702968120575, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 909.9809, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2833, + "mcp_result_bytes": 2980, + "wire_bytes": 3016, + "reported_used_tokens": 2980, + "working_set_bytes": 283172864, + "peak_working_set_bytes": 284082176 + }, + { + "query": "clap command version KIMETSU_VERSION_DISPLAY cfg feature embeddings", + "ranked": [ + "clap-version-build-flavor", + "cargo-feature-unification-embeddings" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YSZ7NPRA3AG5ES716WZ", + "id": "01M1Y06KZ8176AFPD1365C7BDN", + "kind": "memory", + "score": 0.9996613264083862, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + }, + { + "expansion_handle": "memory:01M1Y05YFMXS61RMTQ16GE6735", + "id": "01M1Y06KZ8VH1PPQQAYTAF5ZQ7", + "kind": "memory", + "score": 0.3973360061645508, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 702.3792, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1922, + "mcp_result_bytes": 2041, + "wire_bytes": 2077, + "reported_used_tokens": 2041, + "working_set_bytes": 283545600, + "peak_working_set_bytes": 284446720 + }, + { + "query": "how do I show the build flavor (lean vs embeddings) in the kimetsu --version output?", + "ranked": [ + "clap-version-build-flavor", + "cargo-feature-unification-embeddings", + "onnx-quantization-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YSZ7NPRA3AG5ES716WZ", + "id": "01M1Y06MNAYPTZK0ZV9SK1K9ZT", + "kind": "memory", + "score": 0.9978312849998474, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + }, + { + "expansion_handle": "memory:01M1Y05YFMXS61RMTQ16GE6735", + "id": "01M1Y06MNAB8RXV48R95CTYB4V", + "kind": "memory", + "score": 0.8926984667778015, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1Y060DK4AZTG8FQ5SZ82QD8", + "id": "01M1Y06MNA85JCJHNZN6T3EPJB", + "kind": "memory", + "score": 0.8877003192901611, + "summary": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals \u2014 cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 920.5296, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2672, + "mcp_result_bytes": 2809, + "wire_bytes": 2845, + "reported_used_tokens": 2809, + "working_set_bytes": 283856896, + "peak_working_set_bytes": 284770304 + }, + { + "query": "Harbor pyiceberg os.getcwd stale WSL2 DrvFs worker-result subprocess re-exec", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YV5H4YVBX03WZFS7KSE", + "id": "01M1Y06NJ3FTB83YKWY1ZQVR57", + "kind": "memory", + "score": 0.9998155236244202, + "summary": "project:fact - [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 933.6877999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1026, + "mcp_result_bytes": 1107, + "wire_bytes": 1143, + "reported_used_tokens": 1107, + "working_set_bytes": 283987968, + "peak_working_set_bytes": 284893184 + }, + { + "query": "why does my kbench sweep crash after the first trial with 'result.json missing' on WSL2?", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YV5H4YVBX03WZFS7KSE", + "id": "01M1Y06PFBXY3SVK8AA79RRDQR", + "kind": "memory", + "score": 0.998451828956604, + "summary": "project:fact - [2026-09-07] [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 938.0493, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1038, + "mcp_result_bytes": 1119, + "wire_bytes": 1155, + "reported_used_tokens": 1119, + "working_set_bytes": 284004352, + "peak_working_set_bytes": 284921856 + }, + { + "query": "rusqlite VACUUM transaction WAL checkpoint wal_checkpoint TRUNCATE", + "ranked": [ + "sqlite-vacuum-wal-checkpoint", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YWDJ8MDAQEFE8B75QQR", + "id": "01M1Y06QCMW0KEPJ950MK8R50X", + "kind": "memory", + "score": 0.9996871948242188, + "summary": "project:fact - [tags: rust sqlite vacuum rusqlite windows] When implementing SQLite VACUUM in rusqlite: VACUUM cannot run inside a transaction. rusqlite's Connection does not hold an implicit transaction, so `conn.execute_batch(\"VACUUM;\")` works directly. After VACUUM, run `PRAGMA wal_checkpoint(TRUNCATE);` before measuring file size \u2014 on Windows the WAL file can hold significant space that isn't reflected in the main db file until the checkpoint runs." + }, + { + "expansion_handle": "memory:01M1Y05Z2BBHSJ0CDKCQ4XH9GE", + "id": "01M1Y06QCM96SYD38WB1J078NZ", + "kind": "memory", + "score": 0.5274003744125366, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 710.7049, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1507, + "mcp_result_bytes": 1610, + "wire_bytes": 1646, + "reported_used_tokens": 1610, + "working_set_bytes": 284020736, + "peak_working_set_bytes": 284921856 + }, + { + "query": "my SQLite VACUUM reports the file shrank but the disk usage stayed the same \u2014 Windows WAL?", + "ranked": [ + "sqlite-vacuum-wal-checkpoint", + "sqlite-wal-network-drive" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YWDJ8MDAQEFE8B75QQR", + "id": "01M1Y06R35W8744DYTF20ZXVTK", + "kind": "memory", + "score": 0.9155893921852112, + "summary": "project:fact - [tags: rust sqlite vacuum rusqlite windows] When implementing SQLite VACUUM in rusqlite: VACUUM cannot run inside a transaction. rusqlite's Connection does not hold an implicit transaction, so `conn.execute_batch(\"VACUUM;\")` works directly. After VACUUM, run `PRAGMA wal_checkpoint(TRUNCATE);` before measuring file size \u2014 on Windows the WAL file can hold significant space that isn't reflected in the main db file until the checkpoint runs." + }, + { + "expansion_handle": "memory:01M1Y05Z57SYAWW7723HQ5J9NG", + "id": "01M1Y06R3644EV4G9GC9ZK9CZW", + "kind": "memory", + "score": 0.902395486831665, + "summary": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 956.1005, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1357, + "mcp_result_bytes": 1460, + "wire_bytes": 1496, + "reported_used_tokens": 1460, + "working_set_bytes": 284119040, + "peak_working_set_bytes": 285032448 + }, + { + "query": "add_memory import dedup seen_ids snapshot pre-existing active memory IDs", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YX5TE9MYEX6EDV1N9YT", + "id": "01M1Y06S11VX7M8TDVQ52J8P0R", + "kind": "memory", + "score": 0.9999133348464966, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount \u2014 both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 843.1904999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 966, + "mcp_result_bytes": 1047, + "wire_bytes": 1083, + "reported_used_tokens": 1047, + "working_set_bytes": 284147712, + "peak_working_set_bytes": 285052928 + }, + { + "query": "brain import re-imports the same JSON file but the deduplication counter is wrong \u2014 why?", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YX5TE9MYEX6EDV1N9YT", + "id": "01M1Y06SW49XS0RNWMMJ7MK6YN", + "kind": "memory", + "score": 0.9254016876220704, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount \u2014 both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 889.6086, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 965, + "mcp_result_bytes": 1046, + "wire_bytes": 1082, + "reported_used_tokens": 1046, + "working_set_bytes": 284422144, + "peak_working_set_bytes": 285335552 + }, + { + "query": "toml::from_str Value parse document unexpected content str.parse", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YY75RMS67WX826FQJEE", + "id": "01M1Y06TPXGA80MB7MPMZVAZZ5", + "kind": "memory", + "score": 0.9991866946220398, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 739.3157, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 734, + "mcp_result_bytes": 815, + "wire_bytes": 851, + "reported_used_tokens": 815, + "working_set_bytes": 284422144, + "peak_working_set_bytes": 285335552 + }, + { + "query": "how do I parse a TOML configuration file into a toml::Value in toml 0.9?", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YY75RMS67WX826FQJEE", + "id": "01M1Y06VM2QNNY1JJC8PGBY7CE", + "kind": "memory", + "score": 0.9992641806602478, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1087.2188, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 733, + "mcp_result_bytes": 814, + "wire_bytes": 850, + "reported_used_tokens": 814, + "working_set_bytes": 284426240, + "peak_working_set_bytes": 285335552 + }, + { + "query": "CIM CreationDate DMTF WMI ps etimes started_at assess_mcp_skew", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YZ40GGK5Q4S0G3YMFPW", + "id": "01M1Y06WGDH15NQ6TC0M61PKG0", + "kind": "memory", + "score": 0.9957948923110962, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 689.1389, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 924, + "mcp_result_bytes": 1013, + "wire_bytes": 1049, + "reported_used_tokens": 1013, + "working_set_bytes": 284434432, + "peak_working_set_bytes": 285339648 + }, + { + "query": "how do I read a process start time on both Windows and Linux in pure Rust?", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YZ40GGK5Q4S0G3YMFPW", + "id": "01M1Y06X5M7QVAG77JNXGEPTSY", + "kind": "memory", + "score": 0.99687659740448, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 895.991, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 921, + "mcp_result_bytes": 1010, + "wire_bytes": 1046, + "reported_used_tokens": 1010, + "working_set_bytes": 284442624, + "peak_working_set_bytes": 285364224 + }, + { + "query": "processes_locking_target decide_preflight_action BufRead Write update.rs", + "ranked": [ + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05Z0A5WK204VYT0BQ2A96", + "id": "01M1Y06Y1NJ5T6AFCCEWXK7Y7R", + "kind": "memory", + "score": 0.9995336532592772, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 760.8530999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1133, + "mcp_result_bytes": 1214, + "wire_bytes": 1250, + "reported_used_tokens": 1214, + "working_set_bytes": 284508160, + "peak_working_set_bytes": 285425664 + }, + { + "query": "how should I reuse the existing process enumerator in the update preflight check to avoid a second PowerShell query?", + "ranked": [ + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05Z0A5WK204VYT0BQ2A96", + "id": "01M1Y06YT1GZ3DEBWMMYK9RZVQ", + "kind": "memory", + "score": 0.9973384737968444, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 969.5202, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1132, + "mcp_result_bytes": 1213, + "wire_bytes": 1249, + "reported_used_tokens": 1213, + "working_set_bytes": 284774400, + "peak_working_set_bytes": 285687808 + }, + { + "query": "cfg_attr windows allow dead_code parse_unix_ps cross-platform tests", + "ranked": [ + "cfg-cross-platform-dead-code", + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05Z1FDES73B8EQHWJPVMA", + "id": "01M1Y06ZR1W4HM8ZPH5HCA5F2C", + "kind": "memory", + "score": 0.9999476671218872, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + }, + { + "expansion_handle": "memory:01M1Y05YZ40GGK5Q4S0G3YMFPW", + "id": "01M1Y06ZR2F6HJGVGZV7DHB0BA", + "kind": "memory", + "score": 0.9764312505722046, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 724.8495, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1518, + "mcp_result_bytes": 1625, + "wire_bytes": 1661, + "reported_used_tokens": 1625, + "working_set_bytes": 284942336, + "peak_working_set_bytes": 285855744 + }, + { + "query": "how do I keep a function that is only called on Unix from triggering dead_code warnings on Windows?", + "ranked": [ + "cfg-cross-platform-dead-code" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05Z1FDES73B8EQHWJPVMA", + "id": "01M1Y070EPJ9VW4BANQ21W7VC0", + "kind": "memory", + "score": 0.9988092184066772, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 888.7517, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 939, + "reported_used_tokens": 903, + "working_set_bytes": 285220864, + "peak_working_set_bytes": 286130176 + }, + { + "query": "deadlocking a Rust mutex in integration tests", + "ranked": [ + "mutex-deadlock-user-brain-disabled", + "testing-serial-vs-parallel", + "kimetsu-query-stemming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YAM1J3BB2GHQRB5M0FP", + "id": "01M1Y071B5MEB9M2CM35JHAD6W", + "kind": "memory", + "score": 0.9997490048408508, + "summary": "project:fact - [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure \u2014 `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + }, + { + "expansion_handle": "memory:01M1Y061HRKB2V85Z6MW33WPRE", + "id": "01M1Y071B51PRX6B9KJN9WC89D", + "kind": "memory", + "score": 0.9057517647743224, + "summary": "project:fact - [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`)." + }, + { + "expansion_handle": "memory:01M1Y062H2NN1S48YS74CA719R", + "id": "01M1Y071B5N89S43FRQT57Y90M", + "kind": "memory", + "score": 0.4889622032642365, + "summary": "project:fact - [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 865.5951, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1930, + "mcp_result_bytes": 2063, + "wire_bytes": 2099, + "reported_used_tokens": 2063, + "working_set_bytes": 285224960, + "peak_working_set_bytes": 286134272 + }, + { + "query": "benchmarking retrieval quality across embedders", + "ranked": [ + "kimetsu-bench-remote-embedder-singleton", + "onnx-quantization-drift", + "cargo-feature-unification-embeddings" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y062K14KTD5D5SR7NWV35P", + "id": "01M1Y0725ABHJY4EHSRKRVE560", + "kind": "memory", + "score": 0.988014280796051, + "summary": "project:fact - [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval." + }, + { + "expansion_handle": "memory:01M1Y060DK4AZTG8FQ5SZ82QD8", + "id": "01M1Y0725AWBQDV85KN2NJDHF5", + "kind": "memory", + "score": 0.985597550868988, + "summary": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals \u2014 cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + }, + { + "expansion_handle": "memory:01M1Y05YFMXS61RMTQ16GE6735", + "id": "01M1Y0725A8V7G2P357R4FPQV6", + "kind": "memory", + "score": 0.5341982841491699, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 713.9199000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2537, + "mcp_result_bytes": 2658, + "wire_bytes": 2694, + "reported_used_tokens": 2658, + "working_set_bytes": 285229056, + "peak_working_set_bytes": 286134272 + }, + { + "query": "process memory working set RSS peak measurement Windows", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 891.3518, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 285384704, + "peak_working_set_bytes": 286273536 + }, + { + "query": "cloning a git repository server-side into a managed checkout", + "ranked": [ + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YBPJ3QEY9DGV3Y39QED", + "id": "01M1Y073QHJ9G4DP0P40DZS18A", + "kind": "memory", + "score": 0.9466677904129028, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 742.6744, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1261, + "mcp_result_bytes": 1342, + "wire_bytes": 1378, + "reported_used_tokens": 1342, + "working_set_bytes": 285671424, + "peak_working_set_bytes": 286576640 + }, + { + "query": "SigV4 signing HTTP requests in Rust", + "ranked": [ + "aws-presigned-urls", + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0620B2W192A9Y86X0ZB3D", + "id": "01M1Y074EVDWQXA5MPG7KYTSSW", + "kind": "memory", + "score": 0.9992632269859314, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + }, + { + "expansion_handle": "memory:01M1Y05YPKKNJWV8P7EZDPZJTG", + "id": "01M1Y074EV8EH9CQSVYKSAAEH8", + "kind": "memory", + "score": 0.9991399049758912, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1Y05YH1NQKFMZ0R78E7Z8SP", + "id": "01M1Y074EVBVRF7R511NMJPYQ2", + "kind": "memory", + "score": 0.9803794622421264, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 0.5, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 838.6473, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2840, + "mcp_result_bytes": 2985, + "wire_bytes": 3021, + "reported_used_tokens": 2985, + "working_set_bytes": 285876224, + "peak_working_set_bytes": 286769152 + }, + { + "query": "cargo test --workspace feature flag changes broke my unit tests", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-dev-dep-leak", + "ci-flaky-quarantine" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YFMXS61RMTQ16GE6735", + "id": "01M1Y0759HJF3HX3XEJKRSMNDN", + "kind": "memory", + "score": 0.997899889945984, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1Y05ZDSAHJXT2TMGQG83BWE", + "id": "01M1Y0759HP3CZ2JDW7D729NXR", + "kind": "memory", + "score": 0.9901249408721924, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + }, + { + "expansion_handle": "memory:01M1Y06288TD3HYPJQNP7S1ZNB", + "id": "01M1Y0759HSAKAA32VKBD2H9DQ", + "kind": "memory", + "score": 0.835382342338562, + "summary": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal \u2014 a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 754.432, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2383, + "mcp_result_bytes": 2504, + "wire_bytes": 2540, + "reported_used_tokens": 2504, + "working_set_bytes": 286167040, + "peak_working_set_bytes": 287076352 + }, + { + "query": "how do I make pasta carbonara?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 771.2394, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 286216192, + "peak_working_set_bytes": 287129600 + }, + { + "query": "what is the offside rule in football?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 968.4507, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 286248960, + "peak_working_set_bytes": 287154176 + }, + { + "query": "best way to train for a half marathon", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 972.4001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 286552064, + "peak_working_set_bytes": 287469568 + }, + { + "query": "my test passes when I run it alone but fails under cargo test --workspace", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YFMXS61RMTQ16GE6735", + "id": "01M1Y078P5K5BDNTRG368ET6TX", + "kind": "memory", + "score": 0.9907942414283752, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1Y05ZDSAHJXT2TMGQG83BWE", + "id": "01M1Y078P5Z1RCKD3AZRDYPQQ1", + "kind": "memory", + "score": 0.986136794090271, + "summary": "project:fact - [2026-09-07] [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 952.4747, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1863, + "mcp_result_bytes": 1966, + "wire_bytes": 2002, + "reported_used_tokens": 1966, + "working_set_bytes": 287043584, + "peak_working_set_bytes": 287956992 + }, + { + "query": "all the project tests started hanging forever after I added my new test", + "ranked": [ + "cargo-feature-unification-embeddings", + "tokio-runtime-in-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YFMXS61RMTQ16GE6735", + "id": "01M1Y079M1WG7XHB4RRJ6N3TBJ", + "kind": "memory", + "score": 0.774284839630127, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1Y060XP0KSRSNNYT7NE7D4W", + "id": "01M1Y079M1ZPKTDY3KTTN57H9E", + "kind": "memory", + "score": 0.33030807971954346, + "summary": "project:fact - [2026-09-07] [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 903.3887000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1763, + "mcp_result_bytes": 1874, + "wire_bytes": 1910, + "reported_used_tokens": 1874, + "working_set_bytes": 287182848, + "peak_working_set_bytes": 288088064 + }, + { + "query": "my integration test silently wrote memories into my real home brain instead of the temp workspace", + "ranked": [ + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YS4YPH8D67NSXJGT1SS", + "id": "01M1Y07AFHCRP8JKTHC9J29PQ8", + "kind": "memory", + "score": 0.9922831654548644, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 880.3425, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 780, + "mcp_result_bytes": 861, + "wire_bytes": 897, + "reported_used_tokens": 861, + "working_set_bytes": 287240192, + "peak_working_set_bytes": 288153600 + }, + { + "query": "where should the env-var opt-out check live for a cleanup feature triggered from a hot code path", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YRBDY77MTARFTEY6ESW", + "id": "01M1Y07BB0X99T4D4B25N29G80", + "kind": "memory", + "score": 0.9952055215835572, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 917.7221, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 761, + "mcp_result_bytes": 842, + "wire_bytes": 878, + "reported_used_tokens": 842, + "working_set_bytes": 287256576, + "peak_working_set_bytes": 288169984 + }, + { + "query": "the brain database file stays huge on Windows even after deleting most rows", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 896.4202, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 287313920, + "peak_working_set_bytes": 288231424 + }, + { + "query": "re-importing the same exported memories file counts them as new instead of deduplicated", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YX5TE9MYEX6EDV1N9YT", + "id": "01M1Y07D40C54M0ZJ71GZ0C158", + "kind": "memory", + "score": 0.9878425598144532, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount \u2014 both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 900.6208, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 965, + "mcp_result_bytes": 1046, + "wire_bytes": 1082, + "reported_used_tokens": 1046, + "working_set_bytes": 287326208, + "peak_working_set_bytes": 288239616 + }, + { + "query": "a helper function only called on Unix at runtime fails the dead-code lint on the Windows build", + "ranked": [ + "cfg-cross-platform-dead-code", + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05Z1FDES73B8EQHWJPVMA", + "id": "01M1Y07DZYAZVFW8V8A7YE00KA", + "kind": "memory", + "score": 0.9971064925193788, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + }, + { + "expansion_handle": "memory:01M1Y05Z0A5WK204VYT0BQ2A96", + "id": "01M1Y07DZYFRRHGNC89PW5DGN1", + "kind": "memory", + "score": 0.427912950515747, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1095.172, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1728, + "mcp_result_bytes": 1827, + "wire_bytes": 1863, + "reported_used_tokens": 1827, + "working_set_bytes": 287399936, + "peak_working_set_bytes": 288317440 + }, + { + "query": "the second Terminal-Bench trial always crashes even though the first one passes", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YV5H4YVBX03WZFS7KSE", + "id": "01M1Y07F2F7AP4ZAP92NN54B9W", + "kind": "memory", + "score": 0.9963042736053468, + "summary": "project:fact - [2026-09-07] [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 915.91, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1038, + "mcp_result_bytes": 1119, + "wire_bytes": 1155, + "reported_used_tokens": 1119, + "working_set_bytes": 287440896, + "peak_working_set_bytes": 288350208 + }, + { + "query": "how does doctor tell a running MCP server process is older than the kimetsu binary on disk", + "ranked": [ + "kimetsu-daemon-lifecycle", + "process-start-time-cross-platform", + "mcp-env-propagation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0629C2P66GZ8JJCVPWPR9", + "id": "01M1Y07FZ0H0MTYMAGPJDXR8P1", + "kind": "memory", + "score": 0.9985345602035522, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1Y05YZ40GGK5Q4S0G3YMFPW", + "id": "01M1Y07FZ0DNQQ2GWSE35E1TWG", + "kind": "memory", + "score": 0.9438157677650452, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + }, + { + "expansion_handle": "memory:01M1Y061RKZW8EMQN44S984P8P", + "id": "01M1Y07FZ0929WCTXYZ1RH8DKN", + "kind": "memory", + "score": 0.33611738681793213, + "summary": "project:fact - [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment \u2014 changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 0.5, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 915.9819, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1936, + "mcp_result_bytes": 2061, + "wire_bytes": 2097, + "reported_used_tokens": 2061, + "working_set_bytes": 287473664, + "peak_working_set_bytes": 288387072 + }, + { + "query": "the self-update preflight needs the list of running kimetsu processes without re-running the OS query", + "ranked": [ + "windows-update-process-locking", + "kimetsu-daemon-lifecycle" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05Z0A5WK204VYT0BQ2A96", + "id": "01M1Y07GW7V6YW7NN1TJE4E15F", + "kind": "memory", + "score": 0.9972410202026368, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + }, + { + "expansion_handle": "memory:01M1Y0629C2P66GZ8JJCVPWPR9", + "id": "01M1Y07GW7MCTP1T9TVP8PWV3Z", + "kind": "memory", + "score": 0.8902595043182373, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 906.6437000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1658, + "mcp_result_bytes": 1757, + "wire_bytes": 1793, + "reported_used_tokens": 1757, + "working_set_bytes": 287473664, + "peak_working_set_bytes": 288387072 + }, + { + "query": "parsing the WMI DMTF CreationDate timestamp into epoch seconds without extra crates", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YZ40GGK5Q4S0G3YMFPW", + "id": "01M1Y07HQRMFVCR1Z2G7N7QSYC", + "kind": "memory", + "score": 0.9258026480674744, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 920.3574, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 924, + "mcp_result_bytes": 1013, + "wire_bytes": 1049, + "reported_used_tokens": 1013, + "working_set_bytes": 287473664, + "peak_working_set_bytes": 288387072 + }, + { + "query": "calling Bedrock InvokeModel from blocking reqwest without the aws sdk", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking", + "aws-region-resolution", + "aws-retry-throttling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YH1NQKFMZ0R78E7Z8SP", + "id": "01M1Y07JMKZNHDZ4NNAA7B149H", + "kind": "memory", + "score": 0.9991798996925354, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1Y05YPKKNJWV8P7EZDPZJTG", + "id": "01M1Y07JMK7M1C0M3TDFRJDG82", + "kind": "memory", + "score": 0.999082326889038, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1Y061Y6112PMFKN5GEJZE8M", + "id": "01M1Y07JMKMXJQJ559H7GHG5ME", + "kind": "memory", + "score": 0.8391201496124268, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1Y061Z7A6RC4W57V7Z3CXS9", + "id": "01M1Y07JMKXQ23M72FZRCN5W6Y", + "kind": "memory", + "score": 0.4906356632709503, + "summary": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with \u00b125% jitter." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1007.8635, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3330, + "mcp_result_bytes": 3509, + "wire_bytes": 3545, + "reported_used_tokens": 3509, + "working_set_bytes": 287473664, + "peak_working_set_bytes": 288387072 + }, + { + "query": "how do I rotate the encryption key protecting the kimetsu brain database", + "ranked": [ + "kimetsu-eval-fixture-shape" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y062M3CFFQGT02EBZTJSHN", + "id": "01M1Y07KMSTZSS6R25V5VCH5Z9", + "kind": "memory", + "score": 0.8046634197235107, + "summary": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` \u2014 a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases)." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 996.8795, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 817, + "mcp_result_bytes": 942, + "wire_bytes": 978, + "reported_used_tokens": 942, + "working_set_bytes": 287506432, + "peak_working_set_bytes": 288411648 + }, + { + "query": "which tokio runtime worker-thread settings does the kimetsu MCP server use", + "ranked": [ + "tokio-blocking-in-async", + "tokio-runtime-in-tests", + "mcp-stdout-protocol" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060WH1VTPAKJ89CENJJXP", + "id": "01M1Y07MKC08XH1QTJR63AQDQ3", + "kind": "memory", + "score": 0.9973159432411194, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + }, + { + "expansion_handle": "memory:01M1Y060XP0KSRSNNYT7NE7D4W", + "id": "01M1Y07MKCWSS2KJWS27YX26AS", + "kind": "memory", + "score": 0.8583173155784607, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + }, + { + "expansion_handle": "memory:01M1Y061PEX980HR5500H8P7ZH", + "id": "01M1Y07MKC7095SQEB4QQC9W1T", + "kind": "memory", + "score": 0.8141786456108093, + "summary": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 978.3624, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1847, + "mcp_result_bytes": 1972, + "wire_bytes": 2008, + "reported_used_tokens": 1972, + "working_set_bytes": 288198656, + "peak_working_set_bytes": 289107968 + }, + { + "query": "how does kimetsu sync memories between two machines over the network", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 896.3438, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288231424, + "peak_working_set_bytes": 289140736 + }, + { + "query": "recovering a corrupted usearch ANN index after a power loss", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 808.9003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288235520, + "peak_working_set_bytes": 289144832 + }, + { + "query": "what postgres schema should I use to store kimetsu memories", + "ranked": [ + "kimetsu-memory-scopes", + "testing-fixture-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y062B89NB3SMAHX7SX830R", + "id": "01M1Y07Q7GRTMA4HDBY8D4J6TH", + "kind": "memory", + "score": 0.9890244603157043, + "summary": "project:fact - [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available \u2014 if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope." + }, + { + "expansion_handle": "memory:01M1Y061NJMXF06HD6WSDEN8JZ", + "id": "01M1Y07Q7GGVA8J9GP9X9FD7NK", + "kind": "memory", + "score": 0.8922504782676697, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 896.2509, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1389, + "mcp_result_bytes": 1488, + "wire_bytes": 1524, + "reported_used_tokens": 1488, + "working_set_bytes": 288407552, + "peak_working_set_bytes": 289304576 + }, + { + "query": "the whole CI job just froze forever with no failure output after my latest test PR", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 896.4879, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288411648, + "peak_working_set_bytes": 289325056 + }, + { + "query": "running the test suite left junk state in my home directory", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 953.5631, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288411648, + "peak_working_set_bytes": 289325056 + }, + { + "query": "I deleted a bunch of old rows but the file on disk is still the same size", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 887.1536, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288411648, + "peak_working_set_bytes": 289325056 + }, + { + "query": "adding one new crate quietly changed how the whole workspace builds", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-lockfile-drift", + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YFMXS61RMTQ16GE6735", + "id": "01M1Y07TRS735JBK6YTB2ACC3M", + "kind": "memory", + "score": 0.9941080808639526, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1Y05ZBTM7S2Y9XXR5EA4C7E", + "id": "01M1Y07TRS3MFR8CKYA0G4STZ9", + "kind": "memory", + "score": 0.9717232584953308, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this \u2014 it errors on any lockfile diff." + }, + { + "expansion_handle": "memory:01M1Y05ZDSAHJXT2TMGQG83BWE", + "id": "01M1Y07TRS60C63EZAYVHQZAFJ", + "kind": "memory", + "score": 0.9183088541030884, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 962.7750000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2374, + "mcp_result_bytes": 2495, + "wire_bytes": 2531, + "reported_used_tokens": 2495, + "working_set_bytes": 288415744, + "peak_working_set_bytes": 289333248 + }, + { + "query": "we cannot pull an async runtime into the agent just to talk to AWS", + "ranked": [ + "tokio-blocking-in-async", + "tokio-runtime-in-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060WH1VTPAKJ89CENJJXP", + "id": "01M1Y07VSS41JJE4X0Q7M7YX86", + "kind": "memory", + "score": 0.7520647644996643, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + }, + { + "expansion_handle": "memory:01M1Y060XP0KSRSNNYT7NE7D4W", + "id": "01M1Y07VSSFJ88S7FJQ7TNXD64", + "kind": "memory", + "score": 0.7233642935752869, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1154.2941, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1369, + "mcp_result_bytes": 1476, + "wire_bytes": 1512, + "reported_used_tokens": 1476, + "working_set_bytes": 288800768, + "peak_working_set_bytes": 289705984 + }, + { + "query": "users should be able to tell which build variant they installed from the version output", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 984.8974000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288821248, + "peak_working_set_bytes": 289783808 + }, + { + "query": "what gotchas should I expect writing process-inspection code that works on both Windows and Unix?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 884.4479, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288948224, + "peak_working_set_bytes": 289865728 + }, + { + "query": "why might tests behave differently on my machine than in the full CI run?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 858.9310999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 289468416, + "peak_working_set_bytes": 290381824 + }, + { + "query": "what do I need to know before wiring kimetsu into a brand new host agent?", + "ranked": [ + "bridge-target-enum-seams", + "kimetsu-daemon-lifecycle", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YJSTY39A6KQMMSK2DH6", + "id": "01M1Y07ZGGGQE4GQF1EMXJ7MC6", + "kind": "memory", + "score": 0.9741999506950378, + "summary": "project:fact - [2026-09-07] [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + }, + { + "expansion_handle": "memory:01M1Y0629C2P66GZ8JJCVPWPR9", + "id": "01M1Y07ZGH5R0H5REWM9XDF6KZ", + "kind": "memory", + "score": 0.9637662768363952, + "summary": "project:fact - [2026-09-07] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1Y05YDQZCEVNBN7TDX4XC9B", + "id": "01M1Y07ZGHNWTWAP6GSPEYVGHJ", + "kind": "memory", + "score": 0.4149944484233856, + "summary": "project:fact - [2026-09-07] [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 0.6666666666666666, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 947.5276, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2390, + "mcp_result_bytes": 2555, + "wire_bytes": 2591, + "reported_used_tokens": 2555, + "working_set_bytes": 289480704, + "peak_working_set_bytes": 290394112 + }, + { + "query": "tell me everything relevant to running kimetsu against AWS", + "ranked": [ + "kimetsu-mrr-metric", + "aws-credentials-chain", + "cargo-feature-unification-embeddings", + "kimetsu-eval-fixture-shape" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y062N6MYSHTZCNN349MES6", + "id": "01M1Y080EEVSS4P1RF9ZKPF3BP", + "kind": "memory", + "score": 0.984548270702362, + "summary": "project:fact - [tags: kimetsu bench mrr recall metrics evaluation] kimetsu bench reports MRR (Mean Reciprocal Rank) and Recall@K. MRR is 1/rank_of_first_relevant_result, averaged across cases; it penalizes models that rank the correct answer 2nd or 3rd. Recall@K is the fraction of cases where at least one relevant answer appears in the top K." + }, + { + "expansion_handle": "memory:01M1Y061WWY2XRNJE51QHGVRAM", + "id": "01M1Y080EEPRQJXP40EXN0EJ8T", + "kind": "memory", + "score": 0.9737622141838074, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + }, + { + "expansion_handle": "memory:01M1Y05YFMXS61RMTQ16GE6735", + "id": "01M1Y080EEYAW9F0DWYKZ4YS2F", + "kind": "memory", + "score": 0.9726329445838928, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1Y062M3CFFQGT02EBZTJSHN", + "id": "01M1Y080EE788CPPJKFSFNFE34", + "kind": "memory", + "score": 0.9641559720039368, + "summary": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` \u2014 a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases)." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 939.0904, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2883, + "mcp_result_bytes": 3066, + "wire_bytes": 3102, + "reported_used_tokens": 3066, + "working_set_bytes": 289484800, + "peak_working_set_bytes": 290394112 + }, + { + "query": "ingesting a cloned repo when the brain lives under a different root", + "ranked": [ + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YBPJ3QEY9DGV3Y39QED", + "id": "01M1Y081C9KTDT4B1S1JYQK43P", + "kind": "memory", + "score": 0.9995300769805908, + "summary": "project:fact - [2026-09-07] [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 901.9446, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1274, + "mcp_result_bytes": 1355, + "wire_bytes": 1391, + "reported_used_tokens": 1355, + "working_set_bytes": 289488896, + "peak_working_set_bytes": 290394112 + }, + { + "query": "streamable-http transport entry for openclaw.json with a bearer token", + "ranked": [ + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YDQZCEVNBN7TDX4XC9B", + "id": "01M1Y0827GM7AFR8JA29KT74ZV", + "kind": "memory", + "score": 0.9921918511390686, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 884.4945, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 996, + "mcp_result_bytes": 1125, + "wire_bytes": 1161, + "reported_used_tokens": 1125, + "working_set_bytes": 289492992, + "peak_working_set_bytes": 290406400 + }, + { + "query": "serializing ingests with a tokio mutex to avoid checkout races", + "ranked": [ + "remote-ingest-split-roots", + "testing-serial-vs-parallel", + "tokio-select-cancellation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YBPJ3QEY9DGV3Y39QED", + "id": "01M1Y08338A0E6JJQYEKXTPJW7", + "kind": "memory", + "score": 0.9795480966567992, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1Y061HRKB2V85Z6MW33WPRE", + "id": "01M1Y0833841DNA459PMDZBRD1", + "kind": "memory", + "score": 0.9425267577171326, + "summary": "project:fact - [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`)." + }, + { + "expansion_handle": "memory:01M1Y060Z0MRX8BDRSVJPYBRVF", + "id": "01M1Y08338NRW3HD78YDXDQYR7", + "kind": "memory", + "score": 0.5619664192199707, + "summary": "project:fact - [tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1075.3500000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2376, + "mcp_result_bytes": 2493, + "wire_bytes": 2529, + "reported_used_tokens": 2493, + "working_set_bytes": 289509376, + "peak_working_set_bytes": 290426880 + }, + { + "query": "percent-encoding the colon in the bedrock model id for the invoke URL", + "ranked": [ + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YH1NQKFMZ0R78E7Z8SP", + "id": "01M1Y08457YW08E4MMRGZ634T6", + "kind": "memory", + "score": 0.8341025710105896, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1006.7360000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1204, + "mcp_result_bytes": 1293, + "wire_bytes": 1329, + "reported_used_tokens": 1293, + "working_set_bytes": 289611776, + "peak_working_set_bytes": 290521088 + }, + { + "query": "deduplicating re-imported memories against pre-existing ids", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YX5TE9MYEX6EDV1N9YT", + "id": "01M1Y0854HPQ2MEFK96BPFAKGV", + "kind": "memory", + "score": 0.9991393089294434, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount \u2014 both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1074.5648999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 966, + "mcp_result_bytes": 1047, + "wire_bytes": 1083, + "reported_used_tokens": 1047, + "working_set_bytes": 289619968, + "peak_working_set_bytes": 290521088 + }, + { + "query": "parsing DMTF datetimes", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YZ40GGK5Q4S0G3YMFPW", + "id": "01M1Y08668EKASJ8NFB0KGXMF6", + "kind": "memory", + "score": 0.9934942126274108, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 784.3335999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 924, + "mcp_result_bytes": 1013, + "wire_bytes": 1049, + "reported_used_tokens": 1013, + "working_set_bytes": 289619968, + "peak_working_set_bytes": 290521088 + }, + { + "query": "how should install derive a stable identifier from the git remote URL?", + "ranked": [ + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YDQZCEVNBN7TDX4XC9B", + "id": "01M1Y086YNPZHDY7NV9PRCWNH6", + "kind": "memory", + "score": 0.98285174369812, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1010.0893, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 995, + "mcp_result_bytes": 1124, + "wire_bytes": 1160, + "reported_used_tokens": 1124, + "working_set_bytes": 289677312, + "peak_working_set_bytes": 290586624 + }, + { + "query": "the secret token must not end up written into the host config file", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1077.2346, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 289751040, + "peak_working_set_bytes": 290664448 + }, + { + "query": "keep the cleanup logic unit-testable without touching environment variables", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1013.1093, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 289751040, + "peak_working_set_bytes": 290664448 + }, + { + "query": "how do we stop the server from cloning arbitrary repos clients request?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1014.0511999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 289751040, + "peak_working_set_bytes": 290664448 + }, + { + "query": "make sure a wrong guess about a host plugin API never breaks that host", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YKXXK0W4ER0PGC80N1C", + "id": "01M1Y08AZTY0301EX6EV2HRNQF", + "kind": "memory", + "score": 0.928434193134308, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 876.0531000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 803, + "mcp_result_bytes": 892, + "wire_bytes": 928, + "reported_used_tokens": 892, + "working_set_bytes": 289751040, + "peak_working_set_bytes": 290668544 + }, + { + "query": "which wire-format trick lets us reuse the existing Anthropic request builder for AWS?", + "ranked": [ + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YH1NQKFMZ0R78E7Z8SP", + "id": "01M1Y08BTQ9RFV6DEN40MX8ZK7", + "kind": "memory", + "score": 0.9748817682266236, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1083.1806, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1203, + "mcp_result_bytes": 1292, + "wire_bytes": 1328, + "reported_used_tokens": 1292, + "working_set_bytes": 289800192, + "peak_working_set_bytes": 290713600 + }, + { + "query": "the self-update froze because something was still holding the executable", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1056.811, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 290054144, + "peak_working_set_bytes": 290959360 + }, + { + "query": "our notes about the extension API turned out wrong once we read the actual repo", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1032.815, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 290238464, + "peak_working_set_bytes": 291143680 + }, + { + "query": "half the benchmark trials die right after the first one finishes", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 963.8573, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 290283520, + "peak_working_set_bytes": 291196928 + }, + { + "query": "I need this parser visible to tests on every OS even though only one OS calls it", + "ranked": [ + "cfg-cross-platform-dead-code" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05Z1FDES73B8EQHWJPVMA", + "id": "01M1Y08FVZ3MBVZE80AB5N23WJ", + "kind": "memory", + "score": 0.36490198969841, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 999.7329000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 939, + "reported_used_tokens": 903, + "working_set_bytes": 290283520, + "peak_working_set_bytes": 291196928 + }, + { + "query": "the config file content refuses to parse even though the TOML looks valid", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YY75RMS67WX826FQJEE", + "id": "01M1Y08GVC647P3Z07WXBSG942", + "kind": "memory", + "score": 0.6614054441452026, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1041.4704, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 733, + "mcp_result_bytes": 814, + "wire_bytes": 850, + "reported_used_tokens": 814, + "working_set_bytes": 290291712, + "peak_working_set_bytes": 291205120 + }, + { + "query": "the remote server must refresh its checkout before answering file queries", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1051.6085, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 290291712, + "peak_working_set_bytes": 291205120 + }, + { + "query": "tests must not climb to a parent git repository when resolving project paths", + "ranked": [ + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YS4YPH8D67NSXJGT1SS", + "id": "01M1Y08JX5NPC7BD2PSPPHSQ3H", + "kind": "memory", + "score": 0.9839988350868224, + "summary": "project:fact - [2026-09-07] [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1103.9856, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 794, + "mcp_result_bytes": 875, + "wire_bytes": 911, + "reported_used_tokens": 875, + "working_set_bytes": 290295808, + "peak_working_set_bytes": 291205120 + }, + { + "query": "how do I test request signing deterministically when timestamps change every run?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1053.6784, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 290295808, + "peak_working_set_bytes": 291205120 + }, + { + "query": "adding a new variant to the host target enum - which places will I forget to update?", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YJSTY39A6KQMMSK2DH6", + "id": "01M1Y08N086PENEH3RP57EQ5C0", + "kind": "memory", + "score": 0.885076105594635, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1087.0495, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1058, + "mcp_result_bytes": 1139, + "wire_bytes": 1175, + "reported_used_tokens": 1139, + "working_set_bytes": 290295808, + "peak_working_set_bytes": 291205120 + }, + { + "query": "how do I enable GPU acceleration for kimetsu embedding inference", + "ranked": [ + "mcp-tool-timeouts", + "kimetsu-proactive-hooks" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061QHG7EX7KYBKD86CQYC", + "id": "01M1Y08P277TCVEHF2QPCFSC72", + "kind": "memory", + "score": 0.9826309084892272, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + }, + { + "expansion_handle": "memory:01M1Y062DDSWX11Z89QR995TW9", + "id": "01M1Y08P27A6DPYD3R3WPR7Q5P", + "kind": "memory", + "score": 0.8807981610298157, + "summary": "project:fact - [tags: kimetsu proactive hooks context injection] kimetsu's proactive context injection runs before each agent turn (pre-turn hook) and injects relevant memories into the system prompt prefix. The hook invocation adds latency to the first token: embedding inference + vector search + reranking + context formatting. On a cold start, this can be 1-3 seconds." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 1014.3146999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1377, + "mcp_result_bytes": 1476, + "wire_bytes": 1512, + "reported_used_tokens": 1476, + "working_set_bytes": 290295808, + "peak_working_set_bytes": 291205120 + }, + { + "query": "how do I throttle kimetsu API spend per month", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 980.9875999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 290295808, + "peak_working_set_bytes": 291209216 + }, + { + "query": "can the kimetsu brain database be stored in S3 instead of on disk", + "ranked": [ + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0620B2W192A9Y86X0ZB3D", + "id": "01M1Y08R0K325VHG3HDS2ZFDB9", + "kind": "memory", + "score": 0.38596054911613464, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 979.9511, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 875, + "mcp_result_bytes": 956, + "wire_bytes": 992, + "reported_used_tokens": 956, + "working_set_bytes": 290304000, + "peak_working_set_bytes": 291217408 + }, + { + "query": "how do I plug a custom tokenizer into the FTS index", + "ranked": [ + "sqlite-fts5-tokenizer" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05Z67QE5WZ6CHYBDBREP7", + "id": "01M1Y08RZWN7612PK15AF82RC5", + "kind": "memory", + "score": 0.9691632390022278, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 1048.0276, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 671, + "mcp_result_bytes": 756, + "wire_bytes": 792, + "reported_used_tokens": 756, + "working_set_bytes": 290459648, + "peak_working_set_bytes": 291360768 + }, + { + "query": "what should I check when kimetsu behaves differently on Windows than on Linux?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 997.121, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 290463744, + "peak_working_set_bytes": 291373056 + }, + { + "query": "what are the moving parts of the kimetsu remote deployment story?", + "ranked": [ + "kimetsu-write-tools-gate", + "ci-secrets-masking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y062EEFBC57YJ58J3CSXFD", + "id": "01M1Y08TZ7D3DHBHEQHMQNSACP", + "kind": "memory", + "score": 0.9729357361793518, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level \u2014 disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1Y0626EATKH9B42B4W92CC3", + "id": "01M1Y08TZ7FWFCET7JR8TANG9E", + "kind": "memory", + "score": 0.8412115573883057, + "summary": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output \u2014 but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1023.9429, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1410, + "mcp_result_bytes": 1509, + "wire_bytes": 1546, + "reported_used_tokens": 1509, + "working_set_bytes": 290586624, + "peak_working_set_bytes": 291500032 + }, + { + "query": "which lessons cover guarding behavior behind environment variables?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 922.3257, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 290709504, + "peak_working_set_bytes": 291610624 + }, + { + "query": "SQLite BUSY error under concurrent writes", + "ranked": [ + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05Z2BBHSJ0CDKCQ4XH9GE", + "id": "01M1Y08WWG1H1WPZ9GZYRTRHGQ", + "kind": "memory", + "score": 0.9978362917900084, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 891.023, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 898, + "mcp_result_bytes": 979, + "wire_bytes": 1016, + "reported_used_tokens": 979, + "working_set_bytes": 290721792, + "peak_working_set_bytes": 291614720 + }, + { + "query": "SQLite WAL mode breaks when the database is on a network share", + "ranked": [ + "sqlite-wal-network-drive", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05Z57SYAWW7723HQ5J9NG", + "id": "01M1Y08XQX3RMABSSFNTB5KYXW", + "kind": "memory", + "score": 0.999302864074707, + "summary": "project:fact - [2026-09-07] [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + }, + { + "expansion_handle": "memory:01M1Y05Z2BBHSJ0CDKCQ4XH9GE", + "id": "01M1Y08XQYZE0F8SZTXDB11KR4", + "kind": "memory", + "score": 0.9966553449630736, + "summary": "project:fact - [2026-09-07] [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1094.103, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1448, + "mcp_result_bytes": 1547, + "wire_bytes": 1584, + "reported_used_tokens": 1547, + "working_set_bytes": 290721792, + "peak_working_set_bytes": 291631104 + }, + { + "query": "my SQLite WAL database causes SQLITE_IOERR_LOCK on a mapped drive", + "ranked": [ + "sqlite-wal-network-drive" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05Z57SYAWW7723HQ5J9NG", + "id": "01M1Y08YT53PRBMF932088AVD6", + "kind": "memory", + "score": 0.99892657995224, + "summary": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1082.5716, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 748, + "mcp_result_bytes": 829, + "wire_bytes": 866, + "reported_used_tokens": 829, + "working_set_bytes": 290762752, + "peak_working_set_bytes": 291667968 + }, + { + "query": "FTS5 tokenizer configuration for Rust identifiers with underscores", + "ranked": [ + "sqlite-fts5-tokenizer", + "kimetsu-query-stemming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05Z67QE5WZ6CHYBDBREP7", + "id": "01M1Y08ZVYTSX4SCG90C2D8F0D", + "kind": "memory", + "score": 0.998104453086853, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + }, + { + "expansion_handle": "memory:01M1Y062H2NN1S48YS74CA719R", + "id": "01M1Y08ZVY22TXT6EC5RSFDW4H", + "kind": "memory", + "score": 0.7023860812187195, + "summary": "project:fact - [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1007.5083000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1212, + "mcp_result_bytes": 1331, + "wire_bytes": 1368, + "reported_used_tokens": 1331, + "working_set_bytes": 290762752, + "peak_working_set_bytes": 291667968 + }, + { + "query": "I switched the FTS5 tokenizer but search stopped returning results", + "ranked": [ + "sqlite-fts5-tokenizer" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05Z67QE5WZ6CHYBDBREP7", + "id": "01M1Y090VN0WAQ9XJM7478SG96", + "kind": "memory", + "score": 0.8194089531898499, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1074.3998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 670, + "mcp_result_bytes": 755, + "wire_bytes": 792, + "reported_used_tokens": 755, + "working_set_bytes": 290762752, + "peak_working_set_bytes": 291667968 + }, + { + "query": "optimal SQLite page size for storing embedding vectors", + "ranked": [ + "sqlite-page-size", + "onnx-dim-mismatch", + "onnx-cosine-vs-dot" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05Z77JXSSSNGC8GCACN6S", + "id": "01M1Y091X6Q2MM7Z5ABSGRCH4X", + "kind": "memory", + "score": 0.9990121126174928, + "summary": "project:fact - [tags: sqlite page_size performance rusqlite] SQLite's default page_size is 4096 bytes. For a write-heavy brain database with large BLOB payloads (embedding vectors), raising page_size to 16384 reduces fragmentation and improves sequential scan throughput. `PRAGMA page_size = 16384;` must be set BEFORE the first table is created \u2014 changing it on an existing database requires a VACUUM afterward to rebuild all pages." + }, + { + "expansion_handle": "memory:01M1Y060H9Y1TBKN4BXG507K53", + "id": "01M1Y091X69QXS91Q608X0NCZF", + "kind": "memory", + "score": 0.9881643056869508, + "summary": "project:fact - [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results \u2014 the ANN index shape mismatch isn't always caught at runtime." + }, + { + "expansion_handle": "memory:01M1Y060GAGCBYF97WMPA8SENM", + "id": "01M1Y091X6ZV8P1JHSW4ZQGQZK", + "kind": "memory", + "score": 0.9425415992736816, + "summary": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing \u2014 double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 934.8326, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1860, + "mcp_result_bytes": 1977, + "wire_bytes": 2014, + "reported_used_tokens": 1977, + "working_set_bytes": 290762752, + "peak_working_set_bytes": 291667968 + }, + { + "query": "ON DELETE CASCADE in SQLite does nothing \u2014 foreign keys not enforced", + "ranked": [ + "sqlite-foreign-keys-default-off" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05Z843AFP72Y2BY2XZ4Q0", + "id": "01M1Y092TDXBHHB4EMF2Y6XNFX", + "kind": "memory", + "score": 0.9996858835220336, + "summary": "project:fact - [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting \u2014 every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1078.8933, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 736, + "mcp_result_bytes": 817, + "wire_bytes": 854, + "reported_used_tokens": 817, + "working_set_bytes": 291123200, + "peak_working_set_bytes": 292028416 + }, + { + "query": "indexing a JSON metadata column in SQLite without a schema migration", + "ranked": [ + "sqlite-json1-extract", + "testing-fixture-drift", + "onnx-dim-mismatch", + "sqlite-partial-index" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05Z8Z4DP2CTNJTKSS5QXA", + "id": "01M1Y093W1TYWEFQQBNJ8ZX5Z6", + "kind": "memory", + "score": 0.9955366849899292, + "summary": "project:fact - [tags: sqlite json1 json_extract rusqlite] SQLite's json1 extension (built in since 3.38.0) lets you index and query JSONB columns with `json_extract(col, '$.field')`. To create a partial index over a JSON field: `CREATE INDEX idx ON memories (json_extract(metadata, '$.scope')) WHERE json_extract(metadata, '$.scope') IS NOT NULL;`. Use `json_each` for array fields." + }, + { + "expansion_handle": "memory:01M1Y061NJMXF06HD6WSDEN8JZ", + "id": "01M1Y093W1MFMNWDWKCATKDPV2", + "kind": "memory", + "score": 0.8227390646934509, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + }, + { + "expansion_handle": "memory:01M1Y060H9Y1TBKN4BXG507K53", + "id": "01M1Y093W1TKS6PXA80EYBPYBM", + "kind": "memory", + "score": 0.38374292850494385, + "summary": "project:fact - [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results \u2014 the ANN index shape mismatch isn't always caught at runtime." + }, + { + "expansion_handle": "memory:01M1Y05ZAXZGD4RX1ABAGTSF37", + "id": "01M1Y093W10HWSQ33JFYSG8R4P", + "kind": "memory", + "score": 0.3276048004627228, + "summary": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query \u2014 the planner uses the partial index only when the WHERE clause matches." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 993.4228, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2381, + "mcp_result_bytes": 2516, + "wire_bytes": 2553, + "reported_used_tokens": 2516, + "working_set_bytes": 291127296, + "peak_working_set_bytes": 292032512 + }, + { + "query": "prepare() vs prepare_cached() in rusqlite hot insert loop", + "ranked": [ + "sqlite-prepared-stmt-cache" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05ZA1Y2MV8GX3Z3ZBWBBZ", + "id": "01M1Y094VJZCD7FJF5FQFDTCMN", + "kind": "memory", + "score": 0.9993672966957092, + "summary": "project:fact - [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1004.7586, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 689, + "mcp_result_bytes": 770, + "wire_bytes": 807, + "reported_used_tokens": 770, + "working_set_bytes": 291135488, + "peak_working_set_bytes": 292036608 + }, + { + "query": "speed up bulk memory ingest by caching SQL statements", + "ranked": [ + "sqlite-prepared-stmt-cache" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05ZA1Y2MV8GX3Z3ZBWBBZ", + "id": "01M1Y095V8FBAE4G3DJXDBYYR8", + "kind": "memory", + "score": 0.9823396801948548, + "summary": "project:fact - [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1039.2075, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 688, + "mcp_result_bytes": 769, + "wire_bytes": 806, + "reported_used_tokens": 769, + "working_set_bytes": 291135488, + "peak_working_set_bytes": 292036608 + }, + { + "query": "partial index on deleted_at IS NULL for faster active memory queries", + "ranked": [ + "sqlite-partial-index" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05ZAXZGD4RX1ABAGTSF37", + "id": "01M1Y096V0ZFE27ZQJQHS1ZTJH", + "kind": "memory", + "score": 0.9988954067230223, + "summary": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query \u2014 the planner uses the partial index only when the WHERE clause matches." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1021.5577000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 794, + "mcp_result_bytes": 875, + "wire_bytes": 912, + "reported_used_tokens": 875, + "working_set_bytes": 291135488, + "peak_working_set_bytes": 292036608 + }, + { + "query": "the brain query is slow because it scans all rows including soft-deleted ones", + "ranked": [ + "sqlite-partial-index" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05ZAXZGD4RX1ABAGTSF37", + "id": "01M1Y097V43F8D5F3633P6033F", + "kind": "memory", + "score": 0.5760471224784851, + "summary": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query \u2014 the planner uses the partial index only when the WHERE clause matches." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1069.7898, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 793, + "mcp_result_bytes": 874, + "wire_bytes": 911, + "reported_used_tokens": 874, + "working_set_bytes": 291139584, + "peak_working_set_bytes": 292052992 + }, + { + "query": "Cargo.lock changed unexpectedly after adding a new workspace crate", + "ranked": [ + "cargo-lockfile-drift", + "cargo-feature-unification-embeddings", + "cargo-target-dir-sharing", + "cargo-patch-section" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05ZBTM7S2Y9XXR5EA4C7E", + "id": "01M1Y098WCH2W6SNT7X4QAZTDT", + "kind": "memory", + "score": 0.9991374015808104, + "summary": "project:fact - [2026-09-07] [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this \u2014 it errors on any lockfile diff." + }, + { + "expansion_handle": "memory:01M1Y05YFMXS61RMTQ16GE6735", + "id": "01M1Y098WC1QEAQ62S4FNFCYA2", + "kind": "memory", + "score": 0.9968542456626892, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1Y05ZETTM7QKBSZ1FY92RQ8", + "id": "01M1Y098WCBMH7R8K2ZNPFD4S1", + "kind": "memory", + "score": 0.9829630851745604, + "summary": "project:fact - [2026-09-07] [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps \u2014 use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + }, + { + "expansion_handle": "memory:01M1Y05ZHK2ZA3FKKK7GBBNKFB", + "id": "01M1Y098WD4PAWPZGDF0YTXZZ9", + "kind": "memory", + "score": 0.9262890815734864, + "summary": "project:fact - [2026-09-07] [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace \u2014 including transitive deps \u2014 that depend on `my-crate`. Remove the patch before publishing." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 890.4828, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3010, + "mcp_result_bytes": 3153, + "wire_bytes": 3190, + "reported_used_tokens": 3153, + "working_set_bytes": 291143680, + "peak_working_set_bytes": 292057088 + }, + { + "query": "how do I prevent CI from accepting a modified lockfile silently?", + "ranked": [ + "cargo-lockfile-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05ZBTM7S2Y9XXR5EA4C7E", + "id": "01M1Y099R9WKSWFZHDEFB5BHJW", + "kind": "memory", + "score": 0.9125379323959352, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this \u2014 it errors on any lockfile diff." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1013.7099000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 765, + "mcp_result_bytes": 846, + "wire_bytes": 883, + "reported_used_tokens": 846, + "working_set_bytes": 291143680, + "peak_working_set_bytes": 292057088 + }, + { + "query": "build.rs reruns on every incremental build even when nothing changed", + "ranked": [ + "cargo-build-script-rerun" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05ZCQF2N02VX6MVDA3NBK", + "id": "01M1Y09AR1GWKY4AH04GKYP7AC", + "kind": "memory", + "score": 0.9996689558029176, + "summary": "project:fact - [2026-09-07] [tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1039.4281, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 698, + "mcp_result_bytes": 779, + "wire_bytes": 816, + "reported_used_tokens": 779, + "working_set_bytes": 291278848, + "peak_working_set_bytes": 292192256 + }, + { + "query": "incremental cargo build is slow because build script runs every time", + "ranked": [ + "cargo-build-script-rerun" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05ZCQF2N02VX6MVDA3NBK", + "id": "01M1Y09BRHCNC2TBHW2653MKVG", + "kind": "memory", + "score": 0.9978280663490297, + "summary": "project:fact - [tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1026.6360000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 685, + "mcp_result_bytes": 766, + "wire_bytes": 803, + "reported_used_tokens": 766, + "working_set_bytes": 291278848, + "peak_working_set_bytes": 292192256 + }, + { + "query": "a dev-dependency is activating an embeddings feature in my production build", + "ranked": [ + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05ZDSAHJXT2TMGQG83BWE", + "id": "01M1Y09CSK9W8ASS7VH94Q0R8Z", + "kind": "memory", + "score": 0.9944193959236144, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1070.9234000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 931, + "mcp_result_bytes": 1012, + "wire_bytes": 1049, + "reported_used_tokens": 1012, + "working_set_bytes": 291282944, + "peak_working_set_bytes": 292192256 + }, + { + "query": "how do I prevent a test-only feature from bleeding into the non-test compilation?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1031.9533000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 291282944, + "peak_working_set_bytes": 292192256 + }, + { + "query": "linker errors in target/ caused by antivirus holding the exe file", + "ranked": [ + "windows-file-locking-av", + "cargo-target-dir-sharing" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0606RJBGNW8VJVHPWXP02", + "id": "01M1Y09ETH9ZH4Y8G0MEN6ZWG3", + "kind": "memory", + "score": 0.9997633099555968, + "summary": "project:fact - [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + }, + { + "expansion_handle": "memory:01M1Y05ZETTM7QKBSZ1FY92RQ8", + "id": "01M1Y09ETHKDASHW5C5W6M7A0W", + "kind": "memory", + "score": 0.7463976740837097, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps \u2014 use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 996.7163999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1523, + "mcp_result_bytes": 1622, + "wire_bytes": 1659, + "reported_used_tokens": 1622, + "working_set_bytes": 291282944, + "peak_working_set_bytes": 292192256 + }, + { + "query": "Access is denied (os error 5) when linking on Windows \u2014 how do I fix this?", + "ranked": [ + "windows-file-locking-av" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0606RJBGNW8VJVHPWXP02", + "id": "01M1Y09FSQAQ82K7Y7B0YBR7XV", + "kind": "memory", + "score": 0.9977193474769592, + "summary": "project:fact - [2026-09-07] [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 968.2474, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 769, + "mcp_result_bytes": 850, + "wire_bytes": 887, + "reported_used_tokens": 850, + "working_set_bytes": 291340288, + "peak_working_set_bytes": 292245504 + }, + { + "query": "incremental build broke with a type mismatch after switching branches", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05ZFT0B24FSK97XY8Y0WX", + "id": "01M1Y09GQW3WJYG9BF09Y4YGV0", + "kind": "memory", + "score": 0.7971777319908142, + "summary": "project:fact - [2026-09-07] [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1037.5047, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 890, + "mcp_result_bytes": 971, + "wire_bytes": 1008, + "reported_used_tokens": 971, + "working_set_bytes": 291422208, + "peak_working_set_bytes": 292331520 + }, + { + "query": "cargo reports a type error that references a type not in the codebase", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05ZFT0B24FSK97XY8Y0WX", + "id": "01M1Y09HRB8Y3X3FCBACQ9MXAV", + "kind": "memory", + "score": 0.7925198078155518, + "summary": "project:fact - [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 923.2426, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 877, + "mcp_result_bytes": 958, + "wire_bytes": 995, + "reported_used_tokens": 958, + "working_set_bytes": 291872768, + "peak_working_set_bytes": 292790272 + }, + { + "query": "compile fastembed at O2 in debug builds to avoid slow embedding inference", + "ranked": [ + "cargo-profile-override", + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05ZGMYJJMEZXJZA6WN6W3", + "id": "01M1Y09JNKGN7Q26V5T5GBR00Q", + "kind": "memory", + "score": 0.9932281374931335, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1Y061QHG7EX7KYBKD86CQYC", + "id": "01M1Y09JNKVBJE31YBZWRK4349", + "kind": "memory", + "score": 0.987656831741333, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1083.476, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1322, + "mcp_result_bytes": 1421, + "wire_bytes": 1458, + "reported_used_tokens": 1421, + "working_set_bytes": 291950592, + "peak_working_set_bytes": 292864000 + }, + { + "query": "override compilation profile for a single crate in a Cargo workspace", + "ranked": [ + "cargo-patch-section", + "cargo-profile-override", + "cargo-target-dir-sharing", + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05ZHK2ZA3FKKK7GBBNKFB", + "id": "01M1Y09KQ15F0VXR6VKHMZZESM", + "kind": "memory", + "score": 0.9984123706817628, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace \u2014 including transitive deps \u2014 that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1Y05ZGMYJJMEZXJZA6WN6W3", + "id": "01M1Y09KQ1WJAWVP1JQ3P44DT0", + "kind": "memory", + "score": 0.9979992508888244, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1Y05ZETTM7QKBSZ1FY92RQ8", + "id": "01M1Y09KQ1CEMYPTTT2ZWY218V", + "kind": "memory", + "score": 0.9956549406051636, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps \u2014 use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + }, + { + "expansion_handle": "memory:01M1Y05ZDSAHJXT2TMGQG83BWE", + "id": "01M1Y09KQ1FW0ZDVYJ5MTX2T83", + "kind": "memory", + "score": 0.9820712208747864, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 0.5, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1065.9303, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2682, + "mcp_result_bytes": 2821, + "wire_bytes": 2858, + "reported_used_tokens": 2821, + "working_set_bytes": 292048896, + "peak_working_set_bytes": 292966400 + }, + { + "query": "[patch.crates-io] workspace dependency override", + "ranked": [ + "cargo-patch-section", + "cargo-lockfile-drift", + "cargo-dev-dep-leak", + "cargo-target-dir-sharing" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05ZHK2ZA3FKKK7GBBNKFB", + "id": "01M1Y09MRDECG5PP5MRJ5K28FD", + "kind": "memory", + "score": 0.9999405145645142, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace \u2014 including transitive deps \u2014 that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1Y05ZBTM7S2Y9XXR5EA4C7E", + "id": "01M1Y09MRD49DW1ZJ15J54TCM0", + "kind": "memory", + "score": 0.9975811243057252, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this \u2014 it errors on any lockfile diff." + }, + { + "expansion_handle": "memory:01M1Y05ZDSAHJXT2TMGQG83BWE", + "id": "01M1Y09MRD3Z8TBRBE47VV4YKW", + "kind": "memory", + "score": 0.994149684906006, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + }, + { + "expansion_handle": "memory:01M1Y05ZETTM7QKBSZ1FY92RQ8", + "id": "01M1Y09MRDCNW3C7W004PG1XBR", + "kind": "memory", + "score": 0.7471600770950317, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps \u2014 use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 816.7112, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2755, + "mcp_result_bytes": 2894, + "wire_bytes": 2931, + "reported_used_tokens": 2894, + "working_set_bytes": 292122624, + "peak_working_set_bytes": 293027840 + }, + { + "query": "pin minimum supported Rust version in Cargo.toml", + "ranked": [ + "cargo-msrv" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05ZJEDXBR4GP8409Q7V6X", + "id": "01M1Y09NHYZW63N7WJW7THPTSV", + "kind": "memory", + "score": 0.999652862548828, + "summary": "project:fact - [tags: cargo rust msrv edition compatibility] Set `rust-version` in each `Cargo.toml` to declare the minimum supported Rust version (MSRV). Cargo enforces this with `--check`: `cargo check` fails if the toolchain is older than `rust-version`. Keep MSRV as old as your oldest supported deployment target." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 903.066, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 693, + "mcp_result_bytes": 774, + "wire_bytes": 811, + "reported_used_tokens": 774, + "working_set_bytes": 292155392, + "peak_working_set_bytes": 293072896 + }, + { + "query": "Windows path over 260 characters causes OS error 3 during Cargo build", + "ranked": [ + "windows-long-paths", + "windows-file-locking-av" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0605TX5R0NY7283T1SQG0", + "id": "01M1Y09PF96VDZ5XP5SF3VGVA9", + "kind": "memory", + "score": 0.9964189529418944, + "summary": "project:fact - [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe." + }, + { + "expansion_handle": "memory:01M1Y0606RJBGNW8VJVHPWXP02", + "id": "01M1Y09PF9B3T606ZFRQ77XBE2", + "kind": "memory", + "score": 0.9571694135665894, + "summary": "project:fact - [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 967.4171, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1297, + "mcp_result_bytes": 1406, + "wire_bytes": 1443, + "reported_used_tokens": 1406, + "working_set_bytes": 292167680, + "peak_working_set_bytes": 293072896 + }, + { + "query": "how do I enable long file paths for Cargo on Windows?", + "ranked": [ + "windows-long-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0605TX5R0NY7283T1SQG0", + "id": "01M1Y09QCC53CXHQ1701WTEDTB", + "kind": "memory", + "score": 0.9998334646224976, + "summary": "project:fact - [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1003.7889, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 769, + "mcp_result_bytes": 860, + "wire_bytes": 897, + "reported_used_tokens": 860, + "working_set_bytes": 292171776, + "peak_working_set_bytes": 293085184 + }, + { + "query": "intermittent sharing violation errors when Rust linker writes the exe on Windows", + "ranked": [ + "windows-file-locking-av", + "windows-long-paths", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0606RJBGNW8VJVHPWXP02", + "id": "01M1Y09RBR7N7N8GGP83VQWC9P", + "kind": "memory", + "score": 0.999750316143036, + "summary": "project:fact - [2026-09-07] [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + }, + { + "expansion_handle": "memory:01M1Y0605TX5R0NY7283T1SQG0", + "id": "01M1Y09RBRRG01Y5S0K5NG2D31", + "kind": "memory", + "score": 0.4757097661495209, + "summary": "project:fact - [2026-09-07] [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe." + }, + { + "expansion_handle": "memory:01M1Y05Z2BBHSJ0CDKCQ4XH9GE", + "id": "01M1Y09RBRAK4JTY3CKGKJWA5Z", + "kind": "memory", + "score": 0.38107830286026, + "summary": "project:fact - [2026-09-07] [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 965.9429, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2006, + "mcp_result_bytes": 2133, + "wire_bytes": 2170, + "reported_used_tokens": 2133, + "working_set_bytes": 292306944, + "peak_working_set_bytes": 293216256 + }, + { + "query": "Rust walkdir follows junctions differently from symlinks on Windows", + "ranked": [ + "windows-junctions-vs-symlinks" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0609MBTPXXW97D4DC5BMS", + "id": "01M1Y09SA0ZE28XX2NA26D28CG", + "kind": "memory", + "score": 0.9996020197868348, + "summary": "project:fact - [tags: windows junctions symlinks rust std::fs] On Windows, directory junctions (NTFS reparse points) behave like symlinks for directory traversal but `std::fs::symlink_metadata` returns `FileType::is_symlink() = false` for junctions (only true for regular symlinks). Use `std::fs::read_link` \u2014 it succeeds for both junction and symlink. `walkdir` crate's `follow_links` follows both, but its `is_symlink()` method correctly reports only actual symlinks." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 996.5014, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 845, + "mcp_result_bytes": 926, + "wire_bytes": 963, + "reported_used_tokens": 926, + "working_set_bytes": 292319232, + "peak_working_set_bytes": 293228544 + }, + { + "query": "UNC path canonicalize returns verbatim prefix \u2014 how do I strip it?", + "ranked": [ + "windows-unc-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0607KXAPYC7S6B62TQ091", + "id": "01M1Y09T98SVPJK2R9ZD9NWK75", + "kind": "memory", + "score": 0.9988629817962646, + "summary": "project:fact - [tags: windows unc-paths rust std::fs] Windows UNC paths (`\\\\server\\share\\...`) are not supported by most Rust `std::fs` operations unless passed through the extended-length prefix `\\\\?\\UNC\\server\\share\\...`. `std::path::Path::new(\"\\\\\\\\server\\\\share\")` works for basic operations but breaks with `canonicalize()` which returns the verbatim prefix form. When walking directory trees that may start on UNC paths, use the `dunce` crate to strip the verbatim prefix before comparing or displaying paths." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1030.791, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 908, + "mcp_result_bytes": 1025, + "wire_bytes": 1062, + "reported_used_tokens": 1025, + "working_set_bytes": 292384768, + "peak_working_set_bytes": 293306368 + }, + { + "query": "UTF-8 memory text prints as mojibake in the Windows console", + "ranked": [ + "windows-console-encoding" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0608M7Z5SRRAPS30W3W4A", + "id": "01M1Y09V9PEXX30A28FKJYMBQH", + "kind": "memory", + "score": 0.9996604919433594, + "summary": "project:fact - [tags: windows console encoding utf8 rust] Windows console code page defaults to the system ANSI code page (usually CP1252 or CP932), not UTF-8. Rust's `println!` writes UTF-8 bytes which display as mojibake in a non-UTF-8 console. Fix at process startup: call `SetConsoleOutputCP(65001)` via `winapi` or `windows-sys`, or set `PYTHONUTF8=1`/`RUST_LOG` before launch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 978.4155, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 757, + "mcp_result_bytes": 838, + "wire_bytes": 875, + "reported_used_tokens": 838, + "working_set_bytes": 292487168, + "peak_working_set_bytes": 293400576 + }, + { + "query": "process exit code is 4294967295 instead of -1 on Windows", + "ranked": [ + "windows-exit-codes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060AK3DAAFA6WRH4JX0V8", + "id": "01M1Y09W80JDFVT0GJN20BGM7V", + "kind": "memory", + "score": 0.9966622591018676, + "summary": "project:fact - [tags: windows exit-codes rust process child] On Windows, process exit codes are 32-bit unsigned integers (DWORD). Rust's `ExitStatus::code()` returns `Option` \u2014 it's `None` if the process was killed by a signal (which Windows doesn't use; instead, TerminateProcess with a code). Conventional codes: 0=success, 1=generic error, 0xC0000005=access violation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1001.3358000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 753, + "mcp_result_bytes": 834, + "wire_bytes": 871, + "reported_used_tokens": 834, + "working_set_bytes": 292499456, + "peak_working_set_bytes": 293408768 + }, + { + "query": "tokenizer.json must match the ONNX model \u2014 what breaks if it doesn't?", + "ranked": [ + "onnx-tokenizer-mismatch" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060CPZVTNWCD47V80WX4J", + "id": "01M1Y09X7D2Z4KEYY0QPJV84ZQ", + "kind": "memory", + "score": 0.9991299510002136, + "summary": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly \u2014 specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings \u2014 cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1085.6115, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 959, + "mcp_result_bytes": 1040, + "wire_bytes": 1077, + "reported_used_tokens": 1040, + "working_set_bytes": 292659200, + "peak_working_set_bytes": 293568512 + }, + { + "query": "embedding quality degraded after I swapped in the INT8 quantized model", + "ranked": [ + "onnx-quantization-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060DK4AZTG8FQ5SZ82QD8", + "id": "01M1Y09Y9BGTP4ZDMVZCYFFC6Y", + "kind": "memory", + "score": 0.997980535030365, + "summary": "project:fact - [2026-09-07] [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals \u2014 cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1040.4467, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 990, + "mcp_result_bytes": 1071, + "wire_bytes": 1108, + "reported_used_tokens": 1071, + "working_set_bytes": 292659200, + "peak_working_set_bytes": 293572608 + }, + { + "query": "missing attention mask causes low-norm embeddings in batch inference", + "ranked": [ + "onnx-batch-padding" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060EEY2C43PS90NFEG82N", + "id": "01M1Y09ZA7ZGB1DW6HFRKCV4PC", + "kind": "memory", + "score": 0.9998397827148438, + "summary": "project:fact - [tags: onnx batch padding attention-mask embeddings] When running batch inference with an ONNX model, all inputs in the batch must be padded to the same sequence length. The `attention_mask` tensor marks which tokens are real (1) and which are padding (0). Failing to pass `attention_mask` causes the model to average-pool over padding tokens, producing systematically lower-norm embeddings." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1067.819, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 781, + "mcp_result_bytes": 862, + "wire_bytes": 899, + "reported_used_tokens": 862, + "working_set_bytes": 292798464, + "peak_working_set_bytes": 293699584 + }, + { + "query": "ONNX model download fails in a Docker container with no home directory", + "ranked": [ + "onnx-model-cache-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060FEYS9F7FP0TTZ0J6WY", + "id": "01M1Y0A0BYQDX71CC4MNEZD35H", + "kind": "memory", + "score": 0.9887272119522096, + "summary": "project:fact - [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1077.6163000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 755, + "mcp_result_bytes": 838, + "wire_bytes": 875, + "reported_used_tokens": 838, + "working_set_bytes": 292798464, + "peak_working_set_bytes": 293703680 + }, + { + "query": "fastembed cache path environment variable for CI", + "ranked": [ + "onnx-model-cache-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060FEYS9F7FP0TTZ0J6WY", + "id": "01M1Y0A1D3DPHYKGSHR133H1YP", + "kind": "memory", + "score": 0.9995118379592896, + "summary": "project:fact - [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1065.5311000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 756, + "mcp_result_bytes": 839, + "wire_bytes": 876, + "reported_used_tokens": 839, + "working_set_bytes": 292802560, + "peak_working_set_bytes": 293703680 + }, + { + "query": "cosine similarity vs dot product for L2-normalized embedding vectors", + "ranked": [ + "onnx-cosine-vs-dot", + "onnx-tokenizer-mismatch", + "onnx-quantization-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060GAGCBYF97WMPA8SENM", + "id": "01M1Y0A2E9X9M7V6B2H6SC1TY6", + "kind": "memory", + "score": 0.9999407529830932, + "summary": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing \u2014 double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + }, + { + "expansion_handle": "memory:01M1Y060CPZVTNWCD47V80WX4J", + "id": "01M1Y0A2E9K9QE80Y5PYHA13PK", + "kind": "memory", + "score": 0.9514977931976318, + "summary": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly \u2014 specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings \u2014 cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo." + }, + { + "expansion_handle": "memory:01M1Y060DK4AZTG8FQ5SZ82QD8", + "id": "01M1Y0A2EAWX0PS1PRZ2KS20BD", + "kind": "memory", + "score": 0.941756010055542, + "summary": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals \u2014 cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 999.4296, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2245, + "mcp_result_bytes": 2362, + "wire_bytes": 2399, + "reported_used_tokens": 2362, + "working_set_bytes": 292802560, + "peak_working_set_bytes": 293711872 + }, + { + "query": "stored vectors have wrong dimension after switching embedding models", + "ranked": [ + "onnx-dim-mismatch", + "onnx-cosine-vs-dot", + "onnx-tokenizer-mismatch" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060H9Y1TBKN4BXG507K53", + "id": "01M1Y0A3DG7NNPGRSXB8WK27RX", + "kind": "memory", + "score": 0.9997621178627014, + "summary": "project:fact - [2026-09-07] [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results \u2014 the ANN index shape mismatch isn't always caught at runtime." + }, + { + "expansion_handle": "memory:01M1Y060GAGCBYF97WMPA8SENM", + "id": "01M1Y0A3DG04RHKDDZ4G7ZN07G", + "kind": "memory", + "score": 0.997715711593628, + "summary": "project:fact - [2026-09-07] [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing \u2014 double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + }, + { + "expansion_handle": "memory:01M1Y060CPZVTNWCD47V80WX4J", + "id": "01M1Y0A3DG0PJXP2Q22S4XXN9P", + "kind": "memory", + "score": 0.9388805031776428, + "summary": "project:fact - [2026-09-07] [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly \u2014 specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings \u2014 cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 878.0783, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2049, + "mcp_result_bytes": 2166, + "wire_bytes": 2203, + "reported_used_tokens": 2166, + "working_set_bytes": 292802560, + "peak_working_set_bytes": 293711872 + }, + { + "query": "E5 and Instructor models need a query prefix \u2014 what happens without it?", + "ranked": [ + "onnx-prefix-instructions", + "onnx-cosine-vs-dot" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060MFS2A9XC83N9JHNWTE", + "id": "01M1Y0A48YJF9Q6G03NWXYC8EV", + "kind": "memory", + "score": 0.996955633163452, + "summary": "project:fact - [tags: onnx embeddings prefix instruction e5 query passage] E5 and Instructor family models require a text prefix on BOTH query and passage sides to produce meaningful similarities: query prefix `\"query: \"`, passage prefix `\"passage: \"`. Omitting the prefix can drop MRR by 10-15 percentage points on out-of-domain datasets. Check the model's README for the exact prefix string \u2014 it varies by model family." + }, + { + "expansion_handle": "memory:01M1Y060GAGCBYF97WMPA8SENM", + "id": "01M1Y0A48YJ1KSKKFMEXHV4ND9", + "kind": "memory", + "score": 0.9543967247009276, + "summary": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing \u2014 double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1058.5129, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1339, + "mcp_result_bytes": 1446, + "wire_bytes": 1483, + "reported_used_tokens": 1446, + "working_set_bytes": 292802560, + "peak_working_set_bytes": 293711872 + }, + { + "query": "ORT thread pool contention when running multiple bench processes in parallel", + "ranked": [ + "onnx-ort-threading" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060NFDVT4KKYC4BDWH6D3", + "id": "01M1Y0A5A0W5E2V70M07JA2ZVC", + "kind": "memory", + "score": 0.9998078942298888, + "summary": "project:fact - [2026-09-07] [tags: onnx ort thread-pool parallelism cpu] ORT (ONNX Runtime) creates its own inter-op and intra-op thread pools. In a multi-process bench setup, each child inherits these pools and they compete for CPU cores. Set `SessionOptionsBuilder::with_intra_threads(1).with_inter_threads(1)` if you're running many parallel bench processes \u2014 this sacrifices per-inference throughput for lower contention." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1065.8263, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 802, + "mcp_result_bytes": 883, + "wire_bytes": 920, + "reported_used_tokens": 883, + "working_set_bytes": 292773888, + "peak_working_set_bytes": 293711872 + }, + { + "query": "git worktrees share the .kimetsu brain \u2014 how do I isolate test runs?", + "ranked": [ + "git-worktree-brain-isolation", + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060PEBE9HMM62FJ922NWN", + "id": "01M1Y0A6BD4P6JN23YN05E7W1V", + "kind": "memory", + "score": 0.9996256828308104, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root \u2014 if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + }, + { + "expansion_handle": "memory:01M1Y05YS4YPH8D67NSXJGT1SS", + "id": "01M1Y0A6BD8Q3JAPR4YEP5G8JV", + "kind": "memory", + "score": 0.9904396533966064, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1039.9929000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1435, + "mcp_result_bytes": 1534, + "wire_bytes": 1571, + "reported_used_tokens": 1534, + "working_set_bytes": 292904960, + "peak_working_set_bytes": 293818368 + }, + { + "query": "when is it safe to use --no-verify on git commit?", + "ranked": [ + "git-hooks-bypass", + "git-reflog-rescue" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060QDDEW4AGP54DM3BA1T", + "id": "01M1Y0A7C6P7WT8XBNBST2M4F3", + "kind": "memory", + "score": 0.9956986904144288, + "summary": "project:fact - [2026-09-07] [tags: git hooks bypass pre-commit skip] `git commit --no-verify` skips ALL hooks (pre-commit and commit-msg). Never use this in shared team repos where hooks enforce quality gates (lint, tests, memory harvest). Instead, fix the failing hook." + }, + { + "expansion_handle": "memory:01M1Y060VJEBGRY23E0DJBZS5T", + "id": "01M1Y0A7C6ZJ495NEVV0FEXCEG", + "kind": "memory", + "score": 0.5084817409515381, + "summary": "project:fact - [2026-09-07] [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone \u2014 they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only \u2014 remote reflog is not accessible via normal git commands." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1067.8847, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1193, + "mcp_result_bytes": 1292, + "wire_bytes": 1329, + "reported_used_tokens": 1292, + "working_set_bytes": 292917248, + "peak_working_set_bytes": 293826560 + }, + { + "query": "reduce clone size and bandwidth for server-side repo ingest", + "ranked": [ + "git-sparse-checkout", + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060RDNW3FF1XX9EC1TFG1", + "id": "01M1Y0A8DHZSFTY9PW09E8ADRP", + "kind": "memory", + "score": 0.9969936609268188, + "summary": "project:fact - [tags: git sparse-checkout partial-clone bandwidth] `git sparse-checkout init --cone` combined with `git clone --filter=blob:none` (partial clone) fetches only the commit graph and tree objects, not blobs. Individual blobs are fetched on demand when accessed. This cuts clone time for large repos from minutes to seconds." + }, + { + "expansion_handle": "memory:01M1Y05YBPJ3QEY9DGV3Y39QED", + "id": "01M1Y0A8DH3ZD8VDYRBN6MPYE5", + "kind": "memory", + "score": 0.8199672698974609, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1137.7412, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1744, + "mcp_result_bytes": 1843, + "wire_bytes": 1880, + "reported_used_tokens": 1843, + "working_set_bytes": 292921344, + "peak_working_set_bytes": 293826560 + }, + { + "query": "spurious diffs from Windows CRLF line ending conversion in git", + "ranked": [ + "git-line-endings-windows" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060SFWE8J4K7FA1PT0HMM", + "id": "01M1Y0A9HZA3AD4ZG2C18GYDAM", + "kind": "memory", + "score": 0.9993343949317932, + "summary": "project:fact - [tags: git line-endings windows crlf autocrlf] On Windows, `core.autocrlf=true` (git's default for Windows installs) converts LF to CRLF on checkout and CRLF to LF on commit. This causes spurious diffs when files are edited on Windows then committed \u2014 the content is identical but the line endings differ in the index vs the working tree. Fix: set `core.autocrlf=false` and `.gitattributes` with `* text=auto eol=lf` for the repo." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1076.6487, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 940, + "reported_used_tokens": 903, + "working_set_bytes": 292921344, + "peak_working_set_bytes": 293826560 + }, + { + "query": "git submodule always gets the wrong commit in CI", + "ranked": [ + "git-submodule-pinning", + "git-hooks-bypass" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060THWEBZHRGKPBJRS88X", + "id": "01M1Y0AAJM6Y7E67SXNJ22MPFY", + "kind": "memory", + "score": 0.9992856383323668, + "summary": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip \u2014 this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version." + }, + { + "expansion_handle": "memory:01M1Y060QDDEW4AGP54DM3BA1T", + "id": "01M1Y0AAJMTRQWBSJN5XA5D2J1", + "kind": "memory", + "score": 0.6295387744903564, + "summary": "project:fact - [tags: git hooks bypass pre-commit skip] `git commit --no-verify` skips ALL hooks (pre-commit and commit-msg). Never use this in shared team repos where hooks enforce quality gates (lint, tests, memory harvest). Instead, fix the failing hook." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1061.1885000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1157, + "mcp_result_bytes": 1256, + "wire_bytes": 1293, + "reported_used_tokens": 1256, + "working_set_bytes": 292921344, + "peak_working_set_bytes": 293830656 + }, + { + "query": "accidentally ran git reset --hard and lost commits \u2014 can I recover?", + "ranked": [ + "git-reflog-rescue" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060VJEBGRY23E0DJBZS5T", + "id": "01M1Y0ABKT8KD4212ZJNBTNXAH", + "kind": "memory", + "score": 0.9995450377464294, + "summary": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone \u2014 they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only \u2014 remote reflog is not accessible via normal git commands." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1071.7251999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 762, + "mcp_result_bytes": 843, + "wire_bytes": 880, + "reported_used_tokens": 843, + "working_set_bytes": 292921344, + "peak_working_set_bytes": 293830656 + }, + { + "query": "blocking SQLite call from an async tokio handler causes latency spikes", + "ranked": [ + "tokio-blocking-in-async" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060WH1VTPAKJ89CENJJXP", + "id": "01M1Y0ACN77M49RPZSXDTHK557", + "kind": "memory", + "score": 0.9996535778045654, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 957.0353, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 766, + "mcp_result_bytes": 847, + "wire_bytes": 884, + "reported_used_tokens": 847, + "working_set_bytes": 292921344, + "peak_working_set_bytes": 293830656 + }, + { + "query": "Cannot start a runtime from within a runtime in a tokio test", + "ranked": [ + "tokio-runtime-in-tests", + "tokio-blocking-in-async" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060XP0KSRSNNYT7NE7D4W", + "id": "01M1Y0ADK5H4DZXXHQCMFC2TNA", + "kind": "memory", + "score": 0.9997126460075378, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + }, + { + "expansion_handle": "memory:01M1Y060WH1VTPAKJ89CENJJXP", + "id": "01M1Y0ADK5RRYEGZ9ZE3QPPA10", + "kind": "memory", + "score": 0.5779464840888977, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 978.6442, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1370, + "mcp_result_bytes": 1477, + "wire_bytes": 1514, + "reported_used_tokens": 1477, + "working_set_bytes": 292929536, + "peak_working_set_bytes": 293838848 + }, + { + "query": "tokio select cancels the other branch and loses the value in the channel", + "ranked": [ + "tokio-select-cancellation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060Z0MRX8BDRSVJPYBRVF", + "id": "01M1Y0AEHTTMQ3P0KXSC52VWC8", + "kind": "memory", + "score": 0.9981033802032472, + "summary": "project:fact - [tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1017.7108999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 751, + "mcp_result_bytes": 832, + "wire_bytes": 869, + "reported_used_tokens": 832, + "working_set_bytes": 292925440, + "peak_working_set_bytes": 293838848 + }, + { + "query": "mpsc channel backpressure causing senders to stall", + "ranked": [ + "tokio-channel-backpressure" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y06106AJ5F1TJCW170EKZK", + "id": "01M1Y0AFHKHAFM1NZF3SGFXSQ9", + "kind": "memory", + "score": 0.9999104738235474, + "summary": "project:fact - [tags: tokio mpsc channel backpressure async rust] `tokio::sync::mpsc::channel(N)` with a bounded buffer provides backpressure: senders block when the buffer is full. This prevents unbounded memory growth but can cause sender tasks to stall. Choosing N: too small causes frequent backpressure (throughput drops); too large defeats the purpose." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1093.6848, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 733, + "mcp_result_bytes": 814, + "wire_bytes": 851, + "reported_used_tokens": 814, + "working_set_bytes": 292925440, + "peak_working_set_bytes": 293838848 + }, + { + "query": "overhead from calling spawn_blocking on every single query request", + "ranked": [ + "tokio-spawn-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0611CGW84S4J3YQ49DDQ2", + "id": "01M1Y0AGM0MJ3K5A986T864A8S", + "kind": "memory", + "score": 0.9961729645729064, + "summary": "project:fact - [tags: tokio spawn_blocking thread-pool rust blocking] `tokio::task::spawn_blocking` places work on a dedicated blocking thread pool (default up to 512 threads, configurable via `Builder::max_blocking_threads`). Each call creates or reuses a thread \u2014 there's no true pooling, threads may be created on demand. For many short-duration blocking calls (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1043.2462, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 746, + "mcp_result_bytes": 827, + "wire_bytes": 864, + "reported_used_tokens": 827, + "working_set_bytes": 292925440, + "peak_working_set_bytes": 293838848 + }, + { + "query": "axum server panics during shutdown because the DB pool is already closed", + "ranked": [ + "tokio-shutdown-ordering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0615DY78S91BYCR1PW38X", + "id": "01M1Y0AHMPF4F1YDP3BE5VMMKN", + "kind": "memory", + "score": 0.98052579164505, + "summary": "project:fact - [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries \u2014 the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1044.4894, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 931, + "mcp_result_bytes": 1012, + "wire_bytes": 1049, + "reported_used_tokens": 1012, + "working_set_bytes": 292925440, + "peak_working_set_bytes": 293838848 + }, + { + "query": "reqwest Client created per-request defeats connection pooling", + "ranked": [ + "http-connection-pooling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0616GTXM310F072T1Z5JJ", + "id": "01M1Y0AJP3DDPVHDG6S9ENHVJ4", + "kind": "memory", + "score": 0.9998082518577576, + "summary": "project:fact - [tags: http reqwest connection-pool keep-alive rust] reqwest's `Client` holds a connection pool; always create ONE `Client` instance and clone it for each handler \u2014 cloning is cheap (Arc under the hood). Creating a `Client::new()` per request defeats connection pooling and causes TCP connection exhaustion under load. The default pool settings: max_idle_per_host=usize::MAX (unbounded), idle_timeout=90s." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1046.7995999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 797, + "mcp_result_bytes": 878, + "wire_bytes": 915, + "reported_used_tokens": 878, + "working_set_bytes": 292925440, + "peak_working_set_bytes": 293838848 + }, + { + "query": "LLM request times out during streaming \u2014 which timeout setting applies?", + "ranked": [ + "http-timeout-layering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0617QSA2CVDN4ENGFG0RG", + "id": "01M1Y0AKPG35K4M2NB22JSJZK2", + "kind": "memory", + "score": 0.9987107515335084, + "summary": "project:fact - [tags: http reqwest timeout connect read total rust] reqwest has three distinct timeout knobs: `connect_timeout`, `read_timeout`, and `timeout` (total). They compose: if all three are set, the request fails at whichever fires first. For LLM API calls with streaming responses, `read_timeout` must be larger than the slowest expected token (often 30-60s) while `connect_timeout` can be tight (3-5s)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 912.2832, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 788, + "mcp_result_bytes": 869, + "wire_bytes": 906, + "reported_used_tokens": 869, + "working_set_bytes": 292925440, + "peak_working_set_bytes": 293838848 + }, + { + "query": "how do I safely retry a POST to the LLM API without creating duplicates?", + "ranked": [ + "http-retry-idempotency" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0618VR2BYK0SW8R8GC71W", + "id": "01M1Y0AMJKR92MK2WMQ5YWVT40", + "kind": "memory", + "score": 0.9995805621147156, + "summary": "project:fact - [tags: http retry idempotency post put reqwest] Only retry idempotent requests automatically. GET, HEAD, PUT, DELETE are idempotent. POST is NOT \u2014 retrying a POST may create duplicate resources." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1092.0312999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 585, + "mcp_result_bytes": 666, + "wire_bytes": 703, + "reported_used_tokens": 666, + "working_set_bytes": 292925440, + "peak_working_set_bytes": 293838848 + }, + { + "query": "custom enterprise root CA not trusted by rustls on Windows", + "ranked": [ + "http-tls-roots", + "http-proxy-env" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061A1B4C0T968J5PNRFSH", + "id": "01M1Y0ANMHX0X9Z39VSY2SH0VG", + "kind": "memory", + "score": 0.9998220801353456, + "summary": "project:fact - [tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle \u2014 the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle." + }, + { + "expansion_handle": "memory:01M1Y061CA2XBDZ74DT8HR7CSJ", + "id": "01M1Y0ANMHQE12AA2KBWQ3EB12", + "kind": "memory", + "score": 0.38715291023254395, + "summary": "project:fact - [tags: http proxy environment reqwest rust corporate] reqwest respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` environment variables by default (with `default-tls` or `rustls-tls`). In a corporate network, these may redirect traffic through an intercepting proxy that breaks mTLS or adds latency. To disable proxy usage entirely: `reqwest::ClientBuilder::no_proxy()`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1073.9996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1311, + "mcp_result_bytes": 1410, + "wire_bytes": 1447, + "reported_used_tokens": 1410, + "working_set_bytes": 292929536, + "peak_working_set_bytes": 293838848 + }, + { + "query": "parsing server-sent events when a single TCP chunk contains a partial SSE frame", + "ranked": [ + "http-streaming-bodies" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061B3Z0S7MH3X6Y15J8MA", + "id": "01M1Y0APPFXJVDNDBX19B1KX81", + "kind": "memory", + "score": 0.9667426943778992, + "summary": "project:fact - [2026-09-07] [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding \u2014 a chunk may split across frame boundaries." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 983.6257, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 859, + "mcp_result_bytes": 940, + "wire_bytes": 977, + "reported_used_tokens": 940, + "working_set_bytes": 292929536, + "peak_working_set_bytes": 293838848 + }, + { + "query": "reqwest does not use the system proxy settings on Windows", + "ranked": [ + "http-proxy-env", + "http-tls-roots", + "http-connection-pooling", + "http-streaming-bodies" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061CA2XBDZ74DT8HR7CSJ", + "id": "01M1Y0AQMWE5EQQFMNQS27AN0S", + "kind": "memory", + "score": 0.9997830986976624, + "summary": "project:fact - [tags: http proxy environment reqwest rust corporate] reqwest respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` environment variables by default (with `default-tls` or `rustls-tls`). In a corporate network, these may redirect traffic through an intercepting proxy that breaks mTLS or adds latency. To disable proxy usage entirely: `reqwest::ClientBuilder::no_proxy()`." + }, + { + "expansion_handle": "memory:01M1Y061A1B4C0T968J5PNRFSH", + "id": "01M1Y0AQMW7083FC0SFXDKEYJF", + "kind": "memory", + "score": 0.9808586239814758, + "summary": "project:fact - [tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle \u2014 the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle." + }, + { + "expansion_handle": "memory:01M1Y0616GTXM310F072T1Z5JJ", + "id": "01M1Y0AQMXVP0KD4QWTYPYTKZQ", + "kind": "memory", + "score": 0.719273030757904, + "summary": "project:fact - [tags: http reqwest connection-pool keep-alive rust] reqwest's `Client` holds a connection pool; always create ONE `Client` instance and clone it for each handler \u2014 cloning is cheap (Arc under the hood). Creating a `Client::new()` per request defeats connection pooling and causes TCP connection exhaustion under load. The default pool settings: max_idle_per_host=usize::MAX (unbounded), idle_timeout=90s." + }, + { + "expansion_handle": "memory:01M1Y061B3Z0S7MH3X6Y15J8MA", + "id": "01M1Y0AQMWZ8P9W8Z9KMXS7AFC", + "kind": "memory", + "score": 0.7009692192077637, + "summary": "project:fact - [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding \u2014 a chunk may split across frame boundaries." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1058.414, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2497, + "mcp_result_bytes": 2632, + "wire_bytes": 2669, + "reported_used_tokens": 2632, + "working_set_bytes": 292929536, + "peak_working_set_bytes": 293838848 + }, + { + "query": "insta snapshot tests fail in CI because output includes a timestamp", + "ranked": [ + "testing-snapshot-churn", + "ci-flaky-quarantine" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061DCJN2SYNAWF125PPW3", + "id": "01M1Y0ARP01PP3QXW4712G9RDW", + "kind": "memory", + "score": 0.999855637550354, + "summary": "project:fact - [tags: testing snapshot insta assert churn rust] Snapshot tests (e.g. with the `insta` crate) fail whenever the output changes, even for intended changes. In CI, they fail loudly; locally, `cargo insta review` walks you through accepting or rejecting changes." + }, + { + "expansion_handle": "memory:01M1Y06288TD3HYPJQNP7S1ZNB", + "id": "01M1Y0ARP0EQ42AQ2NECKBHANG", + "kind": "memory", + "score": 0.5997360348701477, + "summary": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal \u2014 a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 921.1368, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1196, + "mcp_result_bytes": 1295, + "wire_bytes": 1332, + "reported_used_tokens": 1295, + "working_set_bytes": 292937728, + "peak_working_set_bytes": 293847040 + }, + { + "query": "two test workers writing to the same temp directory path race each other", + "ranked": [ + "testing-temp-dirs-ci" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061EHA1QHS9A92RMVXS4T", + "id": "01M1Y0ASK1YNX5TK7F9G7DZ9ZD", + "kind": "memory", + "score": 0.9889234900474548, + "summary": "project:fact - [tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 962.231, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 755, + "mcp_result_bytes": 836, + "wire_bytes": 873, + "reported_used_tokens": 836, + "working_set_bytes": 292937728, + "peak_working_set_bytes": 293847040 + }, + { + "query": "test passes locally but fails on a slow CI runner due to a 100ms sleep", + "ranked": [ + "testing-time-dependent-flakes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061FPYM98XSA9JHTRM8PK", + "id": "01M1Y0ATH75EFRF9BH4MT0Q3GR", + "kind": "memory", + "score": 0.808289110660553, + "summary": "project:fact - [tags: testing time flaky clock mock rust] Tests that depend on wall-clock time are inherently flaky under load (slow CI runners, GC pauses). Abstract time behind a trait (`Clock: Fn() -> SystemTime`) injected at construction, and supply a fake in tests. For tests checking that something happened \"within N seconds\", use a generous multiple of the expected duration (10x is not unreasonable for CI)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1059.1616, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 790, + "mcp_result_bytes": 875, + "wire_bytes": 912, + "reported_used_tokens": 875, + "working_set_bytes": 292941824, + "peak_working_set_bytes": 293859328 + }, + { + "query": "proptest found a hash collision in text normalization that example tests missed", + "ranked": [ + "testing-property-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061GRG5X0M57D3GHW6JES", + "id": "01M1Y0AVJRBEFTAK8QAQ2NBJNB", + "kind": "memory", + "score": 0.9994783997535706, + "summary": "project:fact - [tags: testing property-based proptest quickcheck rust] Property-based tests (proptest, quickcheck) find edge cases that example-based tests miss. For kimetsu's memory text normalization, proptest found that zero-width joiner characters and right-to-left marks caused hash collisions. Run proptest with `PROPTEST_CASES=10000` in CI for thorough coverage." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1140.0357000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 744, + "mcp_result_bytes": 825, + "wire_bytes": 862, + "reported_used_tokens": 825, + "working_set_bytes": 292945920, + "peak_working_set_bytes": 293859328 + }, + { + "query": "set_var in tests races when cargo test runs them in parallel", + "ranked": [ + "testing-serial-vs-parallel" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061HRKB2V85Z6MW33WPRE", + "id": "01M1Y0AWNK812QJFV6YZAHT3ZQ", + "kind": "memory", + "score": 0.9997344613075256, + "summary": "project:fact - [2026-09-07] [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 992.7324, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 832, + "mcp_result_bytes": 913, + "wire_bytes": 950, + "reported_used_tokens": 913, + "working_set_bytes": 292945920, + "peak_working_set_bytes": 293863424 + }, + { + "query": "hardcoded JSON fixtures broke after a schema migration", + "ranked": [ + "testing-fixture-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061NJMXF06HD6WSDEN8JZ", + "id": "01M1Y0AXMQAPQEARH68XYCY0SN", + "kind": "memory", + "score": 0.9998371601104736, + "summary": "project:fact - [2026-09-07] [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 819.7195, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 783, + "mcp_result_bytes": 864, + "wire_bytes": 901, + "reported_used_tokens": 864, + "working_set_bytes": 292945920, + "peak_working_set_bytes": 293863424 + }, + { + "query": "debug print in the MCP handler corrupts the JSON-Lines protocol stream", + "ranked": [ + "mcp-stdout-protocol" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061PEX980HR5500H8P7ZH", + "id": "01M1Y0AYEB4CNDR8VE5E083N98", + "kind": "memory", + "score": 0.9997472167015076, + "summary": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1038.4728, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 705, + "mcp_result_bytes": 786, + "wire_bytes": 823, + "reported_used_tokens": 786, + "working_set_bytes": 292945920, + "peak_working_set_bytes": 293863424 + }, + { + "query": "kimetsu MCP tool call times out because embedding model is re-initialized every call", + "ranked": [ + "mcp-tool-timeouts", + "mcp-schema-validation", + "kimetsu-bench-remote-embedder-singleton" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061QHG7EX7KYBKD86CQYC", + "id": "01M1Y0AZERF0HZ5CR65K1AXKWG", + "kind": "memory", + "score": 0.9995898604393004, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + }, + { + "expansion_handle": "memory:01M1Y061SPN3ZV6B1WDAVECD5V", + "id": "01M1Y0AZER79W7J1X7HAK350VJ", + "kind": "memory", + "score": 0.6027993559837341, + "summary": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array \u2014 omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error." + }, + { + "expansion_handle": "memory:01M1Y062K14KTD5D5SR7NWV35P", + "id": "01M1Y0AZERT1QPGF4VQW0KHFFJ", + "kind": "memory", + "score": 0.5117799639701843, + "summary": "project:fact - [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1139.2597, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2085, + "mcp_result_bytes": 2202, + "wire_bytes": 2239, + "reported_used_tokens": 2202, + "working_set_bytes": 292945920, + "peak_working_set_bytes": 293863424 + }, + { + "query": "env var set after host launch is not visible to the MCP server process", + "ranked": [ + "mcp-env-propagation", + "kimetsu-daemon-lifecycle" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061RKZW8EMQN44S984P8P", + "id": "01M1Y0B0JPGBN6KWJ5V92MS95T", + "kind": "memory", + "score": 0.9984827637672424, + "summary": "project:fact - [2026-09-07] [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment \u2014 changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate." + }, + { + "expansion_handle": "memory:01M1Y0629C2P66GZ8JJCVPWPR9", + "id": "01M1Y0B0JPA2116Z1PP6271N3F", + "kind": "memory", + "score": 0.9977922439575196, + "summary": "project:fact - [2026-09-07] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1138.8609999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1267, + "mcp_result_bytes": 1366, + "wire_bytes": 1403, + "reported_used_tokens": 1366, + "working_set_bytes": 292945920, + "peak_working_set_bytes": 293863424 + }, + { + "query": "MCP tool call fails because a required field is missing from the JSON input", + "ranked": [ + "mcp-schema-validation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061SPN3ZV6B1WDAVECD5V", + "id": "01M1Y0B1P1GKRF66N864DP9CNX", + "kind": "memory", + "score": 0.998538613319397, + "summary": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array \u2014 omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 995.9949, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 798, + "mcp_result_bytes": 879, + "wire_bytes": 916, + "reported_used_tokens": 879, + "working_set_bytes": 292945920, + "peak_working_set_bytes": 293863424 + }, + { + "query": "Claude Code rejects the tool name with a hyphen in it", + "ranked": [ + "mcp-tool-naming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061TPTKP8TGYS8ZZJBXTG", + "id": "01M1Y0B2NE5N26H9E6KJM6T72N", + "kind": "memory", + "score": 0.9982439279556274, + "summary": "project:fact - [tags: mcp tool naming convention kimetsu] MCP tool names must be valid identifiers for all host agents. Claude Code restricts tool names to `[a-zA-Z0-9_-]` and max 64 chars. Use `snake_case` (kimetsu_brain_context, kimetsu_brain_record) \u2014 hyphen is technically allowed but some hosts reject it." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1013.8944000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 687, + "mcp_result_bytes": 768, + "wire_bytes": 805, + "reported_used_tokens": 768, + "working_set_bytes": 293064704, + "peak_working_set_bytes": 293974016 + }, + { + "query": "MCP response path uses backslashes and the host rejects it", + "ranked": [ + "mcp-transcript-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061VRMBRDPPJGNQ57KBB0", + "id": "01M1Y0B3MV00EJJT7NF317W0F2", + "kind": "memory", + "score": 0.9984637498855592, + "summary": "project:fact - [tags: mcp transcript paths kimetsu hooks runs] kimetsu writes run transcripts to `/.kimetsu/runs//`. The post-session hook reads the latest run's transcript to trigger memory harvest. On Windows, the path uses backslashes internally but the MCP JSON must use forward slashes or the host may reject path-type arguments." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1029.8874, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 724, + "mcp_result_bytes": 805, + "wire_bytes": 842, + "reported_used_tokens": 805, + "working_set_bytes": 293117952, + "peak_working_set_bytes": 294023168 + }, + { + "query": "AWS credentials not found \u2014 which env var does kimetsu read for Bedrock?", + "ranked": [ + "aws-credentials-chain", + "aws-region-resolution", + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061WWY2XRNJE51QHGVRAM", + "id": "01M1Y0B4NHPTATKKSMG7M3RNRS", + "kind": "memory", + "score": 0.9990235567092896, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + }, + { + "expansion_handle": "memory:01M1Y061Y6112PMFKN5GEJZE8M", + "id": "01M1Y0B4NJFK3BT5VQBF7PRFV2", + "kind": "memory", + "score": 0.9968422651290894, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1Y05YH1NQKFMZ0R78E7Z8SP", + "id": "01M1Y0B4NHZ4RCXGZJDFCJAS06", + "kind": "memory", + "score": 0.9849756360054016, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1Y05YPKKNJWV8P7EZDPZJTG", + "id": "01M1Y0B4NJ62KNWNSQ5XEWDVVD", + "kind": "memory", + "score": 0.9203452467918396, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1108.5313999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3455, + "mcp_result_bytes": 3618, + "wire_bytes": 3655, + "reported_used_tokens": 3618, + "working_set_bytes": 293462016, + "peak_working_set_bytes": 294371328 + }, + { + "query": "Bedrock InvokeModel fails because the region is not configured", + "ranked": [ + "aws-region-resolution", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061Y6112PMFKN5GEJZE8M", + "id": "01M1Y0B5QRS1WJ5V7BN93KCAKB", + "kind": "memory", + "score": 0.99688321352005, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1Y05YPKKNJWV8P7EZDPZJTG", + "id": "01M1Y0B5QRKED8BS5DMNFQ524G", + "kind": "memory", + "score": 0.6450709104537964, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1111.6677, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1810, + "mcp_result_bytes": 1929, + "wire_bytes": 1966, + "reported_used_tokens": 1929, + "working_set_bytes": 293462016, + "peak_working_set_bytes": 294375424 + }, + { + "query": "how do I handle ThrottlingException from Bedrock with exponential backoff?", + "ranked": [ + "aws-retry-throttling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061Z7A6RC4W57V7Z3CXS9", + "id": "01M1Y0B6TG8WSWG0AK1XAS7QKA", + "kind": "memory", + "score": 0.9997082352638244, + "summary": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with \u00b125% jitter." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1104.0339, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 771, + "mcp_result_bytes": 868, + "wire_bytes": 905, + "reported_used_tokens": 868, + "working_set_bytes": 293462016, + "peak_working_set_bytes": 294375424 + }, + { + "query": "generating a presigned S3 URL for brain export without exposing credentials", + "ranked": [ + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0620B2W192A9Y86X0ZB3D", + "id": "01M1Y0B7X6PZ94FB8SF3SGTSCV", + "kind": "memory", + "score": 0.9990487694740297, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1029.8686, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 875, + "mcp_result_bytes": 956, + "wire_bytes": 993, + "reported_used_tokens": 956, + "working_set_bytes": 293462016, + "peak_working_set_bytes": 294375424 + }, + { + "query": "IMDSv2 token required for instance metadata \u2014 PUT before GET", + "ranked": [ + "aws-instance-metadata" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0621DDYB1YEMWDHDQVHKF", + "id": "01M1Y0B8X8YC3WGWZS8DS2CCZG", + "kind": "memory", + "score": 0.9997182488441468, + "summary": "project:fact - [2026-09-07] [tags: aws imds instance-metadata ec2 token] The AWS Instance Metadata Service v2 (IMDSv2) requires a session token: PUT `http://169.254.169.254/latest/api/token` with `X-aws-ec2-metadata-token-ttl-seconds: 21600` to get a token, then GET metadata with `X-aws-ec2-metadata-token: `. IMDSv1 (no token) is disabled on hardened instances. The metadata endpoint is only reachable from within EC2 \u2014 a connection timeout means you're not on EC2." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1061.8654999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 851, + "mcp_result_bytes": 932, + "wire_bytes": 969, + "reported_used_tokens": 932, + "working_set_bytes": 293462016, + "peak_working_set_bytes": 294375424 + }, + { + "query": "Cargo cache key strategy for GitHub Actions to avoid toolchain version collisions", + "ranked": [ + "ci-cache-keys" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0624MJHY6FBK9QT43NJHN", + "id": "01M1Y0B9YN9TSX8WRBWW0KWN2N", + "kind": "memory", + "score": 0.998869240283966, + "summary": "project:fact - [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key \u2014 macOS and Windows have incompatible artifact formats." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1071.9662999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 788, + "mcp_result_bytes": 869, + "wire_bytes": 906, + "reported_used_tokens": 869, + "working_set_bytes": 293462016, + "peak_working_set_bytes": 294375424 + }, + { + "query": "CI matrix has 18 jobs and costs too much \u2014 how do I reduce it?", + "ranked": [ + "ci-matrix-explosion" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0625HY0BZ3TKCV0YJHVBD", + "id": "01M1Y0BAZX62WJ1X1242GN4QF9", + "kind": "memory", + "score": 0.999057948589325, + "summary": "project:fact - [tags: ci github-actions matrix jobs resources] A CI matrix combining OS (3) x Rust toolchain (3) x features (2) = 18 jobs. Each spawns a runner; at $0.008/min for Ubuntu and $0.016/min for Windows, a 10-minute build costs $2.40 per push. Reduce: test the full matrix only on PRs to main; on feature branches, test only Linux+stable." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1095.7056, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 722, + "mcp_result_bytes": 803, + "wire_bytes": 840, + "reported_used_tokens": 803, + "working_set_bytes": 293462016, + "peak_working_set_bytes": 294375424 + }, + { + "query": "GitHub Actions secret accidentally printed in build logs", + "ranked": [ + "ci-secrets-masking", + "ci-cache-keys", + "ci-artifact-retention" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0626EATKH9B42B4W92CC3", + "id": "01M1Y0BC26JG2ZNNXVHATZXEPW", + "kind": "memory", + "score": 0.9963951706886292, + "summary": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output \u2014 but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable." + }, + { + "expansion_handle": "memory:01M1Y0624MJHY6FBK9QT43NJHN", + "id": "01M1Y0BC265KD2MBP0VZHKEFPA", + "kind": "memory", + "score": 0.4342843890190125, + "summary": "project:fact - [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key \u2014 macOS and Windows have incompatible artifact formats." + }, + { + "expansion_handle": "memory:01M1Y0627E5YD6JZ9T5NA8JG83", + "id": "01M1Y0BC26HZE75179TEJ4X98B", + "kind": "memory", + "score": 0.3422144949436188, + "summary": "project:fact - [tags: ci github-actions artifacts retention benchmark] GitHub Actions artifacts are retained for 90 days (default). For benchmark results, use `actions/upload-artifact` with `retention-days: 365` for long-term tracking. The free tier has 500MB storage \u2014 per-combo JSON files from kimetsu bench (each ~60KB) add up fast if you upload them on every push." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1056.0203, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1791, + "mcp_result_bytes": 1908, + "wire_bytes": 1945, + "reported_used_tokens": 1908, + "working_set_bytes": 293462016, + "peak_working_set_bytes": 294375424 + }, + { + "query": "how long do GitHub Actions artifacts persist and what's the storage limit?", + "ranked": [ + "ci-artifact-retention" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0627E5YD6JZ9T5NA8JG83", + "id": "01M1Y0BD38Z88V0MNSPSAGYQKD", + "kind": "memory", + "score": 0.999624252319336, + "summary": "project:fact - [tags: ci github-actions artifacts retention benchmark] GitHub Actions artifacts are retained for 90 days (default). For benchmark results, use `actions/upload-artifact` with `retention-days: 365` for long-term tracking. The free tier has 500MB storage \u2014 per-combo JSON files from kimetsu bench (each ~60KB) add up fast if you upload them on every push." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1124.1330999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 744, + "mcp_result_bytes": 825, + "wire_bytes": 862, + "reported_used_tokens": 825, + "working_set_bytes": 293466112, + "peak_working_set_bytes": 294379520 + }, + { + "query": "timing-based test flake in CI \u2014 quarantine or fix?", + "ranked": [ + "ci-flaky-quarantine", + "testing-time-dependent-flakes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y06288TD3HYPJQNP7S1ZNB", + "id": "01M1Y0BE6QQY5EZZJ4V9AGX64H", + "kind": "memory", + "score": 0.9994743466377258, + "summary": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal \u2014 a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output." + }, + { + "expansion_handle": "memory:01M1Y061FPYM98XSA9JHTRM8PK", + "id": "01M1Y0BE6QW2SR3W7B849671TJ", + "kind": "memory", + "score": 0.9849997162818908, + "summary": "project:fact - [tags: testing time flaky clock mock rust] Tests that depend on wall-clock time are inherently flaky under load (slow CI runners, GC pauses). Abstract time behind a trait (`Clock: Fn() -> SystemTime`) injected at construction, and supply a fake in tests. For tests checking that something happened \"within N seconds\", use a generous multiple of the expected duration (10x is not unreasonable for CI)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1066.3907, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1340, + "mcp_result_bytes": 1443, + "wire_bytes": 1480, + "reported_used_tokens": 1443, + "working_set_bytes": 293466112, + "peak_working_set_bytes": 294383616 + }, + { + "query": "kimetsu doctor says the MCP server is running \u2014 how do I stop it before an update?", + "ranked": [ + "kimetsu-daemon-lifecycle", + "mcp-env-propagation", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0629C2P66GZ8JJCVPWPR9", + "id": "01M1Y0BF8A8D99JENGMYEK0NDM", + "kind": "memory", + "score": 0.9989782571792604, + "summary": "project:fact - [2026-09-07] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1Y061RKZW8EMQN44S984P8P", + "id": "01M1Y0BF8AJX2K27DVSHW7XPKG", + "kind": "memory", + "score": 0.9049031734466552, + "summary": "project:fact - [2026-09-07] [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment \u2014 changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate." + }, + { + "expansion_handle": "memory:01M1Y05YDQZCEVNBN7TDX4XC9B", + "id": "01M1Y0BF8AQ278C2SX9JWBX99K", + "kind": "memory", + "score": 0.4812128245830536, + "summary": "project:fact - [2026-09-07] [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1126.0901000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2046, + "mcp_result_bytes": 2211, + "wire_bytes": 2248, + "reported_used_tokens": 2211, + "working_set_bytes": 293466112, + "peak_working_set_bytes": 294383616 + }, + { + "query": "noise capsules consuming token budget without contributing retrieval signal", + "ranked": [ + "kimetsu-capsule-budgets" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y062ABMMMT09Z0H9J6WG1B", + "id": "01M1Y0BGB0G8NMMN6TXQA570JB", + "kind": "memory", + "score": 0.9997420907020568, + "summary": "project:fact - [tags: kimetsu capsule tokens budget retrieval] kimetsu retrieval enforces a token budget per capsule type: memory capsules are capped at 6000 tokens total (across all retrieved memories), file capsules at 3000 tokens. When a memory is large and would exceed the budget, it is truncated at a sentence boundary. The budget is enforced AFTER reranking \u2014 reranking may reorder results so that a truncated high-ranked memory displaces a full lower-ranked one." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 884.0705, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 847, + "mcp_result_bytes": 928, + "wire_bytes": 965, + "reported_used_tokens": 928, + "working_set_bytes": 293466112, + "peak_working_set_bytes": 294383616 + }, + { + "query": "kimetsu_brain_record writes to the wrong brain location \u2014 user vs project scope", + "ranked": [ + "kimetsu-memory-scopes", + "kimetsu-write-tools-gate", + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y062B89NB3SMAHX7SX830R", + "id": "01M1Y0BH7GP3DG7T459Z02CEYG", + "kind": "memory", + "score": 0.999030828475952, + "summary": "project:fact - [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available \u2014 if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope." + }, + { + "expansion_handle": "memory:01M1Y062EEFBC57YJ58J3CSXFD", + "id": "01M1Y0BH7GM8BKPHZ3X63ESW0G", + "kind": "memory", + "score": 0.9838979840278624, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level \u2014 disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1Y05YS4YPH8D67NSXJGT1SS", + "id": "01M1Y0BH7G6PZD4RT5X8Y4E22V", + "kind": "memory", + "score": 0.3852712512016296, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1052.1639, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2098, + "mcp_result_bytes": 2215, + "wire_bytes": 2252, + "reported_used_tokens": 2215, + "working_set_bytes": 293466112, + "peak_working_set_bytes": 294383616 + }, + { + "query": "how do I configure kimetsu to use Claude Haiku for harvesting but Opus for the agent?", + "ranked": [ + "kimetsu-distiller-config" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y062CBNZWHRGE999FY1B1Q", + "id": "01M1Y0BJ7Q44JXZH4M9FK03V4T", + "kind": "memory", + "score": 0.9989088773727416, + "summary": "project:fact - [tags: kimetsu distiller harvest config provider] The kimetsu distiller (auto-harvester) uses a SEPARATE provider configuration from the main agent: `distiller.provider`, `distiller.model`, `distiller.api_key`. This allows running the agent on an expensive model (Claude Opus) while harvesting with a cheap model (Claude Haiku). If `distiller.provider` is not set, it inherits `provider`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1067.6490000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 778, + "mcp_result_bytes": 859, + "wire_bytes": 896, + "reported_used_tokens": 859, + "working_set_bytes": 293470208, + "peak_working_set_bytes": 294383616 + }, + { + "query": "first agent turn is slow because kimetsu proactive hook runs embedding inference", + "ranked": [ + "kimetsu-proactive-hooks", + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y062DDSWX11Z89QR995TW9", + "id": "01M1Y0BK98C8SYTZ6FNQVSKM26", + "kind": "memory", + "score": 0.999568521976471, + "summary": "project:fact - [2026-09-07] [tags: kimetsu proactive hooks context injection] kimetsu's proactive context injection runs before each agent turn (pre-turn hook) and injects relevant memories into the system prompt prefix. The hook invocation adds latency to the first token: embedding inference + vector search + reranking + context formatting. On a cold start, this can be 1-3 seconds." + }, + { + "expansion_handle": "memory:01M1Y061QHG7EX7KYBKD86CQYC", + "id": "01M1Y0BK98Y6D3N2PPMNR610S7", + "kind": "memory", + "score": 0.9405298233032228, + "summary": "project:fact - [2026-09-07] [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1100.5240999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1403, + "mcp_result_bytes": 1502, + "wire_bytes": 1539, + "reported_used_tokens": 1502, + "working_set_bytes": 293470208, + "peak_working_set_bytes": 294383616 + }, + { + "query": "make the kimetsu brain read-only for certain repos on a shared remote server", + "ranked": [ + "kimetsu-write-tools-gate", + "remote-ingest-split-roots", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y062EEFBC57YJ58J3CSXFD", + "id": "01M1Y0BMCXCYDKB134CKVNXPYM", + "kind": "memory", + "score": 0.997682809829712, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level \u2014 disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1Y05YBPJ3QEY9DGV3Y39QED", + "id": "01M1Y0BMCXG5P32PYW1K2KRGS4", + "kind": "memory", + "score": 0.9957050681114196, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1Y05YDQZCEVNBN7TDX4XC9B", + "id": "01M1Y0BMCXY8FNCD4ZANTS61P5", + "kind": "memory", + "score": 0.9909282326698304, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1080.4236, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2725, + "mcp_result_bytes": 2890, + "wire_bytes": 2927, + "reported_used_tokens": 2890, + "working_set_bytes": 293470208, + "peak_working_set_bytes": 294383616 + }, + { + "query": "kimetsu FTS search misses 'deadlocking' when memory says 'deadlock'", + "ranked": [ + "kimetsu-query-stemming", + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y062H2NN1S48YS74CA719R", + "id": "01M1Y0BNDHRGPSNN01CBVH2TDA", + "kind": "memory", + "score": 0.9904030561447144, + "summary": "project:fact - [2026-09-07] [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression." + }, + { + "expansion_handle": "memory:01M1Y05YAM1J3BB2GHQRB5M0FP", + "id": "01M1Y0BNDHTQWM47HEW9EJJWQA", + "kind": "memory", + "score": 0.91664320230484, + "summary": "project:fact - [2026-09-07] [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure \u2014 `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 962.572, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1363, + "mcp_result_bytes": 1478, + "wire_bytes": 1515, + "reported_used_tokens": 1478, + "working_set_bytes": 293470208, + "peak_working_set_bytes": 294383616 + }, + { + "query": "how does pool size affect retrieval recall and latency in the bench?", + "ranked": [ + "kimetsu-rerank-pool" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y062J3KDZ9VQM7G169HV60", + "id": "01M1Y0BPB9DEKY9DQ1FKMSZYV7", + "kind": "memory", + "score": 0.9998373985290528, + "summary": "project:fact - [tags: kimetsu reranker pool size ann retrieval] kimetsu's retrieval pipeline: ANN (approximate nearest neighbor) retrieves a pool of candidates, then the reranker reorders them, then the top-K are returned. The pool size (default 6 for production, 12 in bench) controls the recall-latency tradeoff: larger pool = higher recall = more reranker calls = more latency. For the jina-tiny reranker, pool 12 adds ~80ms vs pool 6." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1018.4503000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 813, + "mcp_result_bytes": 894, + "wire_bytes": 931, + "reported_used_tokens": 894, + "working_set_bytes": 293470208, + "peak_working_set_bytes": 294383616 + }, + { + "query": "second embedder in a remote bench run gets worse results than the first", + "ranked": [ + "kimetsu-bench-remote-embedder-singleton" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y062K14KTD5D5SR7NWV35P", + "id": "01M1Y0BQBNHS73JRXSZVNRGSV4", + "kind": "memory", + "score": 0.9939629435539246, + "summary": "project:fact - [2026-09-07] [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1065.9832000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 895, + "mcp_result_bytes": 976, + "wire_bytes": 1013, + "reported_used_tokens": 976, + "working_set_bytes": 293470208, + "peak_working_set_bytes": 294383616 + }, + { + "query": "what is the expected JSON schema for kimetsu brain bench dataset files?", + "ranked": [ + "kimetsu-eval-fixture-shape", + "testing-fixture-drift", + "kimetsu-mrr-metric", + "mcp-schema-validation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y062M3CFFQGT02EBZTJSHN", + "id": "01M1Y0BRCH630CXGAAC3JA9QD0", + "kind": "memory", + "score": 0.9996767044067384, + "summary": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` \u2014 a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases)." + }, + { + "expansion_handle": "memory:01M1Y061NJMXF06HD6WSDEN8JZ", + "id": "01M1Y0BRCHV27B3QBVN3972AJ7", + "kind": "memory", + "score": 0.9682880640029908, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + }, + { + "expansion_handle": "memory:01M1Y062N6MYSHTZCNN349MES6", + "id": "01M1Y0BRCHVK21Z3HSWE61HR8S", + "kind": "memory", + "score": 0.8818408250808716, + "summary": "project:fact - [tags: kimetsu bench mrr recall metrics evaluation] kimetsu bench reports MRR (Mean Reciprocal Rank) and Recall@K. MRR is 1/rank_of_first_relevant_result, averaged across cases; it penalizes models that rank the correct answer 2nd or 3rd. Recall@K is the fraction of cases where at least one relevant answer appears in the top K." + }, + { + "expansion_handle": "memory:01M1Y061SPN3ZV6B1WDAVECD5V", + "id": "01M1Y0BRCHG1YAM41JATZ7R9N1", + "kind": "memory", + "score": 0.6527947187423706, + "summary": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array \u2014 omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1105.2305, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2424, + "mcp_result_bytes": 2603, + "wire_bytes": 2640, + "reported_used_tokens": 2603, + "working_set_bytes": 293470208, + "peak_working_set_bytes": 294383616 + }, + { + "query": "what does MRR mean and how do I interpret a 0.01 difference between combos?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1108.5317, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 293470208, + "peak_working_set_bytes": 294383616 + }, + { + "query": "SQLITE_BUSY keeps appearing even with WAL mode enabled", + "ranked": [ + "sqlite-busy-timeout-wal", + "sqlite-wal-network-drive" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05Z2BBHSJ0CDKCQ4XH9GE", + "id": "01M1Y0BTHQRZKQE63P8N5AXDCJ", + "kind": "memory", + "score": 0.9982662796974182, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + }, + { + "expansion_handle": "memory:01M1Y05Z57SYAWW7723HQ5J9NG", + "id": "01M1Y0BTHQY0MAJ69HWNXS3DVY", + "kind": "memory", + "score": 0.7844027280807495, + "summary": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1141.4902, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1423, + "mcp_result_bytes": 1522, + "wire_bytes": 1559, + "reported_used_tokens": 1522, + "working_set_bytes": 293691392, + "peak_working_set_bytes": 294600704 + }, + { + "query": "my brain file got huge again right after I compacted it", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1008.4443000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 293695488, + "peak_working_set_bytes": 294600704 + }, + { + "query": "all my FTS queries stopped returning results after I changed the tokenizer config", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1094.4026, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 293695488, + "peak_working_set_bytes": 294604800 + }, + { + "query": "something is preventing the kimetsu binary from being replaced during update", + "ranked": [ + "kimetsu-daemon-lifecycle", + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0629C2P66GZ8JJCVPWPR9", + "id": "01M1Y0BXQ6TPG3GZFTTFARST16", + "kind": "memory", + "score": 0.9678457975387572, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1Y05Z0A5WK204VYT0BQ2A96", + "id": "01M1Y0BXQ7Z455E5RNH7FCKH80", + "kind": "memory", + "score": 0.9395453929901124, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 0.6666666666666666, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 975.5285, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1657, + "mcp_result_bytes": 1756, + "wire_bytes": 1793, + "reported_used_tokens": 1756, + "working_set_bytes": 293695488, + "peak_working_set_bytes": 294604800 + }, + { + "query": "tool call results not appearing in the context \u2014 is the semantic floor too high?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1076.7582, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 293699584, + "peak_working_set_bytes": 294617088 + }, + { + "query": "CARGO_INCREMENTAL=0 in CI prevents a class of spurious compilation errors", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05ZFT0B24FSK97XY8Y0WX", + "id": "01M1Y0BZQAZ4Z8SYPTP5CJKC07", + "kind": "memory", + "score": 0.7995238304138184, + "summary": "project:fact - [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 989.5859, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 877, + "mcp_result_bytes": 958, + "wire_bytes": 995, + "reported_used_tokens": 958, + "working_set_bytes": 293826560, + "peak_working_set_bytes": 294739968 + }, + { + "query": "how do I check whether my Cargo workspace respects the MSRV constraint?", + "ranked": [ + "cargo-msrv", + "cargo-dev-dep-leak", + "cargo-patch-section", + "cargo-target-dir-sharing" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05ZJEDXBR4GP8409Q7V6X", + "id": "01M1Y0C0PPCEMY907DY4ZVB036", + "kind": "memory", + "score": 0.9921064376831056, + "summary": "project:fact - [tags: cargo rust msrv edition compatibility] Set `rust-version` in each `Cargo.toml` to declare the minimum supported Rust version (MSRV). Cargo enforces this with `--check`: `cargo check` fails if the toolchain is older than `rust-version`. Keep MSRV as old as your oldest supported deployment target." + }, + { + "expansion_handle": "memory:01M1Y05ZDSAHJXT2TMGQG83BWE", + "id": "01M1Y0C0PP9YFWGE3F28YT0MJ5", + "kind": "memory", + "score": 0.887407660484314, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + }, + { + "expansion_handle": "memory:01M1Y05ZHK2ZA3FKKK7GBBNKFB", + "id": "01M1Y0C0PPB6CN4KQ11YBBG56V", + "kind": "memory", + "score": 0.7220955491065979, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace \u2014 including transitive deps \u2014 that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1Y05ZETTM7QKBSZ1FY92RQ8", + "id": "01M1Y0C0PP2ZB6BKAGDVBMR4KQ", + "kind": "memory", + "score": 0.4095200598239898, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps \u2014 use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1115.5216, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2682, + "mcp_result_bytes": 2821, + "wire_bytes": 2858, + "reported_used_tokens": 2821, + "working_set_bytes": 293826560, + "peak_working_set_bytes": 294744064 + }, + { + "query": "rusqlite connection opened but ON DELETE CASCADE cascade never fires", + "ranked": [ + "sqlite-foreign-keys-default-off" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05Z843AFP72Y2BY2XZ4Q0", + "id": "01M1Y0C1SMT4SY9J4AQ1ZANHNR", + "kind": "memory", + "score": 0.9922945499420166, + "summary": "project:fact - [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting \u2014 every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1026.1435999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 735, + "mcp_result_bytes": 816, + "wire_bytes": 853, + "reported_used_tokens": 816, + "working_set_bytes": 293830656, + "peak_working_set_bytes": 294744064 + }, + { + "query": "I cannot connect to kimetsu-remote \u2014 something about TLS cert validation failed", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 963.361, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 293838848, + "peak_working_set_bytes": 294752256 + }, + { + "query": "graceful shutdown fails because in-flight SQLite queries are still running when pool closes", + "ranked": [ + "tokio-shutdown-ordering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0615DY78S91BYCR1PW38X", + "id": "01M1Y0C3QBDM24WCPBARBSR3FC", + "kind": "memory", + "score": 0.9996342658996582, + "summary": "project:fact - [2026-09-07] [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries \u2014 the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1012.2230999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 947, + "mcp_result_bytes": 1028, + "wire_bytes": 1065, + "reported_used_tokens": 1028, + "working_set_bytes": 293965824, + "peak_working_set_bytes": 294875136 + }, + { + "query": "kimetsu-remote response takes 8 seconds \u2014 which stage is slow?", + "ranked": [ + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061QHG7EX7KYBKD86CQYC", + "id": "01M1Y0C4Q0VBFCCZSMJ07GE7Z6", + "kind": "memory", + "score": 0.9876242876052856, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1066.8466999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 858, + "mcp_result_bytes": 939, + "wire_bytes": 976, + "reported_used_tokens": 939, + "working_set_bytes": 293965824, + "peak_working_set_bytes": 294875136 + }, + { + "query": "git reflog to rescue accidentally deleted branch", + "ranked": [ + "git-reflog-rescue" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060VJEBGRY23E0DJBZS5T", + "id": "01M1Y0C5RH06XCS1335S11RPQZ", + "kind": "memory", + "score": 0.998464822769165, + "summary": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone \u2014 they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only \u2014 remote reflog is not accessible via normal git commands." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1115.3663000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 761, + "mcp_result_bytes": 842, + "wire_bytes": 879, + "reported_used_tokens": 842, + "working_set_bytes": 293965824, + "peak_working_set_bytes": 294875136 + }, + { + "query": "git submodule --remote advances the pinned SHA unexpectedly", + "ranked": [ + "git-submodule-pinning", + "git-reflog-rescue", + "ci-secrets-masking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060THWEBZHRGKPBJRS88X", + "id": "01M1Y0C6V64HKHMZ7YGRK4M3NB", + "kind": "memory", + "score": 0.9998551607131958, + "summary": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip \u2014 this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version." + }, + { + "expansion_handle": "memory:01M1Y060VJEBGRY23E0DJBZS5T", + "id": "01M1Y0C6V6YY69CHAE0AQQ79J0", + "kind": "memory", + "score": 0.8857361078262329, + "summary": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone \u2014 they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only \u2014 remote reflog is not accessible via normal git commands." + }, + { + "expansion_handle": "memory:01M1Y0626EATKH9B42B4W92CC3", + "id": "01M1Y0C6V6HZZW29487S2ESHTZ", + "kind": "memory", + "score": 0.8434544205665588, + "summary": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output \u2014 but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 956.2325000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1771, + "mcp_result_bytes": 1888, + "wire_bytes": 1925, + "reported_used_tokens": 1888, + "working_set_bytes": 293965824, + "peak_working_set_bytes": 294875136 + }, + { + "query": "axum SSE streaming drops the last event when client disconnects", + "ranked": [ + "http-streaming-bodies" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061B3Z0S7MH3X6Y15J8MA", + "id": "01M1Y0C7S42DXVB7Q9AE42D5D6", + "kind": "memory", + "score": 0.9926375150680542, + "summary": "project:fact - [2026-09-07] [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding \u2014 a chunk may split across frame boundaries." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1076.2822, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 859, + "mcp_result_bytes": 940, + "wire_bytes": 977, + "reported_used_tokens": 940, + "working_set_bytes": 293965824, + "peak_working_set_bytes": 294875136 + }, + { + "query": "how do I detect that I am running inside a git worktree vs the main checkout?", + "ranked": [ + "git-worktree-brain-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060PEBE9HMM62FJ922NWN", + "id": "01M1Y0C8TVVAT1PWZH1VC4PHG4", + "kind": "memory", + "score": 0.9857924580574036, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root \u2014 if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1033.2648, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 881, + "mcp_result_bytes": 962, + "wire_bytes": 999, + "reported_used_tokens": 962, + "working_set_bytes": 293961728, + "peak_working_set_bytes": 294875136 + }, + { + "query": "ONNX Runtime intra-op threads causing CPU contention during parallel bench", + "ranked": [ + "onnx-ort-threading", + "tokio-blocking-in-async" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060NFDVT4KKYC4BDWH6D3", + "id": "01M1Y0C9VKQT3NJM7GQC7XKR6E", + "kind": "memory", + "score": 0.9999210834503174, + "summary": "project:fact - [tags: onnx ort thread-pool parallelism cpu] ORT (ONNX Runtime) creates its own inter-op and intra-op thread pools. In a multi-process bench setup, each child inherits these pools and they compete for CPU cores. Set `SessionOptionsBuilder::with_intra_threads(1).with_inter_threads(1)` if you're running many parallel bench processes \u2014 this sacrifices per-inference throughput for lower contention." + }, + { + "expansion_handle": "memory:01M1Y060WH1VTPAKJ89CENJJXP", + "id": "01M1Y0C9VKGN0GF8CFZPKJCBXK", + "kind": "memory", + "score": 0.5390238761901855, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 955.3720999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1328, + "mcp_result_bytes": 1427, + "wire_bytes": 1464, + "reported_used_tokens": 1427, + "working_set_bytes": 293965824, + "peak_working_set_bytes": 294875136 + }, + { + "query": "what is the right way to supply AWS session token alongside access key and secret?", + "ranked": [ + "aws-credentials-chain" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061WWY2XRNJE51QHGVRAM", + "id": "01M1Y0CAT4KGKPTJ7H6P1ZD7TC", + "kind": "memory", + "score": 0.9493365287780762, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1084.0350999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 895, + "mcp_result_bytes": 976, + "wire_bytes": 1013, + "reported_used_tokens": 976, + "working_set_bytes": 293965824, + "peak_working_set_bytes": 294875136 + } + ], + "id": "existing-development-100", + "dimension": "retrieval", + "tier": "hard", + "score": 0.8182539682539681, + "skipped": false, + "detail": "positive-recall@4=0.84 mrr=0.85 stale-hit=n/a resolution=n/a false-injection=0.538 (n=13) positive-n=197 negative-n=13 (210 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 0.8182539682539681, + 1 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 0.8182539682539681, + "n": 1, + "ci95": null + } + }, + "overall_index": 0.8182539682539681, + "scenario_weighted_index": 0.8182539682539681 +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-structured-facts/results/development/1-baseline.stderr.log b/docs/audits/2026-09-07-structured-facts/results/development/1-baseline.stderr.log new file mode 100644 index 0000000..68ccab3 --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/results/development/1-baseline.stderr.log @@ -0,0 +1,4 @@ +brainbench: 1 scenario(s) to run + [1/1] existing-development-100 | dim=retrieval tier=hard ... + -> score=0.82 | positive-recall@4=0.84 mrr=0.85 stale-hit=n/a resolution=n/a false-injection=0.538 (n=13) positive-n=197 negative-n=13 (210 queries) +kbench brainbench: report saved -> E:\tmp\kimetsu-brain-hardening\bench\local\runs\brainbench\2026-09-07T13-19-35.0784881Z.json diff --git a/docs/audits/2026-09-07-structured-facts/results/development/1-baseline.stdout.log b/docs/audits/2026-09-07-structured-facts/results/development/1-baseline.stdout.log new file mode 100644 index 0000000..fb538a1 --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/results/development/1-baseline.stdout.log @@ -0,0 +1,6811 @@ +{ + "generated_at": "2026-09-07T13:19:35.0771582Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-retrieval\\development-100.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "test_env_lock inside with_user_brain_disabled deadlock", + "ranked": [ + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YAM1J3BB2GHQRB5M0FP", + "id": "01M1Y063D0RMVQW7HADFVP5KFA", + "kind": "memory", + "score": 0.9999488592147828, + "summary": "project:fact - [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure — `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1075.8079, + "first_query": true, + "server_startup_ms": 79.9213, + "model_text_bytes": 796, + "mcp_result_bytes": 877, + "wire_bytes": 912, + "reported_used_tokens": 877, + "working_set_bytes": 227520512, + "peak_working_set_bytes": 248590336 + }, + { + "query": "why does my test hang after calling with_user_brain_disabled when I also lock test_env_lock?", + "ranked": [ + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YAM1J3BB2GHQRB5M0FP", + "id": "01M1Y064393KH3YHF2KCPMD5QE", + "kind": "memory", + "score": 0.9990190267562866, + "summary": "project:fact - [2026-09-07] [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure — `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 826.4118000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 808, + "mcp_result_bytes": 889, + "wire_bytes": 924, + "reported_used_tokens": 889, + "working_set_bytes": 229535744, + "peak_working_set_bytes": 248590336 + }, + { + "query": "ingest_repo_at_root brain_root files_root kimetsu remote", + "ranked": [ + "remote-ingest-split-roots", + "kimetsu-write-tools-gate", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YBPJ3QEY9DGV3Y39QED", + "id": "01M1Y064X3GJ6GMMCSWPMEZ6XV", + "kind": "memory", + "score": 0.999886393547058, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1Y062EEFBC57YJ58J3CSXFD", + "id": "01M1Y064X3FHARP6XQ47H02N4H", + "kind": "memory", + "score": 0.8439717888832092, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level — disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1Y05YDQZCEVNBN7TDX4XC9B", + "id": "01M1Y064X3DNTH66A6FPXE1XNB", + "kind": "memory", + "score": 0.8363722562789917, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 944.0074000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2726, + "mcp_result_bytes": 2891, + "wire_bytes": 2926, + "reported_used_tokens": 2891, + "working_set_bytes": 252657664, + "peak_working_set_bytes": 253562880 + }, + { + "query": "why does the remote server index the wrong directory when I run kimetsu brain ingest?", + "ranked": [ + "remote-ingest-split-roots", + "onnx-dim-mismatch" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YBPJ3QEY9DGV3Y39QED", + "id": "01M1Y065TVCNMMR7Y0RJ086YG0", + "kind": "memory", + "score": 0.9836117625236512, + "summary": "project:fact - [2026-09-07] [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1Y060H9Y1TBKN4BXG507K53", + "id": "01M1Y065TVW6SGR6XHG9W46DEG", + "kind": "memory", + "score": 0.3657674789428711, + "summary": "project:fact - [2026-09-07] [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results — the ANN index shape mismatch isn't always caught at runtime." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 933.9577999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1800, + "mcp_result_bytes": 1899, + "wire_bytes": 1934, + "reported_used_tokens": 1899, + "working_set_bytes": 258662400, + "peak_working_set_bytes": 259579904 + }, + { + "query": "kimetsu plugin install --remote mcp.json authorization bearer token", + "ranked": [ + "remote-mcp-host-wiring", + "mcp-stdout-protocol" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YDQZCEVNBN7TDX4XC9B", + "id": "01M1Y066R4WEA8HSJ2ZARWX2RV", + "kind": "memory", + "score": 0.999605119228363, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + }, + { + "expansion_handle": "memory:01M1Y061PEX980HR5500H8P7ZH", + "id": "01M1Y066R56PBJDVF3SCHJRVQQ", + "kind": "memory", + "score": 0.3375842869281769, + "summary": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 850.1499, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1472, + "mcp_result_bytes": 1619, + "wire_bytes": 1654, + "reported_used_tokens": 1619, + "working_set_bytes": 259514368, + "peak_working_set_bytes": 260427776 + }, + { + "query": "how do I wire a remote kimetsu brain into Claude Code without storing the token in the config file?", + "ranked": [ + "remote-mcp-host-wiring", + "mcp-tool-naming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YDQZCEVNBN7TDX4XC9B", + "id": "01M1Y067JK05KRFAPW1XEGBYVB", + "kind": "memory", + "score": 0.9963359832763672, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + }, + { + "expansion_handle": "memory:01M1Y061TPTKP8TGYS8ZZJBXTG", + "id": "01M1Y067JKJ1JV6T0D6YPXZC0S", + "kind": "memory", + "score": 0.831425666809082, + "summary": "project:fact - [tags: mcp tool naming convention kimetsu] MCP tool names must be valid identifiers for all host agents. Claude Code restricts tool names to `[a-zA-Z0-9_-]` and max 64 chars. Use `snake_case` (kimetsu_brain_context, kimetsu_brain_record) — hyphen is technically allowed but some hosts reject it." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 850.6700000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1454, + "mcp_result_bytes": 1601, + "wire_bytes": 1636, + "reported_used_tokens": 1601, + "working_set_bytes": 259895296, + "peak_working_set_bytes": 260816896 + }, + { + "query": "cargo feature unification kimetsu-brain embeddings fastembed test failure", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-profile-override", + "clap-version-build-flavor" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YFMXS61RMTQ16GE6735", + "id": "01M1Y068D38TZS9VY9MVTBHXB5", + "kind": "memory", + "score": 0.9996790885925292, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1Y05ZGMYJJMEZXJZA6WN6W3", + "id": "01M1Y068D3N8F1Q4XMQ2HY673K", + "kind": "memory", + "score": 0.9923595786094666, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1Y05YSZ7NPRA3AG5ES716WZ", + "id": "01M1Y068D3TQ0KJR972J66XJX2", + "kind": "memory", + "score": 0.585203230381012, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 850.2271999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2387, + "mcp_result_bytes": 2524, + "wire_bytes": 2559, + "reported_used_tokens": 2524, + "working_set_bytes": 261287936, + "peak_working_set_bytes": 262217728 + }, + { + "query": "my integration tests pass in isolation but break when I run cargo test --workspace — embedder changed?", + "ranked": [ + "cargo-feature-unification-embeddings", + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YFMXS61RMTQ16GE6735", + "id": "01M1Y0697PH5HXYFJYKGATZATE", + "kind": "memory", + "score": 0.9943140745162964, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1Y05YS4YPH8D67NSXJGT1SS", + "id": "01M1Y0697N61A1JJ4TY5TBME81", + "kind": "memory", + "score": 0.31398114562034607, + "summary": "project:fact - [2026-09-07] [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 890.0258, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1714, + "mcp_result_bytes": 1817, + "wire_bytes": 1852, + "reported_used_tokens": 1817, + "working_set_bytes": 261869568, + "peak_working_set_bytes": 262787072 + }, + { + "query": "build_anthropic_body bedrock-2023-05-31 InvokeModel blocking reqwest", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YH1NQKFMZ0R78E7Z8SP", + "id": "01M1Y06A413ZHYKF5VXSP4B3X7", + "kind": "memory", + "score": 0.9973788261413574, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1Y05YPKKNJWV8P7EZDPZJTG", + "id": "01M1Y06A41A7EJA8N19KN81AF6", + "kind": "memory", + "score": 0.6916899085044861, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 706.3015, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2193, + "mcp_result_bytes": 2320, + "wire_bytes": 2356, + "reported_used_tokens": 2320, + "working_set_bytes": 262266880, + "peak_working_set_bytes": 263172096 + }, + { + "query": "how do I add AWS Bedrock as a model provider in Kimetsu without pulling in the aws-sdk?", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-region-resolution", + "aws-credentials-chain", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YH1NQKFMZ0R78E7Z8SP", + "id": "01M1Y06ATDQCAPM18P1E0JSQ5S", + "kind": "memory", + "score": 0.9998898506164552, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1Y061Y6112PMFKN5GEJZE8M", + "id": "01M1Y06ATDXHY4T9N422SDPD0K", + "kind": "memory", + "score": 0.995676338672638, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1Y061WWY2XRNJE51QHGVRAM", + "id": "01M1Y06ATD8Y4TVX8WZNFB5Z5E", + "kind": "memory", + "score": 0.987064242362976, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + }, + { + "expansion_handle": "memory:01M1Y05YPKKNJWV8P7EZDPZJTG", + "id": "01M1Y06ATDPJPC2REACWKB3DM2", + "kind": "memory", + "score": 0.9493880867958068, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 869.6207999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3455, + "mcp_result_bytes": 3618, + "wire_bytes": 3654, + "reported_used_tokens": 3618, + "working_set_bytes": 270737408, + "peak_working_set_bytes": 271663104 + }, + { + "query": "BridgeTarget enum seams plugin_install_inner plugin_status_inner resolve_setup_hosts", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YJSTY39A6KQMMSK2DH6", + "id": "01M1Y06BMVT6DFXMKK227XQTS9", + "kind": "memory", + "score": 0.9997583031654358, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 712.6516, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1060, + "mcp_result_bytes": 1141, + "wire_bytes": 1177, + "reported_used_tokens": 1141, + "working_set_bytes": 280764416, + "peak_working_set_bytes": 281677824 + }, + { + "query": "I added a new host to the bridge enum but cargo gives me compile errors in five different match arms — what did I miss?", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YJSTY39A6KQMMSK2DH6", + "id": "01M1Y06CB7MX6JQ1KXSWXKSSSZ", + "kind": "memory", + "score": 0.9977060556411744, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 920.1981000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1059, + "mcp_result_bytes": 1140, + "wire_bytes": 1176, + "reported_used_tokens": 1140, + "working_set_bytes": 281092096, + "peak_working_set_bytes": 282001408 + }, + { + "query": "Pi extension factory defineExtension agent_end session_shutdown kimetsu.ts", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YKXXK0W4ER0PGC80N1C", + "id": "01M1Y06D7XA30PNDZJ6J3AY9AZ", + "kind": "memory", + "score": 0.9990354776382446, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 915.4213, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 804, + "mcp_result_bytes": 893, + "wire_bytes": 929, + "reported_used_tokens": 893, + "working_set_bytes": 281198592, + "peak_working_set_bytes": 282107904 + }, + { + "query": "how does Pi (earendil-works/pi) load plugins and what lifecycle hooks does it expose?", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YKXXK0W4ER0PGC80N1C", + "id": "01M1Y06E4HPSQPFZ2BWR98GW84", + "kind": "memory", + "score": 0.9934834837913512, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 937.7381, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 803, + "mcp_result_bytes": 892, + "wire_bytes": 928, + "reported_used_tokens": 892, + "working_set_bytes": 281620480, + "peak_working_set_bytes": 282525696 + }, + { + "query": "aws-sigv4 SigningParams apply_to_request_http1x reqwest sign-http", + "ranked": [ + "aws-sigv4-bedrock-blocking", + "aws-presigned-urls", + "bedrock-kimetsu-provider", + "aws-credentials-chain" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YPKKNJWV8P7EZDPZJTG", + "id": "01M1Y06F1YKKBZ4V911T24VZAN", + "kind": "memory", + "score": 0.9995608925819396, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1Y0620B2W192A9Y86X0ZB3D", + "id": "01M1Y06F1YFE0TM6K5M3TBCXXV", + "kind": "memory", + "score": 0.984916627407074, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time — clock skew > 15 minutes causes `RequestTimeTooSkewed`." + }, + { + "expansion_handle": "memory:01M1Y05YH1NQKFMZ0R78E7Z8SP", + "id": "01M1Y06F1YP4VHVFGFFXBD0D95", + "kind": "memory", + "score": 0.983895778656006, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1Y061WWY2XRNJE51QHGVRAM", + "id": "01M1Y06F1Y9HY0GBQFG6HB7VGC", + "kind": "memory", + "score": 0.8592692017555237, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 699.4605, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3507, + "mcp_result_bytes": 3670, + "wire_bytes": 3706, + "reported_used_tokens": 3670, + "working_set_bytes": 281686016, + "peak_working_set_bytes": 282587136 + }, + { + "query": "how do I sign a Bedrock InvokeModel request with aws-sigv4 in blocking Rust?", + "ranked": [ + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider", + "aws-region-resolution", + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YPKKNJWV8P7EZDPZJTG", + "id": "01M1Y06FQSMTF0D2KKK95FR9WB", + "kind": "memory", + "score": 0.9998323917388916, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1Y05YH1NQKFMZ0R78E7Z8SP", + "id": "01M1Y06FQSYC99NE0M26351B7Q", + "kind": "memory", + "score": 0.9970844388008118, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1Y061Y6112PMFKN5GEJZE8M", + "id": "01M1Y06FQSKSM3YKW9GWAZMK2N", + "kind": "memory", + "score": 0.9468621611595154, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1Y0620B2W192A9Y86X0ZB3D", + "id": "01M1Y06FQS30QXMMS0YKQDG1BA", + "kind": "memory", + "score": 0.9210098385810852, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time — clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 865.6446, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3434, + "mcp_result_bytes": 3597, + "wire_bytes": 3633, + "reported_used_tokens": 3597, + "working_set_bytes": 281763840, + "peak_working_set_bytes": 282677248 + }, + { + "query": "KIMETSU_RUNS_GC env opt-out TraceWriter create gc_old_runs caller", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YRBDY77MTARFTEY6ESW", + "id": "01M1Y06GK17W2340CFPP183C3S", + "kind": "memory", + "score": 0.999936580657959, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 819.4787, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 761, + "mcp_result_bytes": 842, + "wire_bytes": 878, + "reported_used_tokens": 842, + "working_set_bytes": 282001408, + "peak_working_set_bytes": 282910720 + }, + { + "query": "where should I put the KIMETSU_RUNS_GC=0 guard — inside the GC function or at the call site?", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YRBDY77MTARFTEY6ESW", + "id": "01M1Y06HCRHNCK3TP7ZSTDR6Z6", + "kind": "memory", + "score": 0.9971211552619934, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 933.4207, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 762, + "mcp_result_bytes": 843, + "wire_bytes": 879, + "reported_used_tokens": 843, + "working_set_bytes": 282267648, + "peak_working_set_bytes": 283185152 + }, + { + "query": "git_init_boundary ProjectPaths::discover temp dir user brain isolation", + "ranked": [ + "init-project-git-boundary", + "git-worktree-brain-isolation", + "testing-temp-dirs-ci", + "kimetsu-memory-scopes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YS4YPH8D67NSXJGT1SS", + "id": "01M1Y06JAKCM09DYT87VK3DRF9", + "kind": "memory", + "score": 0.9997712969779968, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + }, + { + "expansion_handle": "memory:01M1Y060PEBE9HMM62FJ922NWN", + "id": "01M1Y06JAK78CYH5YVF0RQ43QQ", + "kind": "memory", + "score": 0.9962491393089294, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root — if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + }, + { + "expansion_handle": "memory:01M1Y061EHA1QHS9A92RMVXS4T", + "id": "01M1Y06JAKX511CWK76MT4N4G6", + "kind": "memory", + "score": 0.9682154655456544, + "summary": "project:fact - [tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure." + }, + { + "expansion_handle": "memory:01M1Y062B89NB3SMAHX7SX830R", + "id": "01M1Y06JAKMPT3MSZG19JYQ4FT", + "kind": "memory", + "score": 0.3057229816913605, + "summary": "project:fact - [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available — if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 804.396, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2580, + "mcp_result_bytes": 2715, + "wire_bytes": 2751, + "reported_used_tokens": 2715, + "working_set_bytes": 282431488, + "peak_working_set_bytes": 283340800 + }, + { + "query": "my test calls init_project but it writes to the real ~/.kimetsu instead of the temp folder — why?", + "ranked": [ + "init-project-git-boundary", + "cargo-feature-unification-embeddings", + "testing-fixture-drift", + "tokio-runtime-in-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YS4YPH8D67NSXJGT1SS", + "id": "01M1Y06K2VGR31HWTH2GFP7YFJ", + "kind": "memory", + "score": 0.9995088577270508, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + }, + { + "expansion_handle": "memory:01M1Y05YFMXS61RMTQ16GE6735", + "id": "01M1Y06K2VRZ13BMK6T6Y1MWHQ", + "kind": "memory", + "score": 0.7287850975990295, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1Y061NJMXF06HD6WSDEN8JZ", + "id": "01M1Y06K2WK40JCDR057KA6KYT", + "kind": "memory", + "score": 0.6596062183380127, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + }, + { + "expansion_handle": "memory:01M1Y060XP0KSRSNNYT7NE7D4W", + "id": "01M1Y06K2WMD3DW4PPCY1DM18B", + "kind": "memory", + "score": 0.3297702968120575, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 909.9809, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2833, + "mcp_result_bytes": 2980, + "wire_bytes": 3016, + "reported_used_tokens": 2980, + "working_set_bytes": 283172864, + "peak_working_set_bytes": 284082176 + }, + { + "query": "clap command version KIMETSU_VERSION_DISPLAY cfg feature embeddings", + "ranked": [ + "clap-version-build-flavor", + "cargo-feature-unification-embeddings" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YSZ7NPRA3AG5ES716WZ", + "id": "01M1Y06KZ8176AFPD1365C7BDN", + "kind": "memory", + "score": 0.9996613264083862, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + }, + { + "expansion_handle": "memory:01M1Y05YFMXS61RMTQ16GE6735", + "id": "01M1Y06KZ8VH1PPQQAYTAF5ZQ7", + "kind": "memory", + "score": 0.3973360061645508, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 702.3792, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1922, + "mcp_result_bytes": 2041, + "wire_bytes": 2077, + "reported_used_tokens": 2041, + "working_set_bytes": 283545600, + "peak_working_set_bytes": 284446720 + }, + { + "query": "how do I show the build flavor (lean vs embeddings) in the kimetsu --version output?", + "ranked": [ + "clap-version-build-flavor", + "cargo-feature-unification-embeddings", + "onnx-quantization-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YSZ7NPRA3AG5ES716WZ", + "id": "01M1Y06MNAYPTZK0ZV9SK1K9ZT", + "kind": "memory", + "score": 0.9978312849998474, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + }, + { + "expansion_handle": "memory:01M1Y05YFMXS61RMTQ16GE6735", + "id": "01M1Y06MNAB8RXV48R95CTYB4V", + "kind": "memory", + "score": 0.8926984667778015, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1Y060DK4AZTG8FQ5SZ82QD8", + "id": "01M1Y06MNA85JCJHNZN6T3EPJB", + "kind": "memory", + "score": 0.8877003192901611, + "summary": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals — cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 920.5296, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2672, + "mcp_result_bytes": 2809, + "wire_bytes": 2845, + "reported_used_tokens": 2809, + "working_set_bytes": 283856896, + "peak_working_set_bytes": 284770304 + }, + { + "query": "Harbor pyiceberg os.getcwd stale WSL2 DrvFs worker-result subprocess re-exec", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YV5H4YVBX03WZFS7KSE", + "id": "01M1Y06NJ3FTB83YKWY1ZQVR57", + "kind": "memory", + "score": 0.9998155236244202, + "summary": "project:fact - [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 933.6877999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1026, + "mcp_result_bytes": 1107, + "wire_bytes": 1143, + "reported_used_tokens": 1107, + "working_set_bytes": 283987968, + "peak_working_set_bytes": 284893184 + }, + { + "query": "why does my kbench sweep crash after the first trial with 'result.json missing' on WSL2?", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YV5H4YVBX03WZFS7KSE", + "id": "01M1Y06PFBXY3SVK8AA79RRDQR", + "kind": "memory", + "score": 0.998451828956604, + "summary": "project:fact - [2026-09-07] [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 938.0493, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1038, + "mcp_result_bytes": 1119, + "wire_bytes": 1155, + "reported_used_tokens": 1119, + "working_set_bytes": 284004352, + "peak_working_set_bytes": 284921856 + }, + { + "query": "rusqlite VACUUM transaction WAL checkpoint wal_checkpoint TRUNCATE", + "ranked": [ + "sqlite-vacuum-wal-checkpoint", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YWDJ8MDAQEFE8B75QQR", + "id": "01M1Y06QCMW0KEPJ950MK8R50X", + "kind": "memory", + "score": 0.9996871948242188, + "summary": "project:fact - [tags: rust sqlite vacuum rusqlite windows] When implementing SQLite VACUUM in rusqlite: VACUUM cannot run inside a transaction. rusqlite's Connection does not hold an implicit transaction, so `conn.execute_batch(\"VACUUM;\")` works directly. After VACUUM, run `PRAGMA wal_checkpoint(TRUNCATE);` before measuring file size — on Windows the WAL file can hold significant space that isn't reflected in the main db file until the checkpoint runs." + }, + { + "expansion_handle": "memory:01M1Y05Z2BBHSJ0CDKCQ4XH9GE", + "id": "01M1Y06QCM96SYD38WB1J078NZ", + "kind": "memory", + "score": 0.5274003744125366, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 710.7049, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1507, + "mcp_result_bytes": 1610, + "wire_bytes": 1646, + "reported_used_tokens": 1610, + "working_set_bytes": 284020736, + "peak_working_set_bytes": 284921856 + }, + { + "query": "my SQLite VACUUM reports the file shrank but the disk usage stayed the same — Windows WAL?", + "ranked": [ + "sqlite-vacuum-wal-checkpoint", + "sqlite-wal-network-drive" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YWDJ8MDAQEFE8B75QQR", + "id": "01M1Y06R35W8744DYTF20ZXVTK", + "kind": "memory", + "score": 0.9155893921852112, + "summary": "project:fact - [tags: rust sqlite vacuum rusqlite windows] When implementing SQLite VACUUM in rusqlite: VACUUM cannot run inside a transaction. rusqlite's Connection does not hold an implicit transaction, so `conn.execute_batch(\"VACUUM;\")` works directly. After VACUUM, run `PRAGMA wal_checkpoint(TRUNCATE);` before measuring file size — on Windows the WAL file can hold significant space that isn't reflected in the main db file until the checkpoint runs." + }, + { + "expansion_handle": "memory:01M1Y05Z57SYAWW7723HQ5J9NG", + "id": "01M1Y06R3644EV4G9GC9ZK9CZW", + "kind": "memory", + "score": 0.902395486831665, + "summary": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 956.1005, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1357, + "mcp_result_bytes": 1460, + "wire_bytes": 1496, + "reported_used_tokens": 1460, + "working_set_bytes": 284119040, + "peak_working_set_bytes": 285032448 + }, + { + "query": "add_memory import dedup seen_ids snapshot pre-existing active memory IDs", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YX5TE9MYEX6EDV1N9YT", + "id": "01M1Y06S11VX7M8TDVQ52J8P0R", + "kind": "memory", + "score": 0.9999133348464966, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount — both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 843.1904999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 966, + "mcp_result_bytes": 1047, + "wire_bytes": 1083, + "reported_used_tokens": 1047, + "working_set_bytes": 284147712, + "peak_working_set_bytes": 285052928 + }, + { + "query": "brain import re-imports the same JSON file but the deduplication counter is wrong — why?", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YX5TE9MYEX6EDV1N9YT", + "id": "01M1Y06SW49XS0RNWMMJ7MK6YN", + "kind": "memory", + "score": 0.9254016876220704, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount — both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 889.6086, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 965, + "mcp_result_bytes": 1046, + "wire_bytes": 1082, + "reported_used_tokens": 1046, + "working_set_bytes": 284422144, + "peak_working_set_bytes": 285335552 + }, + { + "query": "toml::from_str Value parse document unexpected content str.parse", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YY75RMS67WX826FQJEE", + "id": "01M1Y06TPXGA80MB7MPMZVAZZ5", + "kind": "memory", + "score": 0.9991866946220398, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 739.3157, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 734, + "mcp_result_bytes": 815, + "wire_bytes": 851, + "reported_used_tokens": 815, + "working_set_bytes": 284422144, + "peak_working_set_bytes": 285335552 + }, + { + "query": "how do I parse a TOML configuration file into a toml::Value in toml 0.9?", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YY75RMS67WX826FQJEE", + "id": "01M1Y06VM2QNNY1JJC8PGBY7CE", + "kind": "memory", + "score": 0.9992641806602478, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1087.2188, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 733, + "mcp_result_bytes": 814, + "wire_bytes": 850, + "reported_used_tokens": 814, + "working_set_bytes": 284426240, + "peak_working_set_bytes": 285335552 + }, + { + "query": "CIM CreationDate DMTF WMI ps etimes started_at assess_mcp_skew", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YZ40GGK5Q4S0G3YMFPW", + "id": "01M1Y06WGDH15NQ6TC0M61PKG0", + "kind": "memory", + "score": 0.9957948923110962, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 689.1389, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 924, + "mcp_result_bytes": 1013, + "wire_bytes": 1049, + "reported_used_tokens": 1013, + "working_set_bytes": 284434432, + "peak_working_set_bytes": 285339648 + }, + { + "query": "how do I read a process start time on both Windows and Linux in pure Rust?", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YZ40GGK5Q4S0G3YMFPW", + "id": "01M1Y06X5M7QVAG77JNXGEPTSY", + "kind": "memory", + "score": 0.99687659740448, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 895.991, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 921, + "mcp_result_bytes": 1010, + "wire_bytes": 1046, + "reported_used_tokens": 1010, + "working_set_bytes": 284442624, + "peak_working_set_bytes": 285364224 + }, + { + "query": "processes_locking_target decide_preflight_action BufRead Write update.rs", + "ranked": [ + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05Z0A5WK204VYT0BQ2A96", + "id": "01M1Y06Y1NJ5T6AFCCEWXK7Y7R", + "kind": "memory", + "score": 0.9995336532592772, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics — mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 760.8530999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1133, + "mcp_result_bytes": 1214, + "wire_bytes": 1250, + "reported_used_tokens": 1214, + "working_set_bytes": 284508160, + "peak_working_set_bytes": 285425664 + }, + { + "query": "how should I reuse the existing process enumerator in the update preflight check to avoid a second PowerShell query?", + "ranked": [ + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05Z0A5WK204VYT0BQ2A96", + "id": "01M1Y06YT1GZ3DEBWMMYK9RZVQ", + "kind": "memory", + "score": 0.9973384737968444, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics — mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 969.5202, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1132, + "mcp_result_bytes": 1213, + "wire_bytes": 1249, + "reported_used_tokens": 1213, + "working_set_bytes": 284774400, + "peak_working_set_bytes": 285687808 + }, + { + "query": "cfg_attr windows allow dead_code parse_unix_ps cross-platform tests", + "ranked": [ + "cfg-cross-platform-dead-code", + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05Z1FDES73B8EQHWJPVMA", + "id": "01M1Y06ZR1W4HM8ZPH5HCA5F2C", + "kind": "memory", + "score": 0.9999476671218872, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + }, + { + "expansion_handle": "memory:01M1Y05YZ40GGK5Q4S0G3YMFPW", + "id": "01M1Y06ZR2F6HJGVGZV7DHB0BA", + "kind": "memory", + "score": 0.9764312505722046, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 724.8495, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1518, + "mcp_result_bytes": 1625, + "wire_bytes": 1661, + "reported_used_tokens": 1625, + "working_set_bytes": 284942336, + "peak_working_set_bytes": 285855744 + }, + { + "query": "how do I keep a function that is only called on Unix from triggering dead_code warnings on Windows?", + "ranked": [ + "cfg-cross-platform-dead-code" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05Z1FDES73B8EQHWJPVMA", + "id": "01M1Y070EPJ9VW4BANQ21W7VC0", + "kind": "memory", + "score": 0.9988092184066772, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 888.7517, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 939, + "reported_used_tokens": 903, + "working_set_bytes": 285220864, + "peak_working_set_bytes": 286130176 + }, + { + "query": "deadlocking a Rust mutex in integration tests", + "ranked": [ + "mutex-deadlock-user-brain-disabled", + "testing-serial-vs-parallel", + "kimetsu-query-stemming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YAM1J3BB2GHQRB5M0FP", + "id": "01M1Y071B5MEB9M2CM35JHAD6W", + "kind": "memory", + "score": 0.9997490048408508, + "summary": "project:fact - [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure — `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + }, + { + "expansion_handle": "memory:01M1Y061HRKB2V85Z6MW33WPRE", + "id": "01M1Y071B51PRX6B9KJN9WC89D", + "kind": "memory", + "score": 0.9057517647743224, + "summary": "project:fact - [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`)." + }, + { + "expansion_handle": "memory:01M1Y062H2NN1S48YS74CA719R", + "id": "01M1Y071B5N89S43FRQT57Y90M", + "kind": "memory", + "score": 0.4889622032642365, + "summary": "project:fact - [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 865.5951, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1930, + "mcp_result_bytes": 2063, + "wire_bytes": 2099, + "reported_used_tokens": 2063, + "working_set_bytes": 285224960, + "peak_working_set_bytes": 286134272 + }, + { + "query": "benchmarking retrieval quality across embedders", + "ranked": [ + "kimetsu-bench-remote-embedder-singleton", + "onnx-quantization-drift", + "cargo-feature-unification-embeddings" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y062K14KTD5D5SR7NWV35P", + "id": "01M1Y0725ABHJY4EHSRKRVE560", + "kind": "memory", + "score": 0.988014280796051, + "summary": "project:fact - [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval." + }, + { + "expansion_handle": "memory:01M1Y060DK4AZTG8FQ5SZ82QD8", + "id": "01M1Y0725AWBQDV85KN2NJDHF5", + "kind": "memory", + "score": 0.985597550868988, + "summary": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals — cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + }, + { + "expansion_handle": "memory:01M1Y05YFMXS61RMTQ16GE6735", + "id": "01M1Y0725A8V7G2P357R4FPQV6", + "kind": "memory", + "score": 0.5341982841491699, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 713.9199000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2537, + "mcp_result_bytes": 2658, + "wire_bytes": 2694, + "reported_used_tokens": 2658, + "working_set_bytes": 285229056, + "peak_working_set_bytes": 286134272 + }, + { + "query": "process memory working set RSS peak measurement Windows", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 891.3518, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 285384704, + "peak_working_set_bytes": 286273536 + }, + { + "query": "cloning a git repository server-side into a managed checkout", + "ranked": [ + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YBPJ3QEY9DGV3Y39QED", + "id": "01M1Y073QHJ9G4DP0P40DZS18A", + "kind": "memory", + "score": 0.9466677904129028, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 742.6744, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1261, + "mcp_result_bytes": 1342, + "wire_bytes": 1378, + "reported_used_tokens": 1342, + "working_set_bytes": 285671424, + "peak_working_set_bytes": 286576640 + }, + { + "query": "SigV4 signing HTTP requests in Rust", + "ranked": [ + "aws-presigned-urls", + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0620B2W192A9Y86X0ZB3D", + "id": "01M1Y074EVDWQXA5MPG7KYTSSW", + "kind": "memory", + "score": 0.9992632269859314, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time — clock skew > 15 minutes causes `RequestTimeTooSkewed`." + }, + { + "expansion_handle": "memory:01M1Y05YPKKNJWV8P7EZDPZJTG", + "id": "01M1Y074EV8EH9CQSVYKSAAEH8", + "kind": "memory", + "score": 0.9991399049758912, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1Y05YH1NQKFMZ0R78E7Z8SP", + "id": "01M1Y074EVBVRF7R511NMJPYQ2", + "kind": "memory", + "score": 0.9803794622421264, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 0.5, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 838.6473, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2840, + "mcp_result_bytes": 2985, + "wire_bytes": 3021, + "reported_used_tokens": 2985, + "working_set_bytes": 285876224, + "peak_working_set_bytes": 286769152 + }, + { + "query": "cargo test --workspace feature flag changes broke my unit tests", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-dev-dep-leak", + "ci-flaky-quarantine" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YFMXS61RMTQ16GE6735", + "id": "01M1Y0759HJF3HX3XEJKRSMNDN", + "kind": "memory", + "score": 0.997899889945984, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1Y05ZDSAHJXT2TMGQG83BWE", + "id": "01M1Y0759HP3CZ2JDW7D729NXR", + "kind": "memory", + "score": 0.9901249408721924, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + }, + { + "expansion_handle": "memory:01M1Y06288TD3HYPJQNP7S1ZNB", + "id": "01M1Y0759HSAKAA32VKBD2H9DQ", + "kind": "memory", + "score": 0.835382342338562, + "summary": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal — a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 754.432, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2383, + "mcp_result_bytes": 2504, + "wire_bytes": 2540, + "reported_used_tokens": 2504, + "working_set_bytes": 286167040, + "peak_working_set_bytes": 287076352 + }, + { + "query": "how do I make pasta carbonara?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 771.2394, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 286216192, + "peak_working_set_bytes": 287129600 + }, + { + "query": "what is the offside rule in football?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 968.4507, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 286248960, + "peak_working_set_bytes": 287154176 + }, + { + "query": "best way to train for a half marathon", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 972.4001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 286552064, + "peak_working_set_bytes": 287469568 + }, + { + "query": "my test passes when I run it alone but fails under cargo test --workspace", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YFMXS61RMTQ16GE6735", + "id": "01M1Y078P5K5BDNTRG368ET6TX", + "kind": "memory", + "score": 0.9907942414283752, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1Y05ZDSAHJXT2TMGQG83BWE", + "id": "01M1Y078P5Z1RCKD3AZRDYPQQ1", + "kind": "memory", + "score": 0.986136794090271, + "summary": "project:fact - [2026-09-07] [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 952.4747, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1863, + "mcp_result_bytes": 1966, + "wire_bytes": 2002, + "reported_used_tokens": 1966, + "working_set_bytes": 287043584, + "peak_working_set_bytes": 287956992 + }, + { + "query": "all the project tests started hanging forever after I added my new test", + "ranked": [ + "cargo-feature-unification-embeddings", + "tokio-runtime-in-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YFMXS61RMTQ16GE6735", + "id": "01M1Y079M1WG7XHB4RRJ6N3TBJ", + "kind": "memory", + "score": 0.774284839630127, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1Y060XP0KSRSNNYT7NE7D4W", + "id": "01M1Y079M1ZPKTDY3KTTN57H9E", + "kind": "memory", + "score": 0.33030807971954346, + "summary": "project:fact - [2026-09-07] [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 903.3887000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1763, + "mcp_result_bytes": 1874, + "wire_bytes": 1910, + "reported_used_tokens": 1874, + "working_set_bytes": 287182848, + "peak_working_set_bytes": 288088064 + }, + { + "query": "my integration test silently wrote memories into my real home brain instead of the temp workspace", + "ranked": [ + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YS4YPH8D67NSXJGT1SS", + "id": "01M1Y07AFHCRP8JKTHC9J29PQ8", + "kind": "memory", + "score": 0.9922831654548644, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 880.3425, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 780, + "mcp_result_bytes": 861, + "wire_bytes": 897, + "reported_used_tokens": 861, + "working_set_bytes": 287240192, + "peak_working_set_bytes": 288153600 + }, + { + "query": "where should the env-var opt-out check live for a cleanup feature triggered from a hot code path", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YRBDY77MTARFTEY6ESW", + "id": "01M1Y07BB0X99T4D4B25N29G80", + "kind": "memory", + "score": 0.9952055215835572, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 917.7221, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 761, + "mcp_result_bytes": 842, + "wire_bytes": 878, + "reported_used_tokens": 842, + "working_set_bytes": 287256576, + "peak_working_set_bytes": 288169984 + }, + { + "query": "the brain database file stays huge on Windows even after deleting most rows", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 896.4202, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 287313920, + "peak_working_set_bytes": 288231424 + }, + { + "query": "re-importing the same exported memories file counts them as new instead of deduplicated", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YX5TE9MYEX6EDV1N9YT", + "id": "01M1Y07D40C54M0ZJ71GZ0C158", + "kind": "memory", + "score": 0.9878425598144532, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount — both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 900.6208, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 965, + "mcp_result_bytes": 1046, + "wire_bytes": 1082, + "reported_used_tokens": 1046, + "working_set_bytes": 287326208, + "peak_working_set_bytes": 288239616 + }, + { + "query": "a helper function only called on Unix at runtime fails the dead-code lint on the Windows build", + "ranked": [ + "cfg-cross-platform-dead-code", + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05Z1FDES73B8EQHWJPVMA", + "id": "01M1Y07DZYAZVFW8V8A7YE00KA", + "kind": "memory", + "score": 0.9971064925193788, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + }, + { + "expansion_handle": "memory:01M1Y05Z0A5WK204VYT0BQ2A96", + "id": "01M1Y07DZYFRRHGNC89PW5DGN1", + "kind": "memory", + "score": 0.427912950515747, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics — mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1095.172, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1728, + "mcp_result_bytes": 1827, + "wire_bytes": 1863, + "reported_used_tokens": 1827, + "working_set_bytes": 287399936, + "peak_working_set_bytes": 288317440 + }, + { + "query": "the second Terminal-Bench trial always crashes even though the first one passes", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YV5H4YVBX03WZFS7KSE", + "id": "01M1Y07F2F7AP4ZAP92NN54B9W", + "kind": "memory", + "score": 0.9963042736053468, + "summary": "project:fact - [2026-09-07] [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 915.91, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1038, + "mcp_result_bytes": 1119, + "wire_bytes": 1155, + "reported_used_tokens": 1119, + "working_set_bytes": 287440896, + "peak_working_set_bytes": 288350208 + }, + { + "query": "how does doctor tell a running MCP server process is older than the kimetsu binary on disk", + "ranked": [ + "kimetsu-daemon-lifecycle", + "process-start-time-cross-platform", + "mcp-env-propagation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0629C2P66GZ8JJCVPWPR9", + "id": "01M1Y07FZ0H0MTYMAGPJDXR8P1", + "kind": "memory", + "score": 0.9985345602035522, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1Y05YZ40GGK5Q4S0G3YMFPW", + "id": "01M1Y07FZ0DNQQ2GWSE35E1TWG", + "kind": "memory", + "score": 0.9438157677650452, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + }, + { + "expansion_handle": "memory:01M1Y061RKZW8EMQN44S984P8P", + "id": "01M1Y07FZ0929WCTXYZ1RH8DKN", + "kind": "memory", + "score": 0.33611738681793213, + "summary": "project:fact - [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment — changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 0.5, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 915.9819, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1936, + "mcp_result_bytes": 2061, + "wire_bytes": 2097, + "reported_used_tokens": 2061, + "working_set_bytes": 287473664, + "peak_working_set_bytes": 288387072 + }, + { + "query": "the self-update preflight needs the list of running kimetsu processes without re-running the OS query", + "ranked": [ + "windows-update-process-locking", + "kimetsu-daemon-lifecycle" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05Z0A5WK204VYT0BQ2A96", + "id": "01M1Y07GW7V6YW7NN1TJE4E15F", + "kind": "memory", + "score": 0.9972410202026368, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics — mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + }, + { + "expansion_handle": "memory:01M1Y0629C2P66GZ8JJCVPWPR9", + "id": "01M1Y07GW7MCTP1T9TVP8PWV3Z", + "kind": "memory", + "score": 0.8902595043182373, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 906.6437000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1658, + "mcp_result_bytes": 1757, + "wire_bytes": 1793, + "reported_used_tokens": 1757, + "working_set_bytes": 287473664, + "peak_working_set_bytes": 288387072 + }, + { + "query": "parsing the WMI DMTF CreationDate timestamp into epoch seconds without extra crates", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YZ40GGK5Q4S0G3YMFPW", + "id": "01M1Y07HQRMFVCR1Z2G7N7QSYC", + "kind": "memory", + "score": 0.9258026480674744, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 920.3574, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 924, + "mcp_result_bytes": 1013, + "wire_bytes": 1049, + "reported_used_tokens": 1013, + "working_set_bytes": 287473664, + "peak_working_set_bytes": 288387072 + }, + { + "query": "calling Bedrock InvokeModel from blocking reqwest without the aws sdk", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking", + "aws-region-resolution", + "aws-retry-throttling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YH1NQKFMZ0R78E7Z8SP", + "id": "01M1Y07JMKZNHDZ4NNAA7B149H", + "kind": "memory", + "score": 0.9991798996925354, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1Y05YPKKNJWV8P7EZDPZJTG", + "id": "01M1Y07JMK7M1C0M3TDFRJDG82", + "kind": "memory", + "score": 0.999082326889038, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1Y061Y6112PMFKN5GEJZE8M", + "id": "01M1Y07JMKMXJQJ559H7GHG5ME", + "kind": "memory", + "score": 0.8391201496124268, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1Y061Z7A6RC4W57V7Z3CXS9", + "id": "01M1Y07JMKXQ23M72FZRCN5W6Y", + "kind": "memory", + "score": 0.4906356632709503, + "summary": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with ±25% jitter." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1007.8635, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3330, + "mcp_result_bytes": 3509, + "wire_bytes": 3545, + "reported_used_tokens": 3509, + "working_set_bytes": 287473664, + "peak_working_set_bytes": 288387072 + }, + { + "query": "how do I rotate the encryption key protecting the kimetsu brain database", + "ranked": [ + "kimetsu-eval-fixture-shape" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y062M3CFFQGT02EBZTJSHN", + "id": "01M1Y07KMSTZSS6R25V5VCH5Z9", + "kind": "memory", + "score": 0.8046634197235107, + "summary": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` — a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases)." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 996.8795, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 817, + "mcp_result_bytes": 942, + "wire_bytes": 978, + "reported_used_tokens": 942, + "working_set_bytes": 287506432, + "peak_working_set_bytes": 288411648 + }, + { + "query": "which tokio runtime worker-thread settings does the kimetsu MCP server use", + "ranked": [ + "tokio-blocking-in-async", + "tokio-runtime-in-tests", + "mcp-stdout-protocol" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060WH1VTPAKJ89CENJJXP", + "id": "01M1Y07MKC08XH1QTJR63AQDQ3", + "kind": "memory", + "score": 0.9973159432411194, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking — never call rusqlite directly from an async fn without spawn_blocking." + }, + { + "expansion_handle": "memory:01M1Y060XP0KSRSNNYT7NE7D4W", + "id": "01M1Y07MKCWSS2KJWS27YX26AS", + "kind": "memory", + "score": 0.8583173155784607, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + }, + { + "expansion_handle": "memory:01M1Y061PEX980HR5500H8P7ZH", + "id": "01M1Y07MKC7095SQEB4QQC9W1T", + "kind": "memory", + "score": 0.8141786456108093, + "summary": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 978.3624, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1847, + "mcp_result_bytes": 1972, + "wire_bytes": 2008, + "reported_used_tokens": 1972, + "working_set_bytes": 288198656, + "peak_working_set_bytes": 289107968 + }, + { + "query": "how does kimetsu sync memories between two machines over the network", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 896.3438, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288231424, + "peak_working_set_bytes": 289140736 + }, + { + "query": "recovering a corrupted usearch ANN index after a power loss", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 808.9003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288235520, + "peak_working_set_bytes": 289144832 + }, + { + "query": "what postgres schema should I use to store kimetsu memories", + "ranked": [ + "kimetsu-memory-scopes", + "testing-fixture-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y062B89NB3SMAHX7SX830R", + "id": "01M1Y07Q7GRTMA4HDBY8D4J6TH", + "kind": "memory", + "score": 0.9890244603157043, + "summary": "project:fact - [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available — if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope." + }, + { + "expansion_handle": "memory:01M1Y061NJMXF06HD6WSDEN8JZ", + "id": "01M1Y07Q7GGVA8J9GP9X9FD7NK", + "kind": "memory", + "score": 0.8922504782676697, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 896.2509, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1389, + "mcp_result_bytes": 1488, + "wire_bytes": 1524, + "reported_used_tokens": 1488, + "working_set_bytes": 288407552, + "peak_working_set_bytes": 289304576 + }, + { + "query": "the whole CI job just froze forever with no failure output after my latest test PR", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 896.4879, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288411648, + "peak_working_set_bytes": 289325056 + }, + { + "query": "running the test suite left junk state in my home directory", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 953.5631, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288411648, + "peak_working_set_bytes": 289325056 + }, + { + "query": "I deleted a bunch of old rows but the file on disk is still the same size", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 887.1536, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288411648, + "peak_working_set_bytes": 289325056 + }, + { + "query": "adding one new crate quietly changed how the whole workspace builds", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-lockfile-drift", + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YFMXS61RMTQ16GE6735", + "id": "01M1Y07TRS735JBK6YTB2ACC3M", + "kind": "memory", + "score": 0.9941080808639526, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1Y05ZBTM7S2Y9XXR5EA4C7E", + "id": "01M1Y07TRS3MFR8CKYA0G4STZ9", + "kind": "memory", + "score": 0.9717232584953308, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this — it errors on any lockfile diff." + }, + { + "expansion_handle": "memory:01M1Y05ZDSAHJXT2TMGQG83BWE", + "id": "01M1Y07TRS60C63EZAYVHQZAFJ", + "kind": "memory", + "score": 0.9183088541030884, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 962.7750000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2374, + "mcp_result_bytes": 2495, + "wire_bytes": 2531, + "reported_used_tokens": 2495, + "working_set_bytes": 288415744, + "peak_working_set_bytes": 289333248 + }, + { + "query": "we cannot pull an async runtime into the agent just to talk to AWS", + "ranked": [ + "tokio-blocking-in-async", + "tokio-runtime-in-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060WH1VTPAKJ89CENJJXP", + "id": "01M1Y07VSS41JJE4X0Q7M7YX86", + "kind": "memory", + "score": 0.7520647644996643, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking — never call rusqlite directly from an async fn without spawn_blocking." + }, + { + "expansion_handle": "memory:01M1Y060XP0KSRSNNYT7NE7D4W", + "id": "01M1Y07VSSFJ88S7FJQ7TNXD64", + "kind": "memory", + "score": 0.7233642935752869, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1154.2941, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1369, + "mcp_result_bytes": 1476, + "wire_bytes": 1512, + "reported_used_tokens": 1476, + "working_set_bytes": 288800768, + "peak_working_set_bytes": 289705984 + }, + { + "query": "users should be able to tell which build variant they installed from the version output", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 984.8974000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288821248, + "peak_working_set_bytes": 289783808 + }, + { + "query": "what gotchas should I expect writing process-inspection code that works on both Windows and Unix?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 884.4479, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288948224, + "peak_working_set_bytes": 289865728 + }, + { + "query": "why might tests behave differently on my machine than in the full CI run?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 858.9310999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 289468416, + "peak_working_set_bytes": 290381824 + }, + { + "query": "what do I need to know before wiring kimetsu into a brand new host agent?", + "ranked": [ + "bridge-target-enum-seams", + "kimetsu-daemon-lifecycle", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YJSTY39A6KQMMSK2DH6", + "id": "01M1Y07ZGGGQE4GQF1EMXJ7MC6", + "kind": "memory", + "score": 0.9741999506950378, + "summary": "project:fact - [2026-09-07] [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + }, + { + "expansion_handle": "memory:01M1Y0629C2P66GZ8JJCVPWPR9", + "id": "01M1Y07ZGH5R0H5REWM9XDF6KZ", + "kind": "memory", + "score": 0.9637662768363952, + "summary": "project:fact - [2026-09-07] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1Y05YDQZCEVNBN7TDX4XC9B", + "id": "01M1Y07ZGHNWTWAP6GSPEYVGHJ", + "kind": "memory", + "score": 0.4149944484233856, + "summary": "project:fact - [2026-09-07] [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 0.6666666666666666, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 947.5276, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2390, + "mcp_result_bytes": 2555, + "wire_bytes": 2591, + "reported_used_tokens": 2555, + "working_set_bytes": 289480704, + "peak_working_set_bytes": 290394112 + }, + { + "query": "tell me everything relevant to running kimetsu against AWS", + "ranked": [ + "kimetsu-mrr-metric", + "aws-credentials-chain", + "cargo-feature-unification-embeddings", + "kimetsu-eval-fixture-shape" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y062N6MYSHTZCNN349MES6", + "id": "01M1Y080EEVSS4P1RF9ZKPF3BP", + "kind": "memory", + "score": 0.984548270702362, + "summary": "project:fact - [tags: kimetsu bench mrr recall metrics evaluation] kimetsu bench reports MRR (Mean Reciprocal Rank) and Recall@K. MRR is 1/rank_of_first_relevant_result, averaged across cases; it penalizes models that rank the correct answer 2nd or 3rd. Recall@K is the fraction of cases where at least one relevant answer appears in the top K." + }, + { + "expansion_handle": "memory:01M1Y061WWY2XRNJE51QHGVRAM", + "id": "01M1Y080EEPRQJXP40EXN0EJ8T", + "kind": "memory", + "score": 0.9737622141838074, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + }, + { + "expansion_handle": "memory:01M1Y05YFMXS61RMTQ16GE6735", + "id": "01M1Y080EEYAW9F0DWYKZ4YS2F", + "kind": "memory", + "score": 0.9726329445838928, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1Y062M3CFFQGT02EBZTJSHN", + "id": "01M1Y080EE788CPPJKFSFNFE34", + "kind": "memory", + "score": 0.9641559720039368, + "summary": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` — a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases)." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 939.0904, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2883, + "mcp_result_bytes": 3066, + "wire_bytes": 3102, + "reported_used_tokens": 3066, + "working_set_bytes": 289484800, + "peak_working_set_bytes": 290394112 + }, + { + "query": "ingesting a cloned repo when the brain lives under a different root", + "ranked": [ + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YBPJ3QEY9DGV3Y39QED", + "id": "01M1Y081C9KTDT4B1S1JYQK43P", + "kind": "memory", + "score": 0.9995300769805908, + "summary": "project:fact - [2026-09-07] [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 901.9446, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1274, + "mcp_result_bytes": 1355, + "wire_bytes": 1391, + "reported_used_tokens": 1355, + "working_set_bytes": 289488896, + "peak_working_set_bytes": 290394112 + }, + { + "query": "streamable-http transport entry for openclaw.json with a bearer token", + "ranked": [ + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YDQZCEVNBN7TDX4XC9B", + "id": "01M1Y0827GM7AFR8JA29KT74ZV", + "kind": "memory", + "score": 0.9921918511390686, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 884.4945, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 996, + "mcp_result_bytes": 1125, + "wire_bytes": 1161, + "reported_used_tokens": 1125, + "working_set_bytes": 289492992, + "peak_working_set_bytes": 290406400 + }, + { + "query": "serializing ingests with a tokio mutex to avoid checkout races", + "ranked": [ + "remote-ingest-split-roots", + "testing-serial-vs-parallel", + "tokio-select-cancellation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YBPJ3QEY9DGV3Y39QED", + "id": "01M1Y08338A0E6JJQYEKXTPJW7", + "kind": "memory", + "score": 0.9795480966567992, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1Y061HRKB2V85Z6MW33WPRE", + "id": "01M1Y0833841DNA459PMDZBRD1", + "kind": "memory", + "score": 0.9425267577171326, + "summary": "project:fact - [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`)." + }, + { + "expansion_handle": "memory:01M1Y060Z0MRX8BDRSVJPYBRVF", + "id": "01M1Y08338NRW3HD78YDXDQYR7", + "kind": "memory", + "score": 0.5619664192199707, + "summary": "project:fact - [tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1075.3500000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2376, + "mcp_result_bytes": 2493, + "wire_bytes": 2529, + "reported_used_tokens": 2493, + "working_set_bytes": 289509376, + "peak_working_set_bytes": 290426880 + }, + { + "query": "percent-encoding the colon in the bedrock model id for the invoke URL", + "ranked": [ + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YH1NQKFMZ0R78E7Z8SP", + "id": "01M1Y08457YW08E4MMRGZ634T6", + "kind": "memory", + "score": 0.8341025710105896, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1006.7360000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1204, + "mcp_result_bytes": 1293, + "wire_bytes": 1329, + "reported_used_tokens": 1293, + "working_set_bytes": 289611776, + "peak_working_set_bytes": 290521088 + }, + { + "query": "deduplicating re-imported memories against pre-existing ids", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YX5TE9MYEX6EDV1N9YT", + "id": "01M1Y0854HPQ2MEFK96BPFAKGV", + "kind": "memory", + "score": 0.9991393089294434, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount — both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1074.5648999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 966, + "mcp_result_bytes": 1047, + "wire_bytes": 1083, + "reported_used_tokens": 1047, + "working_set_bytes": 289619968, + "peak_working_set_bytes": 290521088 + }, + { + "query": "parsing DMTF datetimes", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YZ40GGK5Q4S0G3YMFPW", + "id": "01M1Y08668EKASJ8NFB0KGXMF6", + "kind": "memory", + "score": 0.9934942126274108, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 784.3335999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 924, + "mcp_result_bytes": 1013, + "wire_bytes": 1049, + "reported_used_tokens": 1013, + "working_set_bytes": 289619968, + "peak_working_set_bytes": 290521088 + }, + { + "query": "how should install derive a stable identifier from the git remote URL?", + "ranked": [ + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YDQZCEVNBN7TDX4XC9B", + "id": "01M1Y086YNPZHDY7NV9PRCWNH6", + "kind": "memory", + "score": 0.98285174369812, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1010.0893, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 995, + "mcp_result_bytes": 1124, + "wire_bytes": 1160, + "reported_used_tokens": 1124, + "working_set_bytes": 289677312, + "peak_working_set_bytes": 290586624 + }, + { + "query": "the secret token must not end up written into the host config file", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1077.2346, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 289751040, + "peak_working_set_bytes": 290664448 + }, + { + "query": "keep the cleanup logic unit-testable without touching environment variables", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1013.1093, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 289751040, + "peak_working_set_bytes": 290664448 + }, + { + "query": "how do we stop the server from cloning arbitrary repos clients request?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1014.0511999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 289751040, + "peak_working_set_bytes": 290664448 + }, + { + "query": "make sure a wrong guess about a host plugin API never breaks that host", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YKXXK0W4ER0PGC80N1C", + "id": "01M1Y08AZTY0301EX6EV2HRNQF", + "kind": "memory", + "score": 0.928434193134308, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 876.0531000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 803, + "mcp_result_bytes": 892, + "wire_bytes": 928, + "reported_used_tokens": 892, + "working_set_bytes": 289751040, + "peak_working_set_bytes": 290668544 + }, + { + "query": "which wire-format trick lets us reuse the existing Anthropic request builder for AWS?", + "ranked": [ + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YH1NQKFMZ0R78E7Z8SP", + "id": "01M1Y08BTQ9RFV6DEN40MX8ZK7", + "kind": "memory", + "score": 0.9748817682266236, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1083.1806, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1203, + "mcp_result_bytes": 1292, + "wire_bytes": 1328, + "reported_used_tokens": 1292, + "working_set_bytes": 289800192, + "peak_working_set_bytes": 290713600 + }, + { + "query": "the self-update froze because something was still holding the executable", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1056.811, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 290054144, + "peak_working_set_bytes": 290959360 + }, + { + "query": "our notes about the extension API turned out wrong once we read the actual repo", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1032.815, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 290238464, + "peak_working_set_bytes": 291143680 + }, + { + "query": "half the benchmark trials die right after the first one finishes", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 963.8573, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 290283520, + "peak_working_set_bytes": 291196928 + }, + { + "query": "I need this parser visible to tests on every OS even though only one OS calls it", + "ranked": [ + "cfg-cross-platform-dead-code" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05Z1FDES73B8EQHWJPVMA", + "id": "01M1Y08FVZ3MBVZE80AB5N23WJ", + "kind": "memory", + "score": 0.36490198969841, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 999.7329000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 939, + "reported_used_tokens": 903, + "working_set_bytes": 290283520, + "peak_working_set_bytes": 291196928 + }, + { + "query": "the config file content refuses to parse even though the TOML looks valid", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YY75RMS67WX826FQJEE", + "id": "01M1Y08GVC647P3Z07WXBSG942", + "kind": "memory", + "score": 0.6614054441452026, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1041.4704, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 733, + "mcp_result_bytes": 814, + "wire_bytes": 850, + "reported_used_tokens": 814, + "working_set_bytes": 290291712, + "peak_working_set_bytes": 291205120 + }, + { + "query": "the remote server must refresh its checkout before answering file queries", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1051.6085, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 290291712, + "peak_working_set_bytes": 291205120 + }, + { + "query": "tests must not climb to a parent git repository when resolving project paths", + "ranked": [ + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YS4YPH8D67NSXJGT1SS", + "id": "01M1Y08JX5NPC7BD2PSPPHSQ3H", + "kind": "memory", + "score": 0.9839988350868224, + "summary": "project:fact - [2026-09-07] [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1103.9856, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 794, + "mcp_result_bytes": 875, + "wire_bytes": 911, + "reported_used_tokens": 875, + "working_set_bytes": 290295808, + "peak_working_set_bytes": 291205120 + }, + { + "query": "how do I test request signing deterministically when timestamps change every run?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1053.6784, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 290295808, + "peak_working_set_bytes": 291205120 + }, + { + "query": "adding a new variant to the host target enum - which places will I forget to update?", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05YJSTY39A6KQMMSK2DH6", + "id": "01M1Y08N086PENEH3RP57EQ5C0", + "kind": "memory", + "score": 0.885076105594635, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1087.0495, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1058, + "mcp_result_bytes": 1139, + "wire_bytes": 1175, + "reported_used_tokens": 1139, + "working_set_bytes": 290295808, + "peak_working_set_bytes": 291205120 + }, + { + "query": "how do I enable GPU acceleration for kimetsu embedding inference", + "ranked": [ + "mcp-tool-timeouts", + "kimetsu-proactive-hooks" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061QHG7EX7KYBKD86CQYC", + "id": "01M1Y08P277TCVEHF2QPCFSC72", + "kind": "memory", + "score": 0.9826309084892272, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking — in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize — keep it in a process-global `OnceLock`)." + }, + { + "expansion_handle": "memory:01M1Y062DDSWX11Z89QR995TW9", + "id": "01M1Y08P27A6DPYD3R3WPR7Q5P", + "kind": "memory", + "score": 0.8807981610298157, + "summary": "project:fact - [tags: kimetsu proactive hooks context injection] kimetsu's proactive context injection runs before each agent turn (pre-turn hook) and injects relevant memories into the system prompt prefix. The hook invocation adds latency to the first token: embedding inference + vector search + reranking + context formatting. On a cold start, this can be 1-3 seconds." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 1014.3146999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1377, + "mcp_result_bytes": 1476, + "wire_bytes": 1512, + "reported_used_tokens": 1476, + "working_set_bytes": 290295808, + "peak_working_set_bytes": 291205120 + }, + { + "query": "how do I throttle kimetsu API spend per month", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 980.9875999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 290295808, + "peak_working_set_bytes": 291209216 + }, + { + "query": "can the kimetsu brain database be stored in S3 instead of on disk", + "ranked": [ + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0620B2W192A9Y86X0ZB3D", + "id": "01M1Y08R0K325VHG3HDS2ZFDB9", + "kind": "memory", + "score": 0.38596054911613464, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time — clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 979.9511, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 875, + "mcp_result_bytes": 956, + "wire_bytes": 992, + "reported_used_tokens": 956, + "working_set_bytes": 290304000, + "peak_working_set_bytes": 291217408 + }, + { + "query": "how do I plug a custom tokenizer into the FTS index", + "ranked": [ + "sqlite-fts5-tokenizer" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05Z67QE5WZ6CHYBDBREP7", + "id": "01M1Y08RZWN7612PK15AF82RC5", + "kind": "memory", + "score": 0.9691632390022278, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 1048.0276, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 671, + "mcp_result_bytes": 756, + "wire_bytes": 792, + "reported_used_tokens": 756, + "working_set_bytes": 290459648, + "peak_working_set_bytes": 291360768 + }, + { + "query": "what should I check when kimetsu behaves differently on Windows than on Linux?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 997.121, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 290463744, + "peak_working_set_bytes": 291373056 + }, + { + "query": "what are the moving parts of the kimetsu remote deployment story?", + "ranked": [ + "kimetsu-write-tools-gate", + "ci-secrets-masking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y062EEFBC57YJ58J3CSXFD", + "id": "01M1Y08TZ7D3DHBHEQHMQNSACP", + "kind": "memory", + "score": 0.9729357361793518, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level — disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1Y0626EATKH9B42B4W92CC3", + "id": "01M1Y08TZ7FWFCET7JR8TANG9E", + "kind": "memory", + "score": 0.8412115573883057, + "summary": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output — but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1023.9429, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1410, + "mcp_result_bytes": 1509, + "wire_bytes": 1546, + "reported_used_tokens": 1509, + "working_set_bytes": 290586624, + "peak_working_set_bytes": 291500032 + }, + { + "query": "which lessons cover guarding behavior behind environment variables?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 922.3257, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 290709504, + "peak_working_set_bytes": 291610624 + }, + { + "query": "SQLite BUSY error under concurrent writes", + "ranked": [ + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05Z2BBHSJ0CDKCQ4XH9GE", + "id": "01M1Y08WWG1H1WPZ9GZYRTRHGQ", + "kind": "memory", + "score": 0.9978362917900084, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 891.023, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 898, + "mcp_result_bytes": 979, + "wire_bytes": 1016, + "reported_used_tokens": 979, + "working_set_bytes": 290721792, + "peak_working_set_bytes": 291614720 + }, + { + "query": "SQLite WAL mode breaks when the database is on a network share", + "ranked": [ + "sqlite-wal-network-drive", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05Z57SYAWW7723HQ5J9NG", + "id": "01M1Y08XQX3RMABSSFNTB5KYXW", + "kind": "memory", + "score": 0.999302864074707, + "summary": "project:fact - [2026-09-07] [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + }, + { + "expansion_handle": "memory:01M1Y05Z2BBHSJ0CDKCQ4XH9GE", + "id": "01M1Y08XQYZE0F8SZTXDB11KR4", + "kind": "memory", + "score": 0.9966553449630736, + "summary": "project:fact - [2026-09-07] [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1094.103, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1448, + "mcp_result_bytes": 1547, + "wire_bytes": 1584, + "reported_used_tokens": 1547, + "working_set_bytes": 290721792, + "peak_working_set_bytes": 291631104 + }, + { + "query": "my SQLite WAL database causes SQLITE_IOERR_LOCK on a mapped drive", + "ranked": [ + "sqlite-wal-network-drive" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05Z57SYAWW7723HQ5J9NG", + "id": "01M1Y08YT53PRBMF932088AVD6", + "kind": "memory", + "score": 0.99892657995224, + "summary": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1082.5716, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 748, + "mcp_result_bytes": 829, + "wire_bytes": 866, + "reported_used_tokens": 829, + "working_set_bytes": 290762752, + "peak_working_set_bytes": 291667968 + }, + { + "query": "FTS5 tokenizer configuration for Rust identifiers with underscores", + "ranked": [ + "sqlite-fts5-tokenizer", + "kimetsu-query-stemming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05Z67QE5WZ6CHYBDBREP7", + "id": "01M1Y08ZVYTSX4SCG90C2D8F0D", + "kind": "memory", + "score": 0.998104453086853, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + }, + { + "expansion_handle": "memory:01M1Y062H2NN1S48YS74CA719R", + "id": "01M1Y08ZVY22TXT6EC5RSFDW4H", + "kind": "memory", + "score": 0.7023860812187195, + "summary": "project:fact - [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1007.5083000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1212, + "mcp_result_bytes": 1331, + "wire_bytes": 1368, + "reported_used_tokens": 1331, + "working_set_bytes": 290762752, + "peak_working_set_bytes": 291667968 + }, + { + "query": "I switched the FTS5 tokenizer but search stopped returning results", + "ranked": [ + "sqlite-fts5-tokenizer" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05Z67QE5WZ6CHYBDBREP7", + "id": "01M1Y090VN0WAQ9XJM7478SG96", + "kind": "memory", + "score": 0.8194089531898499, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1074.3998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 670, + "mcp_result_bytes": 755, + "wire_bytes": 792, + "reported_used_tokens": 755, + "working_set_bytes": 290762752, + "peak_working_set_bytes": 291667968 + }, + { + "query": "optimal SQLite page size for storing embedding vectors", + "ranked": [ + "sqlite-page-size", + "onnx-dim-mismatch", + "onnx-cosine-vs-dot" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05Z77JXSSSNGC8GCACN6S", + "id": "01M1Y091X6Q2MM7Z5ABSGRCH4X", + "kind": "memory", + "score": 0.9990121126174928, + "summary": "project:fact - [tags: sqlite page_size performance rusqlite] SQLite's default page_size is 4096 bytes. For a write-heavy brain database with large BLOB payloads (embedding vectors), raising page_size to 16384 reduces fragmentation and improves sequential scan throughput. `PRAGMA page_size = 16384;` must be set BEFORE the first table is created — changing it on an existing database requires a VACUUM afterward to rebuild all pages." + }, + { + "expansion_handle": "memory:01M1Y060H9Y1TBKN4BXG507K53", + "id": "01M1Y091X69QXS91Q608X0NCZF", + "kind": "memory", + "score": 0.9881643056869508, + "summary": "project:fact - [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results — the ANN index shape mismatch isn't always caught at runtime." + }, + { + "expansion_handle": "memory:01M1Y060GAGCBYF97WMPA8SENM", + "id": "01M1Y091X6ZV8P1JHSW4ZQGQZK", + "kind": "memory", + "score": 0.9425415992736816, + "summary": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing — double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 934.8326, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1860, + "mcp_result_bytes": 1977, + "wire_bytes": 2014, + "reported_used_tokens": 1977, + "working_set_bytes": 290762752, + "peak_working_set_bytes": 291667968 + }, + { + "query": "ON DELETE CASCADE in SQLite does nothing — foreign keys not enforced", + "ranked": [ + "sqlite-foreign-keys-default-off" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05Z843AFP72Y2BY2XZ4Q0", + "id": "01M1Y092TDXBHHB4EMF2Y6XNFX", + "kind": "memory", + "score": 0.9996858835220336, + "summary": "project:fact - [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting — every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1078.8933, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 736, + "mcp_result_bytes": 817, + "wire_bytes": 854, + "reported_used_tokens": 817, + "working_set_bytes": 291123200, + "peak_working_set_bytes": 292028416 + }, + { + "query": "indexing a JSON metadata column in SQLite without a schema migration", + "ranked": [ + "sqlite-json1-extract", + "testing-fixture-drift", + "onnx-dim-mismatch", + "sqlite-partial-index" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05Z8Z4DP2CTNJTKSS5QXA", + "id": "01M1Y093W1TYWEFQQBNJ8ZX5Z6", + "kind": "memory", + "score": 0.9955366849899292, + "summary": "project:fact - [tags: sqlite json1 json_extract rusqlite] SQLite's json1 extension (built in since 3.38.0) lets you index and query JSONB columns with `json_extract(col, '$.field')`. To create a partial index over a JSON field: `CREATE INDEX idx ON memories (json_extract(metadata, '$.scope')) WHERE json_extract(metadata, '$.scope') IS NOT NULL;`. Use `json_each` for array fields." + }, + { + "expansion_handle": "memory:01M1Y061NJMXF06HD6WSDEN8JZ", + "id": "01M1Y093W1MFMNWDWKCATKDPV2", + "kind": "memory", + "score": 0.8227390646934509, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + }, + { + "expansion_handle": "memory:01M1Y060H9Y1TBKN4BXG507K53", + "id": "01M1Y093W1TKS6PXA80EYBPYBM", + "kind": "memory", + "score": 0.38374292850494385, + "summary": "project:fact - [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results — the ANN index shape mismatch isn't always caught at runtime." + }, + { + "expansion_handle": "memory:01M1Y05ZAXZGD4RX1ABAGTSF37", + "id": "01M1Y093W10HWSQ33JFYSG8R4P", + "kind": "memory", + "score": 0.3276048004627228, + "summary": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query — the planner uses the partial index only when the WHERE clause matches." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 993.4228, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2381, + "mcp_result_bytes": 2516, + "wire_bytes": 2553, + "reported_used_tokens": 2516, + "working_set_bytes": 291127296, + "peak_working_set_bytes": 292032512 + }, + { + "query": "prepare() vs prepare_cached() in rusqlite hot insert loop", + "ranked": [ + "sqlite-prepared-stmt-cache" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05ZA1Y2MV8GX3Z3ZBWBBZ", + "id": "01M1Y094VJZCD7FJF5FQFDTCMN", + "kind": "memory", + "score": 0.9993672966957092, + "summary": "project:fact - [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1004.7586, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 689, + "mcp_result_bytes": 770, + "wire_bytes": 807, + "reported_used_tokens": 770, + "working_set_bytes": 291135488, + "peak_working_set_bytes": 292036608 + }, + { + "query": "speed up bulk memory ingest by caching SQL statements", + "ranked": [ + "sqlite-prepared-stmt-cache" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05ZA1Y2MV8GX3Z3ZBWBBZ", + "id": "01M1Y095V8FBAE4G3DJXDBYYR8", + "kind": "memory", + "score": 0.9823396801948548, + "summary": "project:fact - [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1039.2075, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 688, + "mcp_result_bytes": 769, + "wire_bytes": 806, + "reported_used_tokens": 769, + "working_set_bytes": 291135488, + "peak_working_set_bytes": 292036608 + }, + { + "query": "partial index on deleted_at IS NULL for faster active memory queries", + "ranked": [ + "sqlite-partial-index" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05ZAXZGD4RX1ABAGTSF37", + "id": "01M1Y096V0ZFE27ZQJQHS1ZTJH", + "kind": "memory", + "score": 0.9988954067230223, + "summary": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query — the planner uses the partial index only when the WHERE clause matches." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1021.5577000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 794, + "mcp_result_bytes": 875, + "wire_bytes": 912, + "reported_used_tokens": 875, + "working_set_bytes": 291135488, + "peak_working_set_bytes": 292036608 + }, + { + "query": "the brain query is slow because it scans all rows including soft-deleted ones", + "ranked": [ + "sqlite-partial-index" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05ZAXZGD4RX1ABAGTSF37", + "id": "01M1Y097V43F8D5F3633P6033F", + "kind": "memory", + "score": 0.5760471224784851, + "summary": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query — the planner uses the partial index only when the WHERE clause matches." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1069.7898, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 793, + "mcp_result_bytes": 874, + "wire_bytes": 911, + "reported_used_tokens": 874, + "working_set_bytes": 291139584, + "peak_working_set_bytes": 292052992 + }, + { + "query": "Cargo.lock changed unexpectedly after adding a new workspace crate", + "ranked": [ + "cargo-lockfile-drift", + "cargo-feature-unification-embeddings", + "cargo-target-dir-sharing", + "cargo-patch-section" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05ZBTM7S2Y9XXR5EA4C7E", + "id": "01M1Y098WCH2W6SNT7X4QAZTDT", + "kind": "memory", + "score": 0.9991374015808104, + "summary": "project:fact - [2026-09-07] [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this — it errors on any lockfile diff." + }, + { + "expansion_handle": "memory:01M1Y05YFMXS61RMTQ16GE6735", + "id": "01M1Y098WC1QEAQ62S4FNFCYA2", + "kind": "memory", + "score": 0.9968542456626892, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1Y05ZETTM7QKBSZ1FY92RQ8", + "id": "01M1Y098WCBMH7R8K2ZNPFD4S1", + "kind": "memory", + "score": 0.9829630851745604, + "summary": "project:fact - [2026-09-07] [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps — use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + }, + { + "expansion_handle": "memory:01M1Y05ZHK2ZA3FKKK7GBBNKFB", + "id": "01M1Y098WD4PAWPZGDF0YTXZZ9", + "kind": "memory", + "score": 0.9262890815734864, + "summary": "project:fact - [2026-09-07] [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace — including transitive deps — that depend on `my-crate`. Remove the patch before publishing." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 890.4828, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3010, + "mcp_result_bytes": 3153, + "wire_bytes": 3190, + "reported_used_tokens": 3153, + "working_set_bytes": 291143680, + "peak_working_set_bytes": 292057088 + }, + { + "query": "how do I prevent CI from accepting a modified lockfile silently?", + "ranked": [ + "cargo-lockfile-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05ZBTM7S2Y9XXR5EA4C7E", + "id": "01M1Y099R9WKSWFZHDEFB5BHJW", + "kind": "memory", + "score": 0.9125379323959352, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this — it errors on any lockfile diff." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1013.7099000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 765, + "mcp_result_bytes": 846, + "wire_bytes": 883, + "reported_used_tokens": 846, + "working_set_bytes": 291143680, + "peak_working_set_bytes": 292057088 + }, + { + "query": "build.rs reruns on every incremental build even when nothing changed", + "ranked": [ + "cargo-build-script-rerun" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05ZCQF2N02VX6MVDA3NBK", + "id": "01M1Y09AR1GWKY4AH04GKYP7AC", + "kind": "memory", + "score": 0.9996689558029176, + "summary": "project:fact - [2026-09-07] [tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1039.4281, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 698, + "mcp_result_bytes": 779, + "wire_bytes": 816, + "reported_used_tokens": 779, + "working_set_bytes": 291278848, + "peak_working_set_bytes": 292192256 + }, + { + "query": "incremental cargo build is slow because build script runs every time", + "ranked": [ + "cargo-build-script-rerun" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05ZCQF2N02VX6MVDA3NBK", + "id": "01M1Y09BRHCNC2TBHW2653MKVG", + "kind": "memory", + "score": 0.9978280663490297, + "summary": "project:fact - [tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1026.6360000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 685, + "mcp_result_bytes": 766, + "wire_bytes": 803, + "reported_used_tokens": 766, + "working_set_bytes": 291278848, + "peak_working_set_bytes": 292192256 + }, + { + "query": "a dev-dependency is activating an embeddings feature in my production build", + "ranked": [ + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05ZDSAHJXT2TMGQG83BWE", + "id": "01M1Y09CSK9W8ASS7VH94Q0R8Z", + "kind": "memory", + "score": 0.9944193959236144, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1070.9234000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 931, + "mcp_result_bytes": 1012, + "wire_bytes": 1049, + "reported_used_tokens": 1012, + "working_set_bytes": 291282944, + "peak_working_set_bytes": 292192256 + }, + { + "query": "how do I prevent a test-only feature from bleeding into the non-test compilation?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1031.9533000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 291282944, + "peak_working_set_bytes": 292192256 + }, + { + "query": "linker errors in target/ caused by antivirus holding the exe file", + "ranked": [ + "windows-file-locking-av", + "cargo-target-dir-sharing" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0606RJBGNW8VJVHPWXP02", + "id": "01M1Y09ETH9ZH4Y8G0MEN6ZWG3", + "kind": "memory", + "score": 0.9997633099555968, + "summary": "project:fact - [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + }, + { + "expansion_handle": "memory:01M1Y05ZETTM7QKBSZ1FY92RQ8", + "id": "01M1Y09ETHKDASHW5C5W6M7A0W", + "kind": "memory", + "score": 0.7463976740837097, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps — use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 996.7163999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1523, + "mcp_result_bytes": 1622, + "wire_bytes": 1659, + "reported_used_tokens": 1622, + "working_set_bytes": 291282944, + "peak_working_set_bytes": 292192256 + }, + { + "query": "Access is denied (os error 5) when linking on Windows — how do I fix this?", + "ranked": [ + "windows-file-locking-av" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0606RJBGNW8VJVHPWXP02", + "id": "01M1Y09FSQAQ82K7Y7B0YBR7XV", + "kind": "memory", + "score": 0.9977193474769592, + "summary": "project:fact - [2026-09-07] [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 968.2474, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 769, + "mcp_result_bytes": 850, + "wire_bytes": 887, + "reported_used_tokens": 850, + "working_set_bytes": 291340288, + "peak_working_set_bytes": 292245504 + }, + { + "query": "incremental build broke with a type mismatch after switching branches", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05ZFT0B24FSK97XY8Y0WX", + "id": "01M1Y09GQW3WJYG9BF09Y4YGV0", + "kind": "memory", + "score": 0.7971777319908142, + "summary": "project:fact - [2026-09-07] [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1037.5047, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 890, + "mcp_result_bytes": 971, + "wire_bytes": 1008, + "reported_used_tokens": 971, + "working_set_bytes": 291422208, + "peak_working_set_bytes": 292331520 + }, + { + "query": "cargo reports a type error that references a type not in the codebase", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05ZFT0B24FSK97XY8Y0WX", + "id": "01M1Y09HRB8Y3X3FCBACQ9MXAV", + "kind": "memory", + "score": 0.7925198078155518, + "summary": "project:fact - [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 923.2426, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 877, + "mcp_result_bytes": 958, + "wire_bytes": 995, + "reported_used_tokens": 958, + "working_set_bytes": 291872768, + "peak_working_set_bytes": 292790272 + }, + { + "query": "compile fastembed at O2 in debug builds to avoid slow embedding inference", + "ranked": [ + "cargo-profile-override", + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05ZGMYJJMEZXJZA6WN6W3", + "id": "01M1Y09JNKGN7Q26V5T5GBR00Q", + "kind": "memory", + "score": 0.9932281374931335, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1Y061QHG7EX7KYBKD86CQYC", + "id": "01M1Y09JNKVBJE31YBZWRK4349", + "kind": "memory", + "score": 0.987656831741333, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking — in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize — keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1083.476, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1322, + "mcp_result_bytes": 1421, + "wire_bytes": 1458, + "reported_used_tokens": 1421, + "working_set_bytes": 291950592, + "peak_working_set_bytes": 292864000 + }, + { + "query": "override compilation profile for a single crate in a Cargo workspace", + "ranked": [ + "cargo-patch-section", + "cargo-profile-override", + "cargo-target-dir-sharing", + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05ZHK2ZA3FKKK7GBBNKFB", + "id": "01M1Y09KQ15F0VXR6VKHMZZESM", + "kind": "memory", + "score": 0.9984123706817628, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace — including transitive deps — that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1Y05ZGMYJJMEZXJZA6WN6W3", + "id": "01M1Y09KQ1WJAWVP1JQ3P44DT0", + "kind": "memory", + "score": 0.9979992508888244, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1Y05ZETTM7QKBSZ1FY92RQ8", + "id": "01M1Y09KQ1CEMYPTTT2ZWY218V", + "kind": "memory", + "score": 0.9956549406051636, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps — use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + }, + { + "expansion_handle": "memory:01M1Y05ZDSAHJXT2TMGQG83BWE", + "id": "01M1Y09KQ1FW0ZDVYJ5MTX2T83", + "kind": "memory", + "score": 0.9820712208747864, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 0.5, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1065.9303, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2682, + "mcp_result_bytes": 2821, + "wire_bytes": 2858, + "reported_used_tokens": 2821, + "working_set_bytes": 292048896, + "peak_working_set_bytes": 292966400 + }, + { + "query": "[patch.crates-io] workspace dependency override", + "ranked": [ + "cargo-patch-section", + "cargo-lockfile-drift", + "cargo-dev-dep-leak", + "cargo-target-dir-sharing" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05ZHK2ZA3FKKK7GBBNKFB", + "id": "01M1Y09MRDECG5PP5MRJ5K28FD", + "kind": "memory", + "score": 0.9999405145645142, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace — including transitive deps — that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1Y05ZBTM7S2Y9XXR5EA4C7E", + "id": "01M1Y09MRD49DW1ZJ15J54TCM0", + "kind": "memory", + "score": 0.9975811243057252, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this — it errors on any lockfile diff." + }, + { + "expansion_handle": "memory:01M1Y05ZDSAHJXT2TMGQG83BWE", + "id": "01M1Y09MRD3Z8TBRBE47VV4YKW", + "kind": "memory", + "score": 0.994149684906006, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + }, + { + "expansion_handle": "memory:01M1Y05ZETTM7QKBSZ1FY92RQ8", + "id": "01M1Y09MRDCNW3C7W004PG1XBR", + "kind": "memory", + "score": 0.7471600770950317, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps — use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 816.7112, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2755, + "mcp_result_bytes": 2894, + "wire_bytes": 2931, + "reported_used_tokens": 2894, + "working_set_bytes": 292122624, + "peak_working_set_bytes": 293027840 + }, + { + "query": "pin minimum supported Rust version in Cargo.toml", + "ranked": [ + "cargo-msrv" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05ZJEDXBR4GP8409Q7V6X", + "id": "01M1Y09NHYZW63N7WJW7THPTSV", + "kind": "memory", + "score": 0.999652862548828, + "summary": "project:fact - [tags: cargo rust msrv edition compatibility] Set `rust-version` in each `Cargo.toml` to declare the minimum supported Rust version (MSRV). Cargo enforces this with `--check`: `cargo check` fails if the toolchain is older than `rust-version`. Keep MSRV as old as your oldest supported deployment target." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 903.066, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 693, + "mcp_result_bytes": 774, + "wire_bytes": 811, + "reported_used_tokens": 774, + "working_set_bytes": 292155392, + "peak_working_set_bytes": 293072896 + }, + { + "query": "Windows path over 260 characters causes OS error 3 during Cargo build", + "ranked": [ + "windows-long-paths", + "windows-file-locking-av" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0605TX5R0NY7283T1SQG0", + "id": "01M1Y09PF96VDZ5XP5SF3VGVA9", + "kind": "memory", + "score": 0.9964189529418944, + "summary": "project:fact - [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe." + }, + { + "expansion_handle": "memory:01M1Y0606RJBGNW8VJVHPWXP02", + "id": "01M1Y09PF9B3T606ZFRQ77XBE2", + "kind": "memory", + "score": 0.9571694135665894, + "summary": "project:fact - [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 967.4171, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1297, + "mcp_result_bytes": 1406, + "wire_bytes": 1443, + "reported_used_tokens": 1406, + "working_set_bytes": 292167680, + "peak_working_set_bytes": 293072896 + }, + { + "query": "how do I enable long file paths for Cargo on Windows?", + "ranked": [ + "windows-long-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0605TX5R0NY7283T1SQG0", + "id": "01M1Y09QCC53CXHQ1701WTEDTB", + "kind": "memory", + "score": 0.9998334646224976, + "summary": "project:fact - [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1003.7889, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 769, + "mcp_result_bytes": 860, + "wire_bytes": 897, + "reported_used_tokens": 860, + "working_set_bytes": 292171776, + "peak_working_set_bytes": 293085184 + }, + { + "query": "intermittent sharing violation errors when Rust linker writes the exe on Windows", + "ranked": [ + "windows-file-locking-av", + "windows-long-paths", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0606RJBGNW8VJVHPWXP02", + "id": "01M1Y09RBR7N7N8GGP83VQWC9P", + "kind": "memory", + "score": 0.999750316143036, + "summary": "project:fact - [2026-09-07] [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + }, + { + "expansion_handle": "memory:01M1Y0605TX5R0NY7283T1SQG0", + "id": "01M1Y09RBRRG01Y5S0K5NG2D31", + "kind": "memory", + "score": 0.4757097661495209, + "summary": "project:fact - [2026-09-07] [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe." + }, + { + "expansion_handle": "memory:01M1Y05Z2BBHSJ0CDKCQ4XH9GE", + "id": "01M1Y09RBRAK4JTY3CKGKJWA5Z", + "kind": "memory", + "score": 0.38107830286026, + "summary": "project:fact - [2026-09-07] [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 965.9429, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2006, + "mcp_result_bytes": 2133, + "wire_bytes": 2170, + "reported_used_tokens": 2133, + "working_set_bytes": 292306944, + "peak_working_set_bytes": 293216256 + }, + { + "query": "Rust walkdir follows junctions differently from symlinks on Windows", + "ranked": [ + "windows-junctions-vs-symlinks" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0609MBTPXXW97D4DC5BMS", + "id": "01M1Y09SA0ZE28XX2NA26D28CG", + "kind": "memory", + "score": 0.9996020197868348, + "summary": "project:fact - [tags: windows junctions symlinks rust std::fs] On Windows, directory junctions (NTFS reparse points) behave like symlinks for directory traversal but `std::fs::symlink_metadata` returns `FileType::is_symlink() = false` for junctions (only true for regular symlinks). Use `std::fs::read_link` — it succeeds for both junction and symlink. `walkdir` crate's `follow_links` follows both, but its `is_symlink()` method correctly reports only actual symlinks." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 996.5014, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 845, + "mcp_result_bytes": 926, + "wire_bytes": 963, + "reported_used_tokens": 926, + "working_set_bytes": 292319232, + "peak_working_set_bytes": 293228544 + }, + { + "query": "UNC path canonicalize returns verbatim prefix — how do I strip it?", + "ranked": [ + "windows-unc-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0607KXAPYC7S6B62TQ091", + "id": "01M1Y09T98SVPJK2R9ZD9NWK75", + "kind": "memory", + "score": 0.9988629817962646, + "summary": "project:fact - [tags: windows unc-paths rust std::fs] Windows UNC paths (`\\\\server\\share\\...`) are not supported by most Rust `std::fs` operations unless passed through the extended-length prefix `\\\\?\\UNC\\server\\share\\...`. `std::path::Path::new(\"\\\\\\\\server\\\\share\")` works for basic operations but breaks with `canonicalize()` which returns the verbatim prefix form. When walking directory trees that may start on UNC paths, use the `dunce` crate to strip the verbatim prefix before comparing or displaying paths." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1030.791, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 908, + "mcp_result_bytes": 1025, + "wire_bytes": 1062, + "reported_used_tokens": 1025, + "working_set_bytes": 292384768, + "peak_working_set_bytes": 293306368 + }, + { + "query": "UTF-8 memory text prints as mojibake in the Windows console", + "ranked": [ + "windows-console-encoding" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0608M7Z5SRRAPS30W3W4A", + "id": "01M1Y09V9PEXX30A28FKJYMBQH", + "kind": "memory", + "score": 0.9996604919433594, + "summary": "project:fact - [tags: windows console encoding utf8 rust] Windows console code page defaults to the system ANSI code page (usually CP1252 or CP932), not UTF-8. Rust's `println!` writes UTF-8 bytes which display as mojibake in a non-UTF-8 console. Fix at process startup: call `SetConsoleOutputCP(65001)` via `winapi` or `windows-sys`, or set `PYTHONUTF8=1`/`RUST_LOG` before launch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 978.4155, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 757, + "mcp_result_bytes": 838, + "wire_bytes": 875, + "reported_used_tokens": 838, + "working_set_bytes": 292487168, + "peak_working_set_bytes": 293400576 + }, + { + "query": "process exit code is 4294967295 instead of -1 on Windows", + "ranked": [ + "windows-exit-codes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060AK3DAAFA6WRH4JX0V8", + "id": "01M1Y09W80JDFVT0GJN20BGM7V", + "kind": "memory", + "score": 0.9966622591018676, + "summary": "project:fact - [tags: windows exit-codes rust process child] On Windows, process exit codes are 32-bit unsigned integers (DWORD). Rust's `ExitStatus::code()` returns `Option` — it's `None` if the process was killed by a signal (which Windows doesn't use; instead, TerminateProcess with a code). Conventional codes: 0=success, 1=generic error, 0xC0000005=access violation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1001.3358000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 753, + "mcp_result_bytes": 834, + "wire_bytes": 871, + "reported_used_tokens": 834, + "working_set_bytes": 292499456, + "peak_working_set_bytes": 293408768 + }, + { + "query": "tokenizer.json must match the ONNX model — what breaks if it doesn't?", + "ranked": [ + "onnx-tokenizer-mismatch" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060CPZVTNWCD47V80WX4J", + "id": "01M1Y09X7D2Z4KEYY0QPJV84ZQ", + "kind": "memory", + "score": 0.9991299510002136, + "summary": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly — specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings — cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1085.6115, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 959, + "mcp_result_bytes": 1040, + "wire_bytes": 1077, + "reported_used_tokens": 1040, + "working_set_bytes": 292659200, + "peak_working_set_bytes": 293568512 + }, + { + "query": "embedding quality degraded after I swapped in the INT8 quantized model", + "ranked": [ + "onnx-quantization-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060DK4AZTG8FQ5SZ82QD8", + "id": "01M1Y09Y9BGTP4ZDMVZCYFFC6Y", + "kind": "memory", + "score": 0.997980535030365, + "summary": "project:fact - [2026-09-07] [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals — cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1040.4467, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 990, + "mcp_result_bytes": 1071, + "wire_bytes": 1108, + "reported_used_tokens": 1071, + "working_set_bytes": 292659200, + "peak_working_set_bytes": 293572608 + }, + { + "query": "missing attention mask causes low-norm embeddings in batch inference", + "ranked": [ + "onnx-batch-padding" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060EEY2C43PS90NFEG82N", + "id": "01M1Y09ZA7ZGB1DW6HFRKCV4PC", + "kind": "memory", + "score": 0.9998397827148438, + "summary": "project:fact - [tags: onnx batch padding attention-mask embeddings] When running batch inference with an ONNX model, all inputs in the batch must be padded to the same sequence length. The `attention_mask` tensor marks which tokens are real (1) and which are padding (0). Failing to pass `attention_mask` causes the model to average-pool over padding tokens, producing systematically lower-norm embeddings." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1067.819, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 781, + "mcp_result_bytes": 862, + "wire_bytes": 899, + "reported_used_tokens": 862, + "working_set_bytes": 292798464, + "peak_working_set_bytes": 293699584 + }, + { + "query": "ONNX model download fails in a Docker container with no home directory", + "ranked": [ + "onnx-model-cache-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060FEYS9F7FP0TTZ0J6WY", + "id": "01M1Y0A0BYQDX71CC4MNEZD35H", + "kind": "memory", + "score": 0.9887272119522096, + "summary": "project:fact - [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1077.6163000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 755, + "mcp_result_bytes": 838, + "wire_bytes": 875, + "reported_used_tokens": 838, + "working_set_bytes": 292798464, + "peak_working_set_bytes": 293703680 + }, + { + "query": "fastembed cache path environment variable for CI", + "ranked": [ + "onnx-model-cache-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060FEYS9F7FP0TTZ0J6WY", + "id": "01M1Y0A1D3DPHYKGSHR133H1YP", + "kind": "memory", + "score": 0.9995118379592896, + "summary": "project:fact - [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1065.5311000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 756, + "mcp_result_bytes": 839, + "wire_bytes": 876, + "reported_used_tokens": 839, + "working_set_bytes": 292802560, + "peak_working_set_bytes": 293703680 + }, + { + "query": "cosine similarity vs dot product for L2-normalized embedding vectors", + "ranked": [ + "onnx-cosine-vs-dot", + "onnx-tokenizer-mismatch", + "onnx-quantization-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060GAGCBYF97WMPA8SENM", + "id": "01M1Y0A2E9X9M7V6B2H6SC1TY6", + "kind": "memory", + "score": 0.9999407529830932, + "summary": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing — double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + }, + { + "expansion_handle": "memory:01M1Y060CPZVTNWCD47V80WX4J", + "id": "01M1Y0A2E9K9QE80Y5PYHA13PK", + "kind": "memory", + "score": 0.9514977931976318, + "summary": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly — specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings — cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo." + }, + { + "expansion_handle": "memory:01M1Y060DK4AZTG8FQ5SZ82QD8", + "id": "01M1Y0A2EAWX0PS1PRZ2KS20BD", + "kind": "memory", + "score": 0.941756010055542, + "summary": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals — cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 999.4296, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2245, + "mcp_result_bytes": 2362, + "wire_bytes": 2399, + "reported_used_tokens": 2362, + "working_set_bytes": 292802560, + "peak_working_set_bytes": 293711872 + }, + { + "query": "stored vectors have wrong dimension after switching embedding models", + "ranked": [ + "onnx-dim-mismatch", + "onnx-cosine-vs-dot", + "onnx-tokenizer-mismatch" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060H9Y1TBKN4BXG507K53", + "id": "01M1Y0A3DG7NNPGRSXB8WK27RX", + "kind": "memory", + "score": 0.9997621178627014, + "summary": "project:fact - [2026-09-07] [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results — the ANN index shape mismatch isn't always caught at runtime." + }, + { + "expansion_handle": "memory:01M1Y060GAGCBYF97WMPA8SENM", + "id": "01M1Y0A3DG04RHKDDZ4G7ZN07G", + "kind": "memory", + "score": 0.997715711593628, + "summary": "project:fact - [2026-09-07] [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing — double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + }, + { + "expansion_handle": "memory:01M1Y060CPZVTNWCD47V80WX4J", + "id": "01M1Y0A3DG0PJXP2Q22S4XXN9P", + "kind": "memory", + "score": 0.9388805031776428, + "summary": "project:fact - [2026-09-07] [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly — specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings — cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 878.0783, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2049, + "mcp_result_bytes": 2166, + "wire_bytes": 2203, + "reported_used_tokens": 2166, + "working_set_bytes": 292802560, + "peak_working_set_bytes": 293711872 + }, + { + "query": "E5 and Instructor models need a query prefix — what happens without it?", + "ranked": [ + "onnx-prefix-instructions", + "onnx-cosine-vs-dot" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060MFS2A9XC83N9JHNWTE", + "id": "01M1Y0A48YJF9Q6G03NWXYC8EV", + "kind": "memory", + "score": 0.996955633163452, + "summary": "project:fact - [tags: onnx embeddings prefix instruction e5 query passage] E5 and Instructor family models require a text prefix on BOTH query and passage sides to produce meaningful similarities: query prefix `\"query: \"`, passage prefix `\"passage: \"`. Omitting the prefix can drop MRR by 10-15 percentage points on out-of-domain datasets. Check the model's README for the exact prefix string — it varies by model family." + }, + { + "expansion_handle": "memory:01M1Y060GAGCBYF97WMPA8SENM", + "id": "01M1Y0A48YJ1KSKKFMEXHV4ND9", + "kind": "memory", + "score": 0.9543967247009276, + "summary": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing — double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1058.5129, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1339, + "mcp_result_bytes": 1446, + "wire_bytes": 1483, + "reported_used_tokens": 1446, + "working_set_bytes": 292802560, + "peak_working_set_bytes": 293711872 + }, + { + "query": "ORT thread pool contention when running multiple bench processes in parallel", + "ranked": [ + "onnx-ort-threading" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060NFDVT4KKYC4BDWH6D3", + "id": "01M1Y0A5A0W5E2V70M07JA2ZVC", + "kind": "memory", + "score": 0.9998078942298888, + "summary": "project:fact - [2026-09-07] [tags: onnx ort thread-pool parallelism cpu] ORT (ONNX Runtime) creates its own inter-op and intra-op thread pools. In a multi-process bench setup, each child inherits these pools and they compete for CPU cores. Set `SessionOptionsBuilder::with_intra_threads(1).with_inter_threads(1)` if you're running many parallel bench processes — this sacrifices per-inference throughput for lower contention." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1065.8263, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 802, + "mcp_result_bytes": 883, + "wire_bytes": 920, + "reported_used_tokens": 883, + "working_set_bytes": 292773888, + "peak_working_set_bytes": 293711872 + }, + { + "query": "git worktrees share the .kimetsu brain — how do I isolate test runs?", + "ranked": [ + "git-worktree-brain-isolation", + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060PEBE9HMM62FJ922NWN", + "id": "01M1Y0A6BD4P6JN23YN05E7W1V", + "kind": "memory", + "score": 0.9996256828308104, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root — if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + }, + { + "expansion_handle": "memory:01M1Y05YS4YPH8D67NSXJGT1SS", + "id": "01M1Y0A6BD8Q3JAPR4YEP5G8JV", + "kind": "memory", + "score": 0.9904396533966064, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1039.9929000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1435, + "mcp_result_bytes": 1534, + "wire_bytes": 1571, + "reported_used_tokens": 1534, + "working_set_bytes": 292904960, + "peak_working_set_bytes": 293818368 + }, + { + "query": "when is it safe to use --no-verify on git commit?", + "ranked": [ + "git-hooks-bypass", + "git-reflog-rescue" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060QDDEW4AGP54DM3BA1T", + "id": "01M1Y0A7C6P7WT8XBNBST2M4F3", + "kind": "memory", + "score": 0.9956986904144288, + "summary": "project:fact - [2026-09-07] [tags: git hooks bypass pre-commit skip] `git commit --no-verify` skips ALL hooks (pre-commit and commit-msg). Never use this in shared team repos where hooks enforce quality gates (lint, tests, memory harvest). Instead, fix the failing hook." + }, + { + "expansion_handle": "memory:01M1Y060VJEBGRY23E0DJBZS5T", + "id": "01M1Y0A7C6ZJ495NEVV0FEXCEG", + "kind": "memory", + "score": 0.5084817409515381, + "summary": "project:fact - [2026-09-07] [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone — they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only — remote reflog is not accessible via normal git commands." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1067.8847, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1193, + "mcp_result_bytes": 1292, + "wire_bytes": 1329, + "reported_used_tokens": 1292, + "working_set_bytes": 292917248, + "peak_working_set_bytes": 293826560 + }, + { + "query": "reduce clone size and bandwidth for server-side repo ingest", + "ranked": [ + "git-sparse-checkout", + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060RDNW3FF1XX9EC1TFG1", + "id": "01M1Y0A8DHZSFTY9PW09E8ADRP", + "kind": "memory", + "score": 0.9969936609268188, + "summary": "project:fact - [tags: git sparse-checkout partial-clone bandwidth] `git sparse-checkout init --cone` combined with `git clone --filter=blob:none` (partial clone) fetches only the commit graph and tree objects, not blobs. Individual blobs are fetched on demand when accessed. This cuts clone time for large repos from minutes to seconds." + }, + { + "expansion_handle": "memory:01M1Y05YBPJ3QEY9DGV3Y39QED", + "id": "01M1Y0A8DH3ZD8VDYRBN6MPYE5", + "kind": "memory", + "score": 0.8199672698974609, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1137.7412, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1744, + "mcp_result_bytes": 1843, + "wire_bytes": 1880, + "reported_used_tokens": 1843, + "working_set_bytes": 292921344, + "peak_working_set_bytes": 293826560 + }, + { + "query": "spurious diffs from Windows CRLF line ending conversion in git", + "ranked": [ + "git-line-endings-windows" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060SFWE8J4K7FA1PT0HMM", + "id": "01M1Y0A9HZA3AD4ZG2C18GYDAM", + "kind": "memory", + "score": 0.9993343949317932, + "summary": "project:fact - [tags: git line-endings windows crlf autocrlf] On Windows, `core.autocrlf=true` (git's default for Windows installs) converts LF to CRLF on checkout and CRLF to LF on commit. This causes spurious diffs when files are edited on Windows then committed — the content is identical but the line endings differ in the index vs the working tree. Fix: set `core.autocrlf=false` and `.gitattributes` with `* text=auto eol=lf` for the repo." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1076.6487, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 940, + "reported_used_tokens": 903, + "working_set_bytes": 292921344, + "peak_working_set_bytes": 293826560 + }, + { + "query": "git submodule always gets the wrong commit in CI", + "ranked": [ + "git-submodule-pinning", + "git-hooks-bypass" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060THWEBZHRGKPBJRS88X", + "id": "01M1Y0AAJM6Y7E67SXNJ22MPFY", + "kind": "memory", + "score": 0.9992856383323668, + "summary": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip — this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version." + }, + { + "expansion_handle": "memory:01M1Y060QDDEW4AGP54DM3BA1T", + "id": "01M1Y0AAJMTRQWBSJN5XA5D2J1", + "kind": "memory", + "score": 0.6295387744903564, + "summary": "project:fact - [tags: git hooks bypass pre-commit skip] `git commit --no-verify` skips ALL hooks (pre-commit and commit-msg). Never use this in shared team repos where hooks enforce quality gates (lint, tests, memory harvest). Instead, fix the failing hook." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1061.1885000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1157, + "mcp_result_bytes": 1256, + "wire_bytes": 1293, + "reported_used_tokens": 1256, + "working_set_bytes": 292921344, + "peak_working_set_bytes": 293830656 + }, + { + "query": "accidentally ran git reset --hard and lost commits — can I recover?", + "ranked": [ + "git-reflog-rescue" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060VJEBGRY23E0DJBZS5T", + "id": "01M1Y0ABKT8KD4212ZJNBTNXAH", + "kind": "memory", + "score": 0.9995450377464294, + "summary": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone — they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only — remote reflog is not accessible via normal git commands." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1071.7251999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 762, + "mcp_result_bytes": 843, + "wire_bytes": 880, + "reported_used_tokens": 843, + "working_set_bytes": 292921344, + "peak_working_set_bytes": 293830656 + }, + { + "query": "blocking SQLite call from an async tokio handler causes latency spikes", + "ranked": [ + "tokio-blocking-in-async" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060WH1VTPAKJ89CENJJXP", + "id": "01M1Y0ACN77M49RPZSXDTHK557", + "kind": "memory", + "score": 0.9996535778045654, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking — never call rusqlite directly from an async fn without spawn_blocking." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 957.0353, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 766, + "mcp_result_bytes": 847, + "wire_bytes": 884, + "reported_used_tokens": 847, + "working_set_bytes": 292921344, + "peak_working_set_bytes": 293830656 + }, + { + "query": "Cannot start a runtime from within a runtime in a tokio test", + "ranked": [ + "tokio-runtime-in-tests", + "tokio-blocking-in-async" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060XP0KSRSNNYT7NE7D4W", + "id": "01M1Y0ADK5H4DZXXHQCMFC2TNA", + "kind": "memory", + "score": 0.9997126460075378, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + }, + { + "expansion_handle": "memory:01M1Y060WH1VTPAKJ89CENJJXP", + "id": "01M1Y0ADK5RRYEGZ9ZE3QPPA10", + "kind": "memory", + "score": 0.5779464840888977, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking — never call rusqlite directly from an async fn without spawn_blocking." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 978.6442, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1370, + "mcp_result_bytes": 1477, + "wire_bytes": 1514, + "reported_used_tokens": 1477, + "working_set_bytes": 292929536, + "peak_working_set_bytes": 293838848 + }, + { + "query": "tokio select cancels the other branch and loses the value in the channel", + "ranked": [ + "tokio-select-cancellation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060Z0MRX8BDRSVJPYBRVF", + "id": "01M1Y0AEHTTMQ3P0KXSC52VWC8", + "kind": "memory", + "score": 0.9981033802032472, + "summary": "project:fact - [tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1017.7108999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 751, + "mcp_result_bytes": 832, + "wire_bytes": 869, + "reported_used_tokens": 832, + "working_set_bytes": 292925440, + "peak_working_set_bytes": 293838848 + }, + { + "query": "mpsc channel backpressure causing senders to stall", + "ranked": [ + "tokio-channel-backpressure" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y06106AJ5F1TJCW170EKZK", + "id": "01M1Y0AFHKHAFM1NZF3SGFXSQ9", + "kind": "memory", + "score": 0.9999104738235474, + "summary": "project:fact - [tags: tokio mpsc channel backpressure async rust] `tokio::sync::mpsc::channel(N)` with a bounded buffer provides backpressure: senders block when the buffer is full. This prevents unbounded memory growth but can cause sender tasks to stall. Choosing N: too small causes frequent backpressure (throughput drops); too large defeats the purpose." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1093.6848, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 733, + "mcp_result_bytes": 814, + "wire_bytes": 851, + "reported_used_tokens": 814, + "working_set_bytes": 292925440, + "peak_working_set_bytes": 293838848 + }, + { + "query": "overhead from calling spawn_blocking on every single query request", + "ranked": [ + "tokio-spawn-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0611CGW84S4J3YQ49DDQ2", + "id": "01M1Y0AGM0MJ3K5A986T864A8S", + "kind": "memory", + "score": 0.9961729645729064, + "summary": "project:fact - [tags: tokio spawn_blocking thread-pool rust blocking] `tokio::task::spawn_blocking` places work on a dedicated blocking thread pool (default up to 512 threads, configurable via `Builder::max_blocking_threads`). Each call creates or reuses a thread — there's no true pooling, threads may be created on demand. For many short-duration blocking calls (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1043.2462, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 746, + "mcp_result_bytes": 827, + "wire_bytes": 864, + "reported_used_tokens": 827, + "working_set_bytes": 292925440, + "peak_working_set_bytes": 293838848 + }, + { + "query": "axum server panics during shutdown because the DB pool is already closed", + "ranked": [ + "tokio-shutdown-ordering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0615DY78S91BYCR1PW38X", + "id": "01M1Y0AHMPF4F1YDP3BE5VMMKN", + "kind": "memory", + "score": 0.98052579164505, + "summary": "project:fact - [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries — the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1044.4894, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 931, + "mcp_result_bytes": 1012, + "wire_bytes": 1049, + "reported_used_tokens": 1012, + "working_set_bytes": 292925440, + "peak_working_set_bytes": 293838848 + }, + { + "query": "reqwest Client created per-request defeats connection pooling", + "ranked": [ + "http-connection-pooling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0616GTXM310F072T1Z5JJ", + "id": "01M1Y0AJP3DDPVHDG6S9ENHVJ4", + "kind": "memory", + "score": 0.9998082518577576, + "summary": "project:fact - [tags: http reqwest connection-pool keep-alive rust] reqwest's `Client` holds a connection pool; always create ONE `Client` instance and clone it for each handler — cloning is cheap (Arc under the hood). Creating a `Client::new()` per request defeats connection pooling and causes TCP connection exhaustion under load. The default pool settings: max_idle_per_host=usize::MAX (unbounded), idle_timeout=90s." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1046.7995999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 797, + "mcp_result_bytes": 878, + "wire_bytes": 915, + "reported_used_tokens": 878, + "working_set_bytes": 292925440, + "peak_working_set_bytes": 293838848 + }, + { + "query": "LLM request times out during streaming — which timeout setting applies?", + "ranked": [ + "http-timeout-layering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0617QSA2CVDN4ENGFG0RG", + "id": "01M1Y0AKPG35K4M2NB22JSJZK2", + "kind": "memory", + "score": 0.9987107515335084, + "summary": "project:fact - [tags: http reqwest timeout connect read total rust] reqwest has three distinct timeout knobs: `connect_timeout`, `read_timeout`, and `timeout` (total). They compose: if all three are set, the request fails at whichever fires first. For LLM API calls with streaming responses, `read_timeout` must be larger than the slowest expected token (often 30-60s) while `connect_timeout` can be tight (3-5s)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 912.2832, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 788, + "mcp_result_bytes": 869, + "wire_bytes": 906, + "reported_used_tokens": 869, + "working_set_bytes": 292925440, + "peak_working_set_bytes": 293838848 + }, + { + "query": "how do I safely retry a POST to the LLM API without creating duplicates?", + "ranked": [ + "http-retry-idempotency" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0618VR2BYK0SW8R8GC71W", + "id": "01M1Y0AMJKR92MK2WMQ5YWVT40", + "kind": "memory", + "score": 0.9995805621147156, + "summary": "project:fact - [tags: http retry idempotency post put reqwest] Only retry idempotent requests automatically. GET, HEAD, PUT, DELETE are idempotent. POST is NOT — retrying a POST may create duplicate resources." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1092.0312999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 585, + "mcp_result_bytes": 666, + "wire_bytes": 703, + "reported_used_tokens": 666, + "working_set_bytes": 292925440, + "peak_working_set_bytes": 293838848 + }, + { + "query": "custom enterprise root CA not trusted by rustls on Windows", + "ranked": [ + "http-tls-roots", + "http-proxy-env" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061A1B4C0T968J5PNRFSH", + "id": "01M1Y0ANMHX0X9Z39VSY2SH0VG", + "kind": "memory", + "score": 0.9998220801353456, + "summary": "project:fact - [tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle — the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle." + }, + { + "expansion_handle": "memory:01M1Y061CA2XBDZ74DT8HR7CSJ", + "id": "01M1Y0ANMHQE12AA2KBWQ3EB12", + "kind": "memory", + "score": 0.38715291023254395, + "summary": "project:fact - [tags: http proxy environment reqwest rust corporate] reqwest respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` environment variables by default (with `default-tls` or `rustls-tls`). In a corporate network, these may redirect traffic through an intercepting proxy that breaks mTLS or adds latency. To disable proxy usage entirely: `reqwest::ClientBuilder::no_proxy()`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1073.9996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1311, + "mcp_result_bytes": 1410, + "wire_bytes": 1447, + "reported_used_tokens": 1410, + "working_set_bytes": 292929536, + "peak_working_set_bytes": 293838848 + }, + { + "query": "parsing server-sent events when a single TCP chunk contains a partial SSE frame", + "ranked": [ + "http-streaming-bodies" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061B3Z0S7MH3X6Y15J8MA", + "id": "01M1Y0APPFXJVDNDBX19B1KX81", + "kind": "memory", + "score": 0.9667426943778992, + "summary": "project:fact - [2026-09-07] [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding — a chunk may split across frame boundaries." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 983.6257, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 859, + "mcp_result_bytes": 940, + "wire_bytes": 977, + "reported_used_tokens": 940, + "working_set_bytes": 292929536, + "peak_working_set_bytes": 293838848 + }, + { + "query": "reqwest does not use the system proxy settings on Windows", + "ranked": [ + "http-proxy-env", + "http-tls-roots", + "http-connection-pooling", + "http-streaming-bodies" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061CA2XBDZ74DT8HR7CSJ", + "id": "01M1Y0AQMWE5EQQFMNQS27AN0S", + "kind": "memory", + "score": 0.9997830986976624, + "summary": "project:fact - [tags: http proxy environment reqwest rust corporate] reqwest respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` environment variables by default (with `default-tls` or `rustls-tls`). In a corporate network, these may redirect traffic through an intercepting proxy that breaks mTLS or adds latency. To disable proxy usage entirely: `reqwest::ClientBuilder::no_proxy()`." + }, + { + "expansion_handle": "memory:01M1Y061A1B4C0T968J5PNRFSH", + "id": "01M1Y0AQMW7083FC0SFXDKEYJF", + "kind": "memory", + "score": 0.9808586239814758, + "summary": "project:fact - [tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle — the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle." + }, + { + "expansion_handle": "memory:01M1Y0616GTXM310F072T1Z5JJ", + "id": "01M1Y0AQMXVP0KD4QWTYPYTKZQ", + "kind": "memory", + "score": 0.719273030757904, + "summary": "project:fact - [tags: http reqwest connection-pool keep-alive rust] reqwest's `Client` holds a connection pool; always create ONE `Client` instance and clone it for each handler — cloning is cheap (Arc under the hood). Creating a `Client::new()` per request defeats connection pooling and causes TCP connection exhaustion under load. The default pool settings: max_idle_per_host=usize::MAX (unbounded), idle_timeout=90s." + }, + { + "expansion_handle": "memory:01M1Y061B3Z0S7MH3X6Y15J8MA", + "id": "01M1Y0AQMWZ8P9W8Z9KMXS7AFC", + "kind": "memory", + "score": 0.7009692192077637, + "summary": "project:fact - [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding — a chunk may split across frame boundaries." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1058.414, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2497, + "mcp_result_bytes": 2632, + "wire_bytes": 2669, + "reported_used_tokens": 2632, + "working_set_bytes": 292929536, + "peak_working_set_bytes": 293838848 + }, + { + "query": "insta snapshot tests fail in CI because output includes a timestamp", + "ranked": [ + "testing-snapshot-churn", + "ci-flaky-quarantine" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061DCJN2SYNAWF125PPW3", + "id": "01M1Y0ARP01PP3QXW4712G9RDW", + "kind": "memory", + "score": 0.999855637550354, + "summary": "project:fact - [tags: testing snapshot insta assert churn rust] Snapshot tests (e.g. with the `insta` crate) fail whenever the output changes, even for intended changes. In CI, they fail loudly; locally, `cargo insta review` walks you through accepting or rejecting changes." + }, + { + "expansion_handle": "memory:01M1Y06288TD3HYPJQNP7S1ZNB", + "id": "01M1Y0ARP0EQ42AQ2NECKBHANG", + "kind": "memory", + "score": 0.5997360348701477, + "summary": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal — a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 921.1368, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1196, + "mcp_result_bytes": 1295, + "wire_bytes": 1332, + "reported_used_tokens": 1295, + "working_set_bytes": 292937728, + "peak_working_set_bytes": 293847040 + }, + { + "query": "two test workers writing to the same temp directory path race each other", + "ranked": [ + "testing-temp-dirs-ci" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061EHA1QHS9A92RMVXS4T", + "id": "01M1Y0ASK1YNX5TK7F9G7DZ9ZD", + "kind": "memory", + "score": 0.9889234900474548, + "summary": "project:fact - [tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 962.231, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 755, + "mcp_result_bytes": 836, + "wire_bytes": 873, + "reported_used_tokens": 836, + "working_set_bytes": 292937728, + "peak_working_set_bytes": 293847040 + }, + { + "query": "test passes locally but fails on a slow CI runner due to a 100ms sleep", + "ranked": [ + "testing-time-dependent-flakes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061FPYM98XSA9JHTRM8PK", + "id": "01M1Y0ATH75EFRF9BH4MT0Q3GR", + "kind": "memory", + "score": 0.808289110660553, + "summary": "project:fact - [tags: testing time flaky clock mock rust] Tests that depend on wall-clock time are inherently flaky under load (slow CI runners, GC pauses). Abstract time behind a trait (`Clock: Fn() -> SystemTime`) injected at construction, and supply a fake in tests. For tests checking that something happened \"within N seconds\", use a generous multiple of the expected duration (10x is not unreasonable for CI)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1059.1616, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 790, + "mcp_result_bytes": 875, + "wire_bytes": 912, + "reported_used_tokens": 875, + "working_set_bytes": 292941824, + "peak_working_set_bytes": 293859328 + }, + { + "query": "proptest found a hash collision in text normalization that example tests missed", + "ranked": [ + "testing-property-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061GRG5X0M57D3GHW6JES", + "id": "01M1Y0AVJRBEFTAK8QAQ2NBJNB", + "kind": "memory", + "score": 0.9994783997535706, + "summary": "project:fact - [tags: testing property-based proptest quickcheck rust] Property-based tests (proptest, quickcheck) find edge cases that example-based tests miss. For kimetsu's memory text normalization, proptest found that zero-width joiner characters and right-to-left marks caused hash collisions. Run proptest with `PROPTEST_CASES=10000` in CI for thorough coverage." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1140.0357000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 744, + "mcp_result_bytes": 825, + "wire_bytes": 862, + "reported_used_tokens": 825, + "working_set_bytes": 292945920, + "peak_working_set_bytes": 293859328 + }, + { + "query": "set_var in tests races when cargo test runs them in parallel", + "ranked": [ + "testing-serial-vs-parallel" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061HRKB2V85Z6MW33WPRE", + "id": "01M1Y0AWNK812QJFV6YZAHT3ZQ", + "kind": "memory", + "score": 0.9997344613075256, + "summary": "project:fact - [2026-09-07] [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 992.7324, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 832, + "mcp_result_bytes": 913, + "wire_bytes": 950, + "reported_used_tokens": 913, + "working_set_bytes": 292945920, + "peak_working_set_bytes": 293863424 + }, + { + "query": "hardcoded JSON fixtures broke after a schema migration", + "ranked": [ + "testing-fixture-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061NJMXF06HD6WSDEN8JZ", + "id": "01M1Y0AXMQAPQEARH68XYCY0SN", + "kind": "memory", + "score": 0.9998371601104736, + "summary": "project:fact - [2026-09-07] [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 819.7195, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 783, + "mcp_result_bytes": 864, + "wire_bytes": 901, + "reported_used_tokens": 864, + "working_set_bytes": 292945920, + "peak_working_set_bytes": 293863424 + }, + { + "query": "debug print in the MCP handler corrupts the JSON-Lines protocol stream", + "ranked": [ + "mcp-stdout-protocol" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061PEX980HR5500H8P7ZH", + "id": "01M1Y0AYEB4CNDR8VE5E083N98", + "kind": "memory", + "score": 0.9997472167015076, + "summary": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1038.4728, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 705, + "mcp_result_bytes": 786, + "wire_bytes": 823, + "reported_used_tokens": 786, + "working_set_bytes": 292945920, + "peak_working_set_bytes": 293863424 + }, + { + "query": "kimetsu MCP tool call times out because embedding model is re-initialized every call", + "ranked": [ + "mcp-tool-timeouts", + "mcp-schema-validation", + "kimetsu-bench-remote-embedder-singleton" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061QHG7EX7KYBKD86CQYC", + "id": "01M1Y0AZERF0HZ5CR65K1AXKWG", + "kind": "memory", + "score": 0.9995898604393004, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking — in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize — keep it in a process-global `OnceLock`)." + }, + { + "expansion_handle": "memory:01M1Y061SPN3ZV6B1WDAVECD5V", + "id": "01M1Y0AZER79W7J1X7HAK350VJ", + "kind": "memory", + "score": 0.6027993559837341, + "summary": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array — omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error." + }, + { + "expansion_handle": "memory:01M1Y062K14KTD5D5SR7NWV35P", + "id": "01M1Y0AZERT1QPGF4VQW0KHFFJ", + "kind": "memory", + "score": 0.5117799639701843, + "summary": "project:fact - [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1139.2597, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2085, + "mcp_result_bytes": 2202, + "wire_bytes": 2239, + "reported_used_tokens": 2202, + "working_set_bytes": 292945920, + "peak_working_set_bytes": 293863424 + }, + { + "query": "env var set after host launch is not visible to the MCP server process", + "ranked": [ + "mcp-env-propagation", + "kimetsu-daemon-lifecycle" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061RKZW8EMQN44S984P8P", + "id": "01M1Y0B0JPGBN6KWJ5V92MS95T", + "kind": "memory", + "score": 0.9984827637672424, + "summary": "project:fact - [2026-09-07] [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment — changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate." + }, + { + "expansion_handle": "memory:01M1Y0629C2P66GZ8JJCVPWPR9", + "id": "01M1Y0B0JPA2116Z1PP6271N3F", + "kind": "memory", + "score": 0.9977922439575196, + "summary": "project:fact - [2026-09-07] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1138.8609999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1267, + "mcp_result_bytes": 1366, + "wire_bytes": 1403, + "reported_used_tokens": 1366, + "working_set_bytes": 292945920, + "peak_working_set_bytes": 293863424 + }, + { + "query": "MCP tool call fails because a required field is missing from the JSON input", + "ranked": [ + "mcp-schema-validation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061SPN3ZV6B1WDAVECD5V", + "id": "01M1Y0B1P1GKRF66N864DP9CNX", + "kind": "memory", + "score": 0.998538613319397, + "summary": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array — omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 995.9949, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 798, + "mcp_result_bytes": 879, + "wire_bytes": 916, + "reported_used_tokens": 879, + "working_set_bytes": 292945920, + "peak_working_set_bytes": 293863424 + }, + { + "query": "Claude Code rejects the tool name with a hyphen in it", + "ranked": [ + "mcp-tool-naming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061TPTKP8TGYS8ZZJBXTG", + "id": "01M1Y0B2NE5N26H9E6KJM6T72N", + "kind": "memory", + "score": 0.9982439279556274, + "summary": "project:fact - [tags: mcp tool naming convention kimetsu] MCP tool names must be valid identifiers for all host agents. Claude Code restricts tool names to `[a-zA-Z0-9_-]` and max 64 chars. Use `snake_case` (kimetsu_brain_context, kimetsu_brain_record) — hyphen is technically allowed but some hosts reject it." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1013.8944000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 687, + "mcp_result_bytes": 768, + "wire_bytes": 805, + "reported_used_tokens": 768, + "working_set_bytes": 293064704, + "peak_working_set_bytes": 293974016 + }, + { + "query": "MCP response path uses backslashes and the host rejects it", + "ranked": [ + "mcp-transcript-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061VRMBRDPPJGNQ57KBB0", + "id": "01M1Y0B3MV00EJJT7NF317W0F2", + "kind": "memory", + "score": 0.9984637498855592, + "summary": "project:fact - [tags: mcp transcript paths kimetsu hooks runs] kimetsu writes run transcripts to `/.kimetsu/runs//`. The post-session hook reads the latest run's transcript to trigger memory harvest. On Windows, the path uses backslashes internally but the MCP JSON must use forward slashes or the host may reject path-type arguments." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1029.8874, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 724, + "mcp_result_bytes": 805, + "wire_bytes": 842, + "reported_used_tokens": 805, + "working_set_bytes": 293117952, + "peak_working_set_bytes": 294023168 + }, + { + "query": "AWS credentials not found — which env var does kimetsu read for Bedrock?", + "ranked": [ + "aws-credentials-chain", + "aws-region-resolution", + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061WWY2XRNJE51QHGVRAM", + "id": "01M1Y0B4NHPTATKKSMG7M3RNRS", + "kind": "memory", + "score": 0.9990235567092896, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + }, + { + "expansion_handle": "memory:01M1Y061Y6112PMFKN5GEJZE8M", + "id": "01M1Y0B4NJFK3BT5VQBF7PRFV2", + "kind": "memory", + "score": 0.9968422651290894, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1Y05YH1NQKFMZ0R78E7Z8SP", + "id": "01M1Y0B4NHZ4RCXGZJDFCJAS06", + "kind": "memory", + "score": 0.9849756360054016, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1Y05YPKKNJWV8P7EZDPZJTG", + "id": "01M1Y0B4NJ62KNWNSQ5XEWDVVD", + "kind": "memory", + "score": 0.9203452467918396, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1108.5313999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3455, + "mcp_result_bytes": 3618, + "wire_bytes": 3655, + "reported_used_tokens": 3618, + "working_set_bytes": 293462016, + "peak_working_set_bytes": 294371328 + }, + { + "query": "Bedrock InvokeModel fails because the region is not configured", + "ranked": [ + "aws-region-resolution", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061Y6112PMFKN5GEJZE8M", + "id": "01M1Y0B5QRS1WJ5V7BN93KCAKB", + "kind": "memory", + "score": 0.99688321352005, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1Y05YPKKNJWV8P7EZDPZJTG", + "id": "01M1Y0B5QRKED8BS5DMNFQ524G", + "kind": "memory", + "score": 0.6450709104537964, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1111.6677, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1810, + "mcp_result_bytes": 1929, + "wire_bytes": 1966, + "reported_used_tokens": 1929, + "working_set_bytes": 293462016, + "peak_working_set_bytes": 294375424 + }, + { + "query": "how do I handle ThrottlingException from Bedrock with exponential backoff?", + "ranked": [ + "aws-retry-throttling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061Z7A6RC4W57V7Z3CXS9", + "id": "01M1Y0B6TG8WSWG0AK1XAS7QKA", + "kind": "memory", + "score": 0.9997082352638244, + "summary": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with ±25% jitter." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1104.0339, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 771, + "mcp_result_bytes": 868, + "wire_bytes": 905, + "reported_used_tokens": 868, + "working_set_bytes": 293462016, + "peak_working_set_bytes": 294375424 + }, + { + "query": "generating a presigned S3 URL for brain export without exposing credentials", + "ranked": [ + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0620B2W192A9Y86X0ZB3D", + "id": "01M1Y0B7X6PZ94FB8SF3SGTSCV", + "kind": "memory", + "score": 0.9990487694740297, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time — clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1029.8686, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 875, + "mcp_result_bytes": 956, + "wire_bytes": 993, + "reported_used_tokens": 956, + "working_set_bytes": 293462016, + "peak_working_set_bytes": 294375424 + }, + { + "query": "IMDSv2 token required for instance metadata — PUT before GET", + "ranked": [ + "aws-instance-metadata" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0621DDYB1YEMWDHDQVHKF", + "id": "01M1Y0B8X8YC3WGWZS8DS2CCZG", + "kind": "memory", + "score": 0.9997182488441468, + "summary": "project:fact - [2026-09-07] [tags: aws imds instance-metadata ec2 token] The AWS Instance Metadata Service v2 (IMDSv2) requires a session token: PUT `http://169.254.169.254/latest/api/token` with `X-aws-ec2-metadata-token-ttl-seconds: 21600` to get a token, then GET metadata with `X-aws-ec2-metadata-token: `. IMDSv1 (no token) is disabled on hardened instances. The metadata endpoint is only reachable from within EC2 — a connection timeout means you're not on EC2." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1061.8654999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 851, + "mcp_result_bytes": 932, + "wire_bytes": 969, + "reported_used_tokens": 932, + "working_set_bytes": 293462016, + "peak_working_set_bytes": 294375424 + }, + { + "query": "Cargo cache key strategy for GitHub Actions to avoid toolchain version collisions", + "ranked": [ + "ci-cache-keys" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0624MJHY6FBK9QT43NJHN", + "id": "01M1Y0B9YN9TSX8WRBWW0KWN2N", + "kind": "memory", + "score": 0.998869240283966, + "summary": "project:fact - [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key — macOS and Windows have incompatible artifact formats." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1071.9662999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 788, + "mcp_result_bytes": 869, + "wire_bytes": 906, + "reported_used_tokens": 869, + "working_set_bytes": 293462016, + "peak_working_set_bytes": 294375424 + }, + { + "query": "CI matrix has 18 jobs and costs too much — how do I reduce it?", + "ranked": [ + "ci-matrix-explosion" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0625HY0BZ3TKCV0YJHVBD", + "id": "01M1Y0BAZX62WJ1X1242GN4QF9", + "kind": "memory", + "score": 0.999057948589325, + "summary": "project:fact - [tags: ci github-actions matrix jobs resources] A CI matrix combining OS (3) x Rust toolchain (3) x features (2) = 18 jobs. Each spawns a runner; at $0.008/min for Ubuntu and $0.016/min for Windows, a 10-minute build costs $2.40 per push. Reduce: test the full matrix only on PRs to main; on feature branches, test only Linux+stable." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1095.7056, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 722, + "mcp_result_bytes": 803, + "wire_bytes": 840, + "reported_used_tokens": 803, + "working_set_bytes": 293462016, + "peak_working_set_bytes": 294375424 + }, + { + "query": "GitHub Actions secret accidentally printed in build logs", + "ranked": [ + "ci-secrets-masking", + "ci-cache-keys", + "ci-artifact-retention" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0626EATKH9B42B4W92CC3", + "id": "01M1Y0BC26JG2ZNNXVHATZXEPW", + "kind": "memory", + "score": 0.9963951706886292, + "summary": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output — but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable." + }, + { + "expansion_handle": "memory:01M1Y0624MJHY6FBK9QT43NJHN", + "id": "01M1Y0BC265KD2MBP0VZHKEFPA", + "kind": "memory", + "score": 0.4342843890190125, + "summary": "project:fact - [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key — macOS and Windows have incompatible artifact formats." + }, + { + "expansion_handle": "memory:01M1Y0627E5YD6JZ9T5NA8JG83", + "id": "01M1Y0BC26HZE75179TEJ4X98B", + "kind": "memory", + "score": 0.3422144949436188, + "summary": "project:fact - [tags: ci github-actions artifacts retention benchmark] GitHub Actions artifacts are retained for 90 days (default). For benchmark results, use `actions/upload-artifact` with `retention-days: 365` for long-term tracking. The free tier has 500MB storage — per-combo JSON files from kimetsu bench (each ~60KB) add up fast if you upload them on every push." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1056.0203, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1791, + "mcp_result_bytes": 1908, + "wire_bytes": 1945, + "reported_used_tokens": 1908, + "working_set_bytes": 293462016, + "peak_working_set_bytes": 294375424 + }, + { + "query": "how long do GitHub Actions artifacts persist and what's the storage limit?", + "ranked": [ + "ci-artifact-retention" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0627E5YD6JZ9T5NA8JG83", + "id": "01M1Y0BD38Z88V0MNSPSAGYQKD", + "kind": "memory", + "score": 0.999624252319336, + "summary": "project:fact - [tags: ci github-actions artifacts retention benchmark] GitHub Actions artifacts are retained for 90 days (default). For benchmark results, use `actions/upload-artifact` with `retention-days: 365` for long-term tracking. The free tier has 500MB storage — per-combo JSON files from kimetsu bench (each ~60KB) add up fast if you upload them on every push." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1124.1330999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 744, + "mcp_result_bytes": 825, + "wire_bytes": 862, + "reported_used_tokens": 825, + "working_set_bytes": 293466112, + "peak_working_set_bytes": 294379520 + }, + { + "query": "timing-based test flake in CI — quarantine or fix?", + "ranked": [ + "ci-flaky-quarantine", + "testing-time-dependent-flakes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y06288TD3HYPJQNP7S1ZNB", + "id": "01M1Y0BE6QQY5EZZJ4V9AGX64H", + "kind": "memory", + "score": 0.9994743466377258, + "summary": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal — a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output." + }, + { + "expansion_handle": "memory:01M1Y061FPYM98XSA9JHTRM8PK", + "id": "01M1Y0BE6QW2SR3W7B849671TJ", + "kind": "memory", + "score": 0.9849997162818908, + "summary": "project:fact - [tags: testing time flaky clock mock rust] Tests that depend on wall-clock time are inherently flaky under load (slow CI runners, GC pauses). Abstract time behind a trait (`Clock: Fn() -> SystemTime`) injected at construction, and supply a fake in tests. For tests checking that something happened \"within N seconds\", use a generous multiple of the expected duration (10x is not unreasonable for CI)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1066.3907, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1340, + "mcp_result_bytes": 1443, + "wire_bytes": 1480, + "reported_used_tokens": 1443, + "working_set_bytes": 293466112, + "peak_working_set_bytes": 294383616 + }, + { + "query": "kimetsu doctor says the MCP server is running — how do I stop it before an update?", + "ranked": [ + "kimetsu-daemon-lifecycle", + "mcp-env-propagation", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0629C2P66GZ8JJCVPWPR9", + "id": "01M1Y0BF8A8D99JENGMYEK0NDM", + "kind": "memory", + "score": 0.9989782571792604, + "summary": "project:fact - [2026-09-07] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1Y061RKZW8EMQN44S984P8P", + "id": "01M1Y0BF8AJX2K27DVSHW7XPKG", + "kind": "memory", + "score": 0.9049031734466552, + "summary": "project:fact - [2026-09-07] [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment — changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate." + }, + { + "expansion_handle": "memory:01M1Y05YDQZCEVNBN7TDX4XC9B", + "id": "01M1Y0BF8AQ278C2SX9JWBX99K", + "kind": "memory", + "score": 0.4812128245830536, + "summary": "project:fact - [2026-09-07] [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1126.0901000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2046, + "mcp_result_bytes": 2211, + "wire_bytes": 2248, + "reported_used_tokens": 2211, + "working_set_bytes": 293466112, + "peak_working_set_bytes": 294383616 + }, + { + "query": "noise capsules consuming token budget without contributing retrieval signal", + "ranked": [ + "kimetsu-capsule-budgets" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y062ABMMMT09Z0H9J6WG1B", + "id": "01M1Y0BGB0G8NMMN6TXQA570JB", + "kind": "memory", + "score": 0.9997420907020568, + "summary": "project:fact - [tags: kimetsu capsule tokens budget retrieval] kimetsu retrieval enforces a token budget per capsule type: memory capsules are capped at 6000 tokens total (across all retrieved memories), file capsules at 3000 tokens. When a memory is large and would exceed the budget, it is truncated at a sentence boundary. The budget is enforced AFTER reranking — reranking may reorder results so that a truncated high-ranked memory displaces a full lower-ranked one." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 884.0705, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 847, + "mcp_result_bytes": 928, + "wire_bytes": 965, + "reported_used_tokens": 928, + "working_set_bytes": 293466112, + "peak_working_set_bytes": 294383616 + }, + { + "query": "kimetsu_brain_record writes to the wrong brain location — user vs project scope", + "ranked": [ + "kimetsu-memory-scopes", + "kimetsu-write-tools-gate", + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y062B89NB3SMAHX7SX830R", + "id": "01M1Y0BH7GP3DG7T459Z02CEYG", + "kind": "memory", + "score": 0.999030828475952, + "summary": "project:fact - [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available — if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope." + }, + { + "expansion_handle": "memory:01M1Y062EEFBC57YJ58J3CSXFD", + "id": "01M1Y0BH7GM8BKPHZ3X63ESW0G", + "kind": "memory", + "score": 0.9838979840278624, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level — disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1Y05YS4YPH8D67NSXJGT1SS", + "id": "01M1Y0BH7G6PZD4RT5X8Y4E22V", + "kind": "memory", + "score": 0.3852712512016296, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1052.1639, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2098, + "mcp_result_bytes": 2215, + "wire_bytes": 2252, + "reported_used_tokens": 2215, + "working_set_bytes": 293466112, + "peak_working_set_bytes": 294383616 + }, + { + "query": "how do I configure kimetsu to use Claude Haiku for harvesting but Opus for the agent?", + "ranked": [ + "kimetsu-distiller-config" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y062CBNZWHRGE999FY1B1Q", + "id": "01M1Y0BJ7Q44JXZH4M9FK03V4T", + "kind": "memory", + "score": 0.9989088773727416, + "summary": "project:fact - [tags: kimetsu distiller harvest config provider] The kimetsu distiller (auto-harvester) uses a SEPARATE provider configuration from the main agent: `distiller.provider`, `distiller.model`, `distiller.api_key`. This allows running the agent on an expensive model (Claude Opus) while harvesting with a cheap model (Claude Haiku). If `distiller.provider` is not set, it inherits `provider`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1067.6490000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 778, + "mcp_result_bytes": 859, + "wire_bytes": 896, + "reported_used_tokens": 859, + "working_set_bytes": 293470208, + "peak_working_set_bytes": 294383616 + }, + { + "query": "first agent turn is slow because kimetsu proactive hook runs embedding inference", + "ranked": [ + "kimetsu-proactive-hooks", + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y062DDSWX11Z89QR995TW9", + "id": "01M1Y0BK98C8SYTZ6FNQVSKM26", + "kind": "memory", + "score": 0.999568521976471, + "summary": "project:fact - [2026-09-07] [tags: kimetsu proactive hooks context injection] kimetsu's proactive context injection runs before each agent turn (pre-turn hook) and injects relevant memories into the system prompt prefix. The hook invocation adds latency to the first token: embedding inference + vector search + reranking + context formatting. On a cold start, this can be 1-3 seconds." + }, + { + "expansion_handle": "memory:01M1Y061QHG7EX7KYBKD86CQYC", + "id": "01M1Y0BK98Y6D3N2PPMNR610S7", + "kind": "memory", + "score": 0.9405298233032228, + "summary": "project:fact - [2026-09-07] [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking — in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize — keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1100.5240999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1403, + "mcp_result_bytes": 1502, + "wire_bytes": 1539, + "reported_used_tokens": 1502, + "working_set_bytes": 293470208, + "peak_working_set_bytes": 294383616 + }, + { + "query": "make the kimetsu brain read-only for certain repos on a shared remote server", + "ranked": [ + "kimetsu-write-tools-gate", + "remote-ingest-split-roots", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y062EEFBC57YJ58J3CSXFD", + "id": "01M1Y0BMCXCYDKB134CKVNXPYM", + "kind": "memory", + "score": 0.997682809829712, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level — disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1Y05YBPJ3QEY9DGV3Y39QED", + "id": "01M1Y0BMCXG5P32PYW1K2KRGS4", + "kind": "memory", + "score": 0.9957050681114196, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1Y05YDQZCEVNBN7TDX4XC9B", + "id": "01M1Y0BMCXY8FNCD4ZANTS61P5", + "kind": "memory", + "score": 0.9909282326698304, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1080.4236, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2725, + "mcp_result_bytes": 2890, + "wire_bytes": 2927, + "reported_used_tokens": 2890, + "working_set_bytes": 293470208, + "peak_working_set_bytes": 294383616 + }, + { + "query": "kimetsu FTS search misses 'deadlocking' when memory says 'deadlock'", + "ranked": [ + "kimetsu-query-stemming", + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y062H2NN1S48YS74CA719R", + "id": "01M1Y0BNDHRGPSNN01CBVH2TDA", + "kind": "memory", + "score": 0.9904030561447144, + "summary": "project:fact - [2026-09-07] [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression." + }, + { + "expansion_handle": "memory:01M1Y05YAM1J3BB2GHQRB5M0FP", + "id": "01M1Y0BNDHTQWM47HEW9EJJWQA", + "kind": "memory", + "score": 0.91664320230484, + "summary": "project:fact - [2026-09-07] [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure — `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 962.572, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1363, + "mcp_result_bytes": 1478, + "wire_bytes": 1515, + "reported_used_tokens": 1478, + "working_set_bytes": 293470208, + "peak_working_set_bytes": 294383616 + }, + { + "query": "how does pool size affect retrieval recall and latency in the bench?", + "ranked": [ + "kimetsu-rerank-pool" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y062J3KDZ9VQM7G169HV60", + "id": "01M1Y0BPB9DEKY9DQ1FKMSZYV7", + "kind": "memory", + "score": 0.9998373985290528, + "summary": "project:fact - [tags: kimetsu reranker pool size ann retrieval] kimetsu's retrieval pipeline: ANN (approximate nearest neighbor) retrieves a pool of candidates, then the reranker reorders them, then the top-K are returned. The pool size (default 6 for production, 12 in bench) controls the recall-latency tradeoff: larger pool = higher recall = more reranker calls = more latency. For the jina-tiny reranker, pool 12 adds ~80ms vs pool 6." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1018.4503000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 813, + "mcp_result_bytes": 894, + "wire_bytes": 931, + "reported_used_tokens": 894, + "working_set_bytes": 293470208, + "peak_working_set_bytes": 294383616 + }, + { + "query": "second embedder in a remote bench run gets worse results than the first", + "ranked": [ + "kimetsu-bench-remote-embedder-singleton" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y062K14KTD5D5SR7NWV35P", + "id": "01M1Y0BQBNHS73JRXSZVNRGSV4", + "kind": "memory", + "score": 0.9939629435539246, + "summary": "project:fact - [2026-09-07] [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1065.9832000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 895, + "mcp_result_bytes": 976, + "wire_bytes": 1013, + "reported_used_tokens": 976, + "working_set_bytes": 293470208, + "peak_working_set_bytes": 294383616 + }, + { + "query": "what is the expected JSON schema for kimetsu brain bench dataset files?", + "ranked": [ + "kimetsu-eval-fixture-shape", + "testing-fixture-drift", + "kimetsu-mrr-metric", + "mcp-schema-validation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y062M3CFFQGT02EBZTJSHN", + "id": "01M1Y0BRCH630CXGAAC3JA9QD0", + "kind": "memory", + "score": 0.9996767044067384, + "summary": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` — a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases)." + }, + { + "expansion_handle": "memory:01M1Y061NJMXF06HD6WSDEN8JZ", + "id": "01M1Y0BRCHV27B3QBVN3972AJ7", + "kind": "memory", + "score": 0.9682880640029908, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + }, + { + "expansion_handle": "memory:01M1Y062N6MYSHTZCNN349MES6", + "id": "01M1Y0BRCHVK21Z3HSWE61HR8S", + "kind": "memory", + "score": 0.8818408250808716, + "summary": "project:fact - [tags: kimetsu bench mrr recall metrics evaluation] kimetsu bench reports MRR (Mean Reciprocal Rank) and Recall@K. MRR is 1/rank_of_first_relevant_result, averaged across cases; it penalizes models that rank the correct answer 2nd or 3rd. Recall@K is the fraction of cases where at least one relevant answer appears in the top K." + }, + { + "expansion_handle": "memory:01M1Y061SPN3ZV6B1WDAVECD5V", + "id": "01M1Y0BRCHG1YAM41JATZ7R9N1", + "kind": "memory", + "score": 0.6527947187423706, + "summary": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array — omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1105.2305, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2424, + "mcp_result_bytes": 2603, + "wire_bytes": 2640, + "reported_used_tokens": 2603, + "working_set_bytes": 293470208, + "peak_working_set_bytes": 294383616 + }, + { + "query": "what does MRR mean and how do I interpret a 0.01 difference between combos?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1108.5317, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 293470208, + "peak_working_set_bytes": 294383616 + }, + { + "query": "SQLITE_BUSY keeps appearing even with WAL mode enabled", + "ranked": [ + "sqlite-busy-timeout-wal", + "sqlite-wal-network-drive" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05Z2BBHSJ0CDKCQ4XH9GE", + "id": "01M1Y0BTHQRZKQE63P8N5AXDCJ", + "kind": "memory", + "score": 0.9982662796974182, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + }, + { + "expansion_handle": "memory:01M1Y05Z57SYAWW7723HQ5J9NG", + "id": "01M1Y0BTHQY0MAJ69HWNXS3DVY", + "kind": "memory", + "score": 0.7844027280807495, + "summary": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1141.4902, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1423, + "mcp_result_bytes": 1522, + "wire_bytes": 1559, + "reported_used_tokens": 1522, + "working_set_bytes": 293691392, + "peak_working_set_bytes": 294600704 + }, + { + "query": "my brain file got huge again right after I compacted it", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1008.4443000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 293695488, + "peak_working_set_bytes": 294600704 + }, + { + "query": "all my FTS queries stopped returning results after I changed the tokenizer config", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1094.4026, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 293695488, + "peak_working_set_bytes": 294604800 + }, + { + "query": "something is preventing the kimetsu binary from being replaced during update", + "ranked": [ + "kimetsu-daemon-lifecycle", + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0629C2P66GZ8JJCVPWPR9", + "id": "01M1Y0BXQ6TPG3GZFTTFARST16", + "kind": "memory", + "score": 0.9678457975387572, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1Y05Z0A5WK204VYT0BQ2A96", + "id": "01M1Y0BXQ7Z455E5RNH7FCKH80", + "kind": "memory", + "score": 0.9395453929901124, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics — mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 0.6666666666666666, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 975.5285, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1657, + "mcp_result_bytes": 1756, + "wire_bytes": 1793, + "reported_used_tokens": 1756, + "working_set_bytes": 293695488, + "peak_working_set_bytes": 294604800 + }, + { + "query": "tool call results not appearing in the context — is the semantic floor too high?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1076.7582, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 293699584, + "peak_working_set_bytes": 294617088 + }, + { + "query": "CARGO_INCREMENTAL=0 in CI prevents a class of spurious compilation errors", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05ZFT0B24FSK97XY8Y0WX", + "id": "01M1Y0BZQAZ4Z8SYPTP5CJKC07", + "kind": "memory", + "score": 0.7995238304138184, + "summary": "project:fact - [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 989.5859, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 877, + "mcp_result_bytes": 958, + "wire_bytes": 995, + "reported_used_tokens": 958, + "working_set_bytes": 293826560, + "peak_working_set_bytes": 294739968 + }, + { + "query": "how do I check whether my Cargo workspace respects the MSRV constraint?", + "ranked": [ + "cargo-msrv", + "cargo-dev-dep-leak", + "cargo-patch-section", + "cargo-target-dir-sharing" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05ZJEDXBR4GP8409Q7V6X", + "id": "01M1Y0C0PPCEMY907DY4ZVB036", + "kind": "memory", + "score": 0.9921064376831056, + "summary": "project:fact - [tags: cargo rust msrv edition compatibility] Set `rust-version` in each `Cargo.toml` to declare the minimum supported Rust version (MSRV). Cargo enforces this with `--check`: `cargo check` fails if the toolchain is older than `rust-version`. Keep MSRV as old as your oldest supported deployment target." + }, + { + "expansion_handle": "memory:01M1Y05ZDSAHJXT2TMGQG83BWE", + "id": "01M1Y0C0PP9YFWGE3F28YT0MJ5", + "kind": "memory", + "score": 0.887407660484314, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + }, + { + "expansion_handle": "memory:01M1Y05ZHK2ZA3FKKK7GBBNKFB", + "id": "01M1Y0C0PPB6CN4KQ11YBBG56V", + "kind": "memory", + "score": 0.7220955491065979, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace — including transitive deps — that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1Y05ZETTM7QKBSZ1FY92RQ8", + "id": "01M1Y0C0PP2ZB6BKAGDVBMR4KQ", + "kind": "memory", + "score": 0.4095200598239898, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps — use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1115.5216, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2682, + "mcp_result_bytes": 2821, + "wire_bytes": 2858, + "reported_used_tokens": 2821, + "working_set_bytes": 293826560, + "peak_working_set_bytes": 294744064 + }, + { + "query": "rusqlite connection opened but ON DELETE CASCADE cascade never fires", + "ranked": [ + "sqlite-foreign-keys-default-off" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y05Z843AFP72Y2BY2XZ4Q0", + "id": "01M1Y0C1SMT4SY9J4AQ1ZANHNR", + "kind": "memory", + "score": 0.9922945499420166, + "summary": "project:fact - [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting — every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1026.1435999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 735, + "mcp_result_bytes": 816, + "wire_bytes": 853, + "reported_used_tokens": 816, + "working_set_bytes": 293830656, + "peak_working_set_bytes": 294744064 + }, + { + "query": "I cannot connect to kimetsu-remote — something about TLS cert validation failed", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 963.361, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 293838848, + "peak_working_set_bytes": 294752256 + }, + { + "query": "graceful shutdown fails because in-flight SQLite queries are still running when pool closes", + "ranked": [ + "tokio-shutdown-ordering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0615DY78S91BYCR1PW38X", + "id": "01M1Y0C3QBDM24WCPBARBSR3FC", + "kind": "memory", + "score": 0.9996342658996582, + "summary": "project:fact - [2026-09-07] [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries — the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1012.2230999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 947, + "mcp_result_bytes": 1028, + "wire_bytes": 1065, + "reported_used_tokens": 1028, + "working_set_bytes": 293965824, + "peak_working_set_bytes": 294875136 + }, + { + "query": "kimetsu-remote response takes 8 seconds — which stage is slow?", + "ranked": [ + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061QHG7EX7KYBKD86CQYC", + "id": "01M1Y0C4Q0VBFCCZSMJ07GE7Z6", + "kind": "memory", + "score": 0.9876242876052856, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking — in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize — keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1066.8466999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 858, + "mcp_result_bytes": 939, + "wire_bytes": 976, + "reported_used_tokens": 939, + "working_set_bytes": 293965824, + "peak_working_set_bytes": 294875136 + }, + { + "query": "git reflog to rescue accidentally deleted branch", + "ranked": [ + "git-reflog-rescue" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060VJEBGRY23E0DJBZS5T", + "id": "01M1Y0C5RH06XCS1335S11RPQZ", + "kind": "memory", + "score": 0.998464822769165, + "summary": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone — they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only — remote reflog is not accessible via normal git commands." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1115.3663000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 761, + "mcp_result_bytes": 842, + "wire_bytes": 879, + "reported_used_tokens": 842, + "working_set_bytes": 293965824, + "peak_working_set_bytes": 294875136 + }, + { + "query": "git submodule --remote advances the pinned SHA unexpectedly", + "ranked": [ + "git-submodule-pinning", + "git-reflog-rescue", + "ci-secrets-masking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060THWEBZHRGKPBJRS88X", + "id": "01M1Y0C6V64HKHMZ7YGRK4M3NB", + "kind": "memory", + "score": 0.9998551607131958, + "summary": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip — this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version." + }, + { + "expansion_handle": "memory:01M1Y060VJEBGRY23E0DJBZS5T", + "id": "01M1Y0C6V6YY69CHAE0AQQ79J0", + "kind": "memory", + "score": 0.8857361078262329, + "summary": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone — they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only — remote reflog is not accessible via normal git commands." + }, + { + "expansion_handle": "memory:01M1Y0626EATKH9B42B4W92CC3", + "id": "01M1Y0C6V6HZZW29487S2ESHTZ", + "kind": "memory", + "score": 0.8434544205665588, + "summary": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output — but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 956.2325000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1771, + "mcp_result_bytes": 1888, + "wire_bytes": 1925, + "reported_used_tokens": 1888, + "working_set_bytes": 293965824, + "peak_working_set_bytes": 294875136 + }, + { + "query": "axum SSE streaming drops the last event when client disconnects", + "ranked": [ + "http-streaming-bodies" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061B3Z0S7MH3X6Y15J8MA", + "id": "01M1Y0C7S42DXVB7Q9AE42D5D6", + "kind": "memory", + "score": 0.9926375150680542, + "summary": "project:fact - [2026-09-07] [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding — a chunk may split across frame boundaries." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1076.2822, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 859, + "mcp_result_bytes": 940, + "wire_bytes": 977, + "reported_used_tokens": 940, + "working_set_bytes": 293965824, + "peak_working_set_bytes": 294875136 + }, + { + "query": "how do I detect that I am running inside a git worktree vs the main checkout?", + "ranked": [ + "git-worktree-brain-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060PEBE9HMM62FJ922NWN", + "id": "01M1Y0C8TVVAT1PWZH1VC4PHG4", + "kind": "memory", + "score": 0.9857924580574036, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root — if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1033.2648, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 881, + "mcp_result_bytes": 962, + "wire_bytes": 999, + "reported_used_tokens": 962, + "working_set_bytes": 293961728, + "peak_working_set_bytes": 294875136 + }, + { + "query": "ONNX Runtime intra-op threads causing CPU contention during parallel bench", + "ranked": [ + "onnx-ort-threading", + "tokio-blocking-in-async" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y060NFDVT4KKYC4BDWH6D3", + "id": "01M1Y0C9VKQT3NJM7GQC7XKR6E", + "kind": "memory", + "score": 0.9999210834503174, + "summary": "project:fact - [tags: onnx ort thread-pool parallelism cpu] ORT (ONNX Runtime) creates its own inter-op and intra-op thread pools. In a multi-process bench setup, each child inherits these pools and they compete for CPU cores. Set `SessionOptionsBuilder::with_intra_threads(1).with_inter_threads(1)` if you're running many parallel bench processes — this sacrifices per-inference throughput for lower contention." + }, + { + "expansion_handle": "memory:01M1Y060WH1VTPAKJ89CENJJXP", + "id": "01M1Y0C9VKGN0GF8CFZPKJCBXK", + "kind": "memory", + "score": 0.5390238761901855, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking — never call rusqlite directly from an async fn without spawn_blocking." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 955.3720999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1328, + "mcp_result_bytes": 1427, + "wire_bytes": 1464, + "reported_used_tokens": 1427, + "working_set_bytes": 293965824, + "peak_working_set_bytes": 294875136 + }, + { + "query": "what is the right way to supply AWS session token alongside access key and secret?", + "ranked": [ + "aws-credentials-chain" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y061WWY2XRNJE51QHGVRAM", + "id": "01M1Y0CAT4KGKPTJ7H6P1ZD7TC", + "kind": "memory", + "score": 0.9493365287780762, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1084.0350999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 895, + "mcp_result_bytes": 976, + "wire_bytes": 1013, + "reported_used_tokens": 976, + "working_set_bytes": 293965824, + "peak_working_set_bytes": 294875136 + } + ], + "id": "existing-development-100", + "dimension": "retrieval", + "tier": "hard", + "score": 0.8182539682539681, + "skipped": false, + "detail": "positive-recall@4=0.84 mrr=0.85 stale-hit=n/a resolution=n/a false-injection=0.538 (n=13) positive-n=197 negative-n=13 (210 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 0.8182539682539681, + 1 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 0.8182539682539681, + "n": 1, + "ci95": null + } + }, + "overall_index": 0.8182539682539681, + "scenario_weighted_index": 0.8182539682539681 +} diff --git a/docs/audits/2026-09-07-structured-facts/results/development/1-candidate.json b/docs/audits/2026-09-07-structured-facts/results/development/1-candidate.json new file mode 100644 index 0000000..8623d8a --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/results/development/1-candidate.json @@ -0,0 +1,6811 @@ +{ + "generated_at": "2026-09-07T13:23:10.2766037Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-retrieval\\development-100.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "test_env_lock inside with_user_brain_disabled deadlock", + "ranked": [ + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDDKV46DFCQB5VK0TWX5", + "id": "01M1Y0CJM7QHXE6WM12F244071", + "kind": "memory", + "score": 0.9999488592147828, + "summary": "project:fact - [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure \u2014 `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1064.4432, + "first_query": true, + "server_startup_ms": 74.8257, + "model_text_bytes": 796, + "mcp_result_bytes": 877, + "wire_bytes": 912, + "reported_used_tokens": 877, + "working_set_bytes": 226983936, + "peak_working_set_bytes": 248229888 + }, + { + "query": "why does my test hang after calling with_user_brain_disabled when I also lock test_env_lock?", + "ranked": [ + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDDKV46DFCQB5VK0TWX5", + "id": "01M1Y0CKA74STYV56A8DWW7RHE", + "kind": "memory", + "score": 0.9990190267562866, + "summary": "project:fact - [2026-09-07] [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure \u2014 `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 811.5477, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 808, + "mcp_result_bytes": 889, + "wire_bytes": 924, + "reported_used_tokens": 889, + "working_set_bytes": 229031936, + "peak_working_set_bytes": 248229888 + }, + { + "query": "ingest_repo_at_root brain_root files_root kimetsu remote", + "ranked": [ + "remote-ingest-split-roots", + "kimetsu-write-tools-gate", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDERH0MV40CSBCQDEQYA", + "id": "01M1Y0CM3S0J02KTEWJZHJDH9F", + "kind": "memory", + "score": 0.999886393547058, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1Y0CHMVHR9GRRHBAB5NZNDN", + "id": "01M1Y0CM3SPDAC35Y97RRGSCBV", + "kind": "memory", + "score": 0.8439717888832092, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level \u2014 disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1Y0CDGSHENNVZQKGWMDBNDR", + "id": "01M1Y0CM3SN1TT7HY4H6941GZW", + "kind": "memory", + "score": 0.8363722562789917, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 922.0899000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2726, + "mcp_result_bytes": 2891, + "wire_bytes": 2926, + "reported_used_tokens": 2891, + "working_set_bytes": 251822080, + "peak_working_set_bytes": 252735488 + }, + { + "query": "why does the remote server index the wrong directory when I run kimetsu brain ingest?", + "ranked": [ + "remote-ingest-split-roots", + "onnx-dim-mismatch" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDERH0MV40CSBCQDEQYA", + "id": "01M1Y0CN0JZ8HA05XDW2W68JQC", + "kind": "memory", + "score": 0.9836117625236512, + "summary": "project:fact - [2026-09-07] [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1Y0CFS76DA1V14BVQR0NX3T", + "id": "01M1Y0CN0J8S4QAJACBX273RGC", + "kind": "memory", + "score": 0.3657674789428711, + "summary": "project:fact - [2026-09-07] [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results \u2014 the ANN index shape mismatch isn't always caught at runtime." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1032.2575000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1800, + "mcp_result_bytes": 1899, + "wire_bytes": 1934, + "reported_used_tokens": 1899, + "working_set_bytes": 257736704, + "peak_working_set_bytes": 258662400 + }, + { + "query": "kimetsu plugin install --remote mcp.json authorization bearer token", + "ranked": [ + "remote-mcp-host-wiring", + "mcp-stdout-protocol" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDGSHENNVZQKGWMDBNDR", + "id": "01M1Y0CP1BRYA9951N71M38SFA", + "kind": "memory", + "score": 0.999605119228363, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + }, + { + "expansion_handle": "memory:01M1Y0CGVVT9QFVZB1R92FVJYX", + "id": "01M1Y0CP1BCB7WC7ZK46P0CGMX", + "kind": "memory", + "score": 0.3375842869281769, + "summary": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 981.4427999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1472, + "mcp_result_bytes": 1619, + "wire_bytes": 1654, + "reported_used_tokens": 1619, + "working_set_bytes": 258039808, + "peak_working_set_bytes": 258961408 + }, + { + "query": "how do I wire a remote kimetsu brain into Claude Code without storing the token in the config file?", + "ranked": [ + "remote-mcp-host-wiring", + "mcp-tool-naming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDGSHENNVZQKGWMDBNDR", + "id": "01M1Y0CPZMR9A8KFDHVS9YKHG6", + "kind": "memory", + "score": 0.9963359832763672, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + }, + { + "expansion_handle": "memory:01M1Y0CH08W8KRWZHZ6QYESXCW", + "id": "01M1Y0CPZMGCAQDK432HMY5V8X", + "kind": "memory", + "score": 0.831425666809082, + "summary": "project:fact - [tags: mcp tool naming convention kimetsu] MCP tool names must be valid identifiers for all host agents. Claude Code restricts tool names to `[a-zA-Z0-9_-]` and max 64 chars. Use `snake_case` (kimetsu_brain_context, kimetsu_brain_record) \u2014 hyphen is technically allowed but some hosts reject it." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 937.6639, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1454, + "mcp_result_bytes": 1601, + "wire_bytes": 1636, + "reported_used_tokens": 1601, + "working_set_bytes": 258412544, + "peak_working_set_bytes": 259342336 + }, + { + "query": "cargo feature unification kimetsu-brain embeddings fastembed test failure", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-profile-override", + "clap-version-build-flavor" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDJN5BPC6G4D9PSYG7G6", + "id": "01M1Y0CQWXE5KMM65QSW8F48CG", + "kind": "memory", + "score": 0.9996790885925292, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1Y0CEMKNXWNXAX5DG21NDNC", + "id": "01M1Y0CQWXYR9D300CHN24980Y", + "kind": "memory", + "score": 0.9923595786094666, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1Y0CDWXMHS387P46NG7NJCM", + "id": "01M1Y0CQWX2ZZ7984M1QXQVCSH", + "kind": "memory", + "score": 0.585203230381012, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 882.5066, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2387, + "mcp_result_bytes": 2524, + "wire_bytes": 2559, + "reported_used_tokens": 2524, + "working_set_bytes": 259944448, + "peak_working_set_bytes": 260882432 + }, + { + "query": "my integration tests pass in isolation but break when I run cargo test --workspace \u2014 embedder changed?", + "ranked": [ + "cargo-feature-unification-embeddings", + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDJN5BPC6G4D9PSYG7G6", + "id": "01M1Y0CRRJ8XEC8QS787DX7638", + "kind": "memory", + "score": 0.9943140745162964, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1Y0CDW1WMXYA9MGV0TK68J6", + "id": "01M1Y0CRRJG5ZP054Y59DYQ7T9", + "kind": "memory", + "score": 0.31398114562034607, + "summary": "project:fact - [2026-09-07] [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 984.525, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1714, + "mcp_result_bytes": 1817, + "wire_bytes": 1852, + "reported_used_tokens": 1817, + "working_set_bytes": 260771840, + "peak_working_set_bytes": 261697536 + }, + { + "query": "build_anthropic_body bedrock-2023-05-31 InvokeModel blocking reqwest", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDKZJ260T9MFP5RRHHQY", + "id": "01M1Y0CSQ8W9YNNCYP7YYN4YWS", + "kind": "memory", + "score": 0.9973788261413574, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1Y0CDSEBRPP3R78RVYDDGZA", + "id": "01M1Y0CSQ9VMEHFTXZP7JME0GS", + "kind": "memory", + "score": 0.6916899085044861, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 738.1855, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2193, + "mcp_result_bytes": 2320, + "wire_bytes": 2356, + "reported_used_tokens": 2320, + "working_set_bytes": 261148672, + "peak_working_set_bytes": 262066176 + }, + { + "query": "how do I add AWS Bedrock as a model provider in Kimetsu without pulling in the aws-sdk?", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-region-resolution", + "aws-credentials-chain", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDKZJ260T9MFP5RRHHQY", + "id": "01M1Y0CTEK467TJQ479NG6P6CB", + "kind": "memory", + "score": 0.9998898506164552, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1Y0CH4021WNS0JP2HVKJDXQ", + "id": "01M1Y0CTEK2ET2AF229KAFJXAC", + "kind": "memory", + "score": 0.995676338672638, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1Y0CH2MJMDSTCRQ0SR4KPC5", + "id": "01M1Y0CTEK1ZCKVZ1GRAAVJ4AK", + "kind": "memory", + "score": 0.987064242362976, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + }, + { + "expansion_handle": "memory:01M1Y0CDSEBRPP3R78RVYDDGZA", + "id": "01M1Y0CTEKQBB5EFBD1DWRCH9E", + "kind": "memory", + "score": 0.9493880867958068, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 978.8104, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3455, + "mcp_result_bytes": 3618, + "wire_bytes": 3654, + "reported_used_tokens": 3618, + "working_set_bytes": 269479936, + "peak_working_set_bytes": 270401536 + }, + { + "query": "BridgeTarget enum seams plugin_install_inner plugin_status_inner resolve_setup_hosts", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDNPXTVTYW8S244N1GCB", + "id": "01M1Y0CVD1BH6XMPAQ1MYVFGJ6", + "kind": "memory", + "score": 0.9997583031654358, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 763.8307, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1060, + "mcp_result_bytes": 1141, + "wire_bytes": 1177, + "reported_used_tokens": 1141, + "working_set_bytes": 279420928, + "peak_working_set_bytes": 280338432 + }, + { + "query": "I added a new host to the bridge enum but cargo gives me compile errors in five different match arms \u2014 what did I miss?", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDNPXTVTYW8S244N1GCB", + "id": "01M1Y0CW5K74PVV8TYTJRCYGTK", + "kind": "memory", + "score": 0.9977060556411744, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1082.3542, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1059, + "mcp_result_bytes": 1140, + "wire_bytes": 1176, + "reported_used_tokens": 1140, + "working_set_bytes": 279846912, + "peak_working_set_bytes": 280764416 + }, + { + "query": "Pi extension factory defineExtension agent_end session_shutdown kimetsu.ts", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDPTB91KV3WJ6A8WXR9N", + "id": "01M1Y0CX6S0GRS2WEETJG72QC1", + "kind": "memory", + "score": 0.9990354776382446, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1031.424, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 804, + "mcp_result_bytes": 893, + "wire_bytes": 929, + "reported_used_tokens": 893, + "working_set_bytes": 280129536, + "peak_working_set_bytes": 281047040 + }, + { + "query": "how does Pi (earendil-works/pi) load plugins and what lifecycle hooks does it expose?", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDPTB91KV3WJ6A8WXR9N", + "id": "01M1Y0CY7FJH9ANK3RJRFGD691", + "kind": "memory", + "score": 0.9934834837913512, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1114.2602, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 803, + "mcp_result_bytes": 892, + "wire_bytes": 928, + "reported_used_tokens": 892, + "working_set_bytes": 280248320, + "peak_working_set_bytes": 281161728 + }, + { + "query": "aws-sigv4 SigningParams apply_to_request_http1x reqwest sign-http", + "ranked": [ + "aws-sigv4-bedrock-blocking", + "aws-presigned-urls", + "bedrock-kimetsu-provider", + "aws-credentials-chain" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDSEBRPP3R78RVYDDGZA", + "id": "01M1Y0CZ9RGP3WXV6QJCT2GH9C", + "kind": "memory", + "score": 0.9995608925819396, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1Y0CH6AWWAV734TFW14SHW0", + "id": "01M1Y0CZ9RHYE1WF09KKZWP39B", + "kind": "memory", + "score": 0.984916627407074, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + }, + { + "expansion_handle": "memory:01M1Y0CDKZJ260T9MFP5RRHHQY", + "id": "01M1Y0CZ9RT7C943Q6SHTXA3RA", + "kind": "memory", + "score": 0.983895778656006, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1Y0CH2MJMDSTCRQ0SR4KPC5", + "id": "01M1Y0CZ9RMMWN3A7P9XSZG2CH", + "kind": "memory", + "score": 0.8592692017555237, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 798.3355, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3507, + "mcp_result_bytes": 3670, + "wire_bytes": 3706, + "reported_used_tokens": 3670, + "working_set_bytes": 280408064, + "peak_working_set_bytes": 281313280 + }, + { + "query": "how do I sign a Bedrock InvokeModel request with aws-sigv4 in blocking Rust?", + "ranked": [ + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider", + "aws-region-resolution", + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDSEBRPP3R78RVYDDGZA", + "id": "01M1Y0D02WVA4D5V36SQEGFZEX", + "kind": "memory", + "score": 0.9998323917388916, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1Y0CDKZJ260T9MFP5RRHHQY", + "id": "01M1Y0D02WDDA9Z8T7PJR6M3AQ", + "kind": "memory", + "score": 0.9970844388008118, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1Y0CH4021WNS0JP2HVKJDXQ", + "id": "01M1Y0D02W17W5FJPPQ7V8MH1S", + "kind": "memory", + "score": 0.9468621611595154, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1Y0CH6AWWAV734TFW14SHW0", + "id": "01M1Y0D02W0EZET761XESTVBAE", + "kind": "memory", + "score": 0.9210098385810852, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 938.9264000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3434, + "mcp_result_bytes": 3597, + "wire_bytes": 3633, + "reported_used_tokens": 3597, + "working_set_bytes": 280866816, + "peak_working_set_bytes": 281788416 + }, + { + "query": "KIMETSU_RUNS_GC env opt-out TraceWriter create gc_old_runs caller", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDV606AJC3CB2VR63EVV", + "id": "01M1Y0D106DG8W1P6VXDHR0463", + "kind": "memory", + "score": 0.999936580657959, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 863.3108000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 761, + "mcp_result_bytes": 842, + "wire_bytes": 878, + "reported_used_tokens": 842, + "working_set_bytes": 281141248, + "peak_working_set_bytes": 282058752 + }, + { + "query": "where should I put the KIMETSU_RUNS_GC=0 guard \u2014 inside the GC function or at the call site?", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDV606AJC3CB2VR63EVV", + "id": "01M1Y0D1V8EAWC1EPQEHRS8QAZ", + "kind": "memory", + "score": 0.9971211552619934, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1037.4566, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 762, + "mcp_result_bytes": 843, + "wire_bytes": 879, + "reported_used_tokens": 843, + "working_set_bytes": 281636864, + "peak_working_set_bytes": 282562560 + }, + { + "query": "git_init_boundary ProjectPaths::discover temp dir user brain isolation", + "ranked": [ + "init-project-git-boundary", + "git-worktree-brain-isolation", + "testing-temp-dirs-ci", + "kimetsu-memory-scopes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDW1WMXYA9MGV0TK68J6", + "id": "01M1Y0D2VWEFWPXTPVP0M4TWX6", + "kind": "memory", + "score": 0.9997712969779968, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + }, + { + "expansion_handle": "memory:01M1Y0CFYDVF1XT2NJ4R1CE9KK", + "id": "01M1Y0D2VW6E1GRPN56JC33YPS", + "kind": "memory", + "score": 0.9962491393089294, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root \u2014 if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + }, + { + "expansion_handle": "memory:01M1Y0CGN6SREKHFEAXY7YMS2G", + "id": "01M1Y0D2VW7R73GB9VD7ZTBQXY", + "kind": "memory", + "score": 0.9682154655456544, + "summary": "project:fact - [tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure." + }, + { + "expansion_handle": "memory:01M1Y0CHHMQS2VGXDD5KZTKWV1", + "id": "01M1Y0D2VW3V695TPW4MMBD054", + "kind": "memory", + "score": 0.3057229816913605, + "summary": "project:fact - [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available \u2014 if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 853.662, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2580, + "mcp_result_bytes": 2715, + "wire_bytes": 2751, + "reported_used_tokens": 2715, + "working_set_bytes": 281747456, + "peak_working_set_bytes": 282664960 + }, + { + "query": "my test calls init_project but it writes to the real ~/.kimetsu instead of the temp folder \u2014 why?", + "ranked": [ + "init-project-git-boundary", + "cargo-feature-unification-embeddings", + "testing-fixture-drift", + "tokio-runtime-in-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDW1WMXYA9MGV0TK68J6", + "id": "01M1Y0D3PAAFQN4MZZ3Z477RZD", + "kind": "memory", + "score": 0.9995088577270508, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + }, + { + "expansion_handle": "memory:01M1Y0CDJN5BPC6G4D9PSYG7G6", + "id": "01M1Y0D3PAY4HY9F3VMB05GMGM", + "kind": "memory", + "score": 0.7287850975990295, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1Y0CGTZS0E9FZN5W5KHQQXA", + "id": "01M1Y0D3PB235DQBJCX1N0SJ1X", + "kind": "memory", + "score": 0.6596062183380127, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + }, + { + "expansion_handle": "memory:01M1Y0CG5NP8CQF81Q3J12R0GS", + "id": "01M1Y0D3PAE7PKXEPQAR22R060", + "kind": "memory", + "score": 0.3297702968120575, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1012.4678, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2833, + "mcp_result_bytes": 2980, + "wire_bytes": 3016, + "reported_used_tokens": 2980, + "working_set_bytes": 281931776, + "peak_working_set_bytes": 282849280 + }, + { + "query": "clap command version KIMETSU_VERSION_DISPLAY cfg feature embeddings", + "ranked": [ + "clap-version-build-flavor", + "cargo-feature-unification-embeddings" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDWXMHS387P46NG7NJCM", + "id": "01M1Y0D4NZ7DE236QD317K4WE5", + "kind": "memory", + "score": 0.9996613264083862, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + }, + { + "expansion_handle": "memory:01M1Y0CDJN5BPC6G4D9PSYG7G6", + "id": "01M1Y0D4NZBW0C1PKMXK1GZ3D6", + "kind": "memory", + "score": 0.3973360061645508, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 786.9559, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1922, + "mcp_result_bytes": 2041, + "wire_bytes": 2077, + "reported_used_tokens": 2041, + "working_set_bytes": 282095616, + "peak_working_set_bytes": 283004928 + }, + { + "query": "how do I show the build flavor (lean vs embeddings) in the kimetsu --version output?", + "ranked": [ + "clap-version-build-flavor", + "cargo-feature-unification-embeddings", + "onnx-quantization-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDWXMHS387P46NG7NJCM", + "id": "01M1Y0D5EN6DCGHN0M3WM6SFEK", + "kind": "memory", + "score": 0.9978312849998474, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + }, + { + "expansion_handle": "memory:01M1Y0CDJN5BPC6G4D9PSYG7G6", + "id": "01M1Y0D5EPTKPBG3MQMKHJDPPW", + "kind": "memory", + "score": 0.8926984667778015, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1Y0CFN2M30C7MN0GWMT2VSV", + "id": "01M1Y0D5EP55XJSCA230CPNEHR", + "kind": "memory", + "score": 0.8877003192901611, + "summary": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals \u2014 cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1021.3448999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2672, + "mcp_result_bytes": 2809, + "wire_bytes": 2845, + "reported_used_tokens": 2809, + "working_set_bytes": 282415104, + "peak_working_set_bytes": 283336704 + }, + { + "query": "Harbor pyiceberg os.getcwd stale WSL2 DrvFs worker-result subprocess re-exec", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDY5Q7FP1HV3ZB904S29", + "id": "01M1Y0D6F5TS3DFJ5D19B06K4P", + "kind": "memory", + "score": 0.9998155236244202, + "summary": "project:fact - [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1011.3393, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1026, + "mcp_result_bytes": 1107, + "wire_bytes": 1143, + "reported_used_tokens": 1107, + "working_set_bytes": 282464256, + "peak_working_set_bytes": 283377664 + }, + { + "query": "why does my kbench sweep crash after the first trial with 'result.json missing' on WSL2?", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDY5Q7FP1HV3ZB904S29", + "id": "01M1Y0D7F1ND19CEVGVNYRBZ0M", + "kind": "memory", + "score": 0.998451828956604, + "summary": "project:fact - [2026-09-07] [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1058.056, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1038, + "mcp_result_bytes": 1119, + "wire_bytes": 1155, + "reported_used_tokens": 1119, + "working_set_bytes": 282484736, + "peak_working_set_bytes": 283410432 + }, + { + "query": "rusqlite VACUUM transaction WAL checkpoint wal_checkpoint TRUNCATE", + "ranked": [ + "sqlite-vacuum-wal-checkpoint", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDZHQMF7CEANBH94ZC5Z", + "id": "01M1Y0D8F9BY75EVPB6HRM9GGN", + "kind": "memory", + "score": 0.9996871948242188, + "summary": "project:fact - [tags: rust sqlite vacuum rusqlite windows] When implementing SQLite VACUUM in rusqlite: VACUUM cannot run inside a transaction. rusqlite's Connection does not hold an implicit transaction, so `conn.execute_batch(\"VACUUM;\")` works directly. After VACUUM, run `PRAGMA wal_checkpoint(TRUNCATE);` before measuring file size \u2014 on Windows the WAL file can hold significant space that isn't reflected in the main db file until the checkpoint runs." + }, + { + "expansion_handle": "memory:01M1Y0CE63YDGRR9GCFM6A405N", + "id": "01M1Y0D8F9YPYS62RR1D2MHJ37", + "kind": "memory", + "score": 0.5274003744125366, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 817.7686, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1507, + "mcp_result_bytes": 1610, + "wire_bytes": 1646, + "reported_used_tokens": 1610, + "working_set_bytes": 282497024, + "peak_working_set_bytes": 283410432 + }, + { + "query": "my SQLite VACUUM reports the file shrank but the disk usage stayed the same \u2014 Windows WAL?", + "ranked": [ + "sqlite-vacuum-wal-checkpoint", + "sqlite-wal-network-drive" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDZHQMF7CEANBH94ZC5Z", + "id": "01M1Y0D98VHE5EWCVEJNAK7MSN", + "kind": "memory", + "score": 0.9155893921852112, + "summary": "project:fact - [tags: rust sqlite vacuum rusqlite windows] When implementing SQLite VACUUM in rusqlite: VACUUM cannot run inside a transaction. rusqlite's Connection does not hold an implicit transaction, so `conn.execute_batch(\"VACUUM;\")` works directly. After VACUUM, run `PRAGMA wal_checkpoint(TRUNCATE);` before measuring file size \u2014 on Windows the WAL file can hold significant space that isn't reflected in the main db file until the checkpoint runs." + }, + { + "expansion_handle": "memory:01M1Y0CE84FR94JFPZ2CTRK5KY", + "id": "01M1Y0D98V4P0N87DPVB34XF67", + "kind": "memory", + "score": 0.902395486831665, + "summary": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1077.2008, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1357, + "mcp_result_bytes": 1460, + "wire_bytes": 1496, + "reported_used_tokens": 1460, + "working_set_bytes": 282669056, + "peak_working_set_bytes": 283590656 + }, + { + "query": "add_memory import dedup seen_ids snapshot pre-existing active memory IDs", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE0BVW8K017DXQ2P7M8E", + "id": "01M1Y0DAAJEKKFCA7MGYXEDTS3", + "kind": "memory", + "score": 0.9999133348464966, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount \u2014 both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 907.7742, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 966, + "mcp_result_bytes": 1047, + "wire_bytes": 1083, + "reported_used_tokens": 1047, + "working_set_bytes": 282882048, + "peak_working_set_bytes": 283795456 + }, + { + "query": "brain import re-imports the same JSON file but the deduplication counter is wrong \u2014 why?", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE0BVW8K017DXQ2P7M8E", + "id": "01M1Y0DB8HZWN7T2JWHYTSSGNR", + "kind": "memory", + "score": 0.9254016876220704, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount \u2014 both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1019.945, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 965, + "mcp_result_bytes": 1046, + "wire_bytes": 1082, + "reported_used_tokens": 1046, + "working_set_bytes": 283058176, + "peak_working_set_bytes": 283979776 + }, + { + "query": "toml::from_str Value parse document unexpected content str.parse", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE1F83ZZ1GGZDPZBKQMF", + "id": "01M1Y0DC7MZ20J9R7DKMFEP28K", + "kind": "memory", + "score": 0.9991866946220398, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 879.7893, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 734, + "mcp_result_bytes": 815, + "wire_bytes": 851, + "reported_used_tokens": 815, + "working_set_bytes": 283099136, + "peak_working_set_bytes": 284016640 + }, + { + "query": "how do I parse a TOML configuration file into a toml::Value in toml 0.9?", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE1F83ZZ1GGZDPZBKQMF", + "id": "01M1Y0DD2SM1DA3X3ZSTV9JCH7", + "kind": "memory", + "score": 0.9992641806602478, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 997.8065, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 733, + "mcp_result_bytes": 814, + "wire_bytes": 850, + "reported_used_tokens": 814, + "working_set_bytes": 283111424, + "peak_working_set_bytes": 284028928 + }, + { + "query": "CIM CreationDate DMTF WMI ps etimes started_at assess_mcp_skew", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE2KCEZ4A9F2TGGTDYZY", + "id": "01M1Y0DE1J612YQEW7WCZ2N26H", + "kind": "memory", + "score": 0.9957948923110962, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 772.7819999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 924, + "mcp_result_bytes": 1013, + "wire_bytes": 1049, + "reported_used_tokens": 1013, + "working_set_bytes": 283140096, + "peak_working_set_bytes": 284049408 + }, + { + "query": "how do I read a process start time on both Windows and Linux in pure Rust?", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE2KCEZ4A9F2TGGTDYZY", + "id": "01M1Y0DETTT2CVKEXKVT1YZZXQ", + "kind": "memory", + "score": 0.99687659740448, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1018.7578, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 921, + "mcp_result_bytes": 1010, + "wire_bytes": 1046, + "reported_used_tokens": 1010, + "working_set_bytes": 283447296, + "peak_working_set_bytes": 284377088 + }, + { + "query": "processes_locking_target decide_preflight_action BufRead Write update.rs", + "ranked": [ + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE3VN854570296AQJ8B8", + "id": "01M1Y0DFTMTXS9AWBGGRZEHYRT", + "kind": "memory", + "score": 0.9995336532592772, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 870.0039, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1133, + "mcp_result_bytes": 1214, + "wire_bytes": 1250, + "reported_used_tokens": 1214, + "working_set_bytes": 283475968, + "peak_working_set_bytes": 284397568 + }, + { + "query": "how should I reuse the existing process enumerator in the update preflight check to avoid a second PowerShell query?", + "ranked": [ + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE3VN854570296AQJ8B8", + "id": "01M1Y0DGMTBQT39PZ6ZP7V9ZRR", + "kind": "memory", + "score": 0.9973384737968444, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1006.9813, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1132, + "mcp_result_bytes": 1213, + "wire_bytes": 1249, + "reported_used_tokens": 1213, + "working_set_bytes": 283926528, + "peak_working_set_bytes": 284848128 + }, + { + "query": "cfg_attr windows allow dead_code parse_unix_ps cross-platform tests", + "ranked": [ + "cfg-cross-platform-dead-code", + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE53127TY01EC3730MH1", + "id": "01M1Y0DHMBH8F2M6HT83FNSR7C", + "kind": "memory", + "score": 0.9999476671218872, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + }, + { + "expansion_handle": "memory:01M1Y0CE2KCEZ4A9F2TGGTDYZY", + "id": "01M1Y0DHMBJT8846ACDPFS8VZY", + "kind": "memory", + "score": 0.9764312505722046, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 788.6767, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1518, + "mcp_result_bytes": 1625, + "wire_bytes": 1661, + "reported_used_tokens": 1625, + "working_set_bytes": 283942912, + "peak_working_set_bytes": 284860416 + }, + { + "query": "how do I keep a function that is only called on Unix from triggering dead_code warnings on Windows?", + "ranked": [ + "cfg-cross-platform-dead-code" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE53127TY01EC3730MH1", + "id": "01M1Y0DJD1HXD976S6WPWCBM2X", + "kind": "memory", + "score": 0.9988092184066772, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 989.4556, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 939, + "reported_used_tokens": 903, + "working_set_bytes": 284200960, + "peak_working_set_bytes": 285118464 + }, + { + "query": "deadlocking a Rust mutex in integration tests", + "ranked": [ + "mutex-deadlock-user-brain-disabled", + "testing-serial-vs-parallel", + "kimetsu-query-stemming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDDKV46DFCQB5VK0TWX5", + "id": "01M1Y0DKBXJ2C1PM8XMZZBT6X5", + "kind": "memory", + "score": 0.9997490048408508, + "summary": "project:fact - [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure \u2014 `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + }, + { + "expansion_handle": "memory:01M1Y0CGRCR5ZFR10QAFCTQWHR", + "id": "01M1Y0DKBX36W96TMKHRAMCTP2", + "kind": "memory", + "score": 0.9057517647743224, + "summary": "project:fact - [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`)." + }, + { + "expansion_handle": "memory:01M1Y0CHR9RRWWX5FQ2Z9H6B27", + "id": "01M1Y0DKBYVYX3N3R9YJBBW2C1", + "kind": "memory", + "score": 0.4889622032642365, + "summary": "project:fact - [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 945.5635, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1930, + "mcp_result_bytes": 2063, + "wire_bytes": 2099, + "reported_used_tokens": 2063, + "working_set_bytes": 284217344, + "peak_working_set_bytes": 285134848 + }, + { + "query": "benchmarking retrieval quality across embedders", + "ranked": [ + "kimetsu-bench-remote-embedder-singleton", + "onnx-quantization-drift", + "cargo-feature-unification-embeddings" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHTA3AQK16G6EE1QCJKZ", + "id": "01M1Y0DM9PK9ST4HHFNG3ZFNP6", + "kind": "memory", + "score": 0.988014280796051, + "summary": "project:fact - [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval." + }, + { + "expansion_handle": "memory:01M1Y0CFN2M30C7MN0GWMT2VSV", + "id": "01M1Y0DM9PT5RMH0HYDBM0GADE", + "kind": "memory", + "score": 0.985597550868988, + "summary": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals \u2014 cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + }, + { + "expansion_handle": "memory:01M1Y0CDJN5BPC6G4D9PSYG7G6", + "id": "01M1Y0DM9QW0D5ENBTQ9BA68W7", + "kind": "memory", + "score": 0.5341982841491699, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 806.3176, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2537, + "mcp_result_bytes": 2658, + "wire_bytes": 2694, + "reported_used_tokens": 2658, + "working_set_bytes": 284217344, + "peak_working_set_bytes": 285134848 + }, + { + "query": "process memory working set RSS peak measurement Windows", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 985.7869999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 284487680, + "peak_working_set_bytes": 285384704 + }, + { + "query": "cloning a git repository server-side into a managed checkout", + "ranked": [ + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDERH0MV40CSBCQDEQYA", + "id": "01M1Y0DP1T4TTQJRQSZTS93J0E", + "kind": "memory", + "score": 0.9466677904129028, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 848.966, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1261, + "mcp_result_bytes": 1342, + "wire_bytes": 1378, + "reported_used_tokens": 1342, + "working_set_bytes": 284639232, + "peak_working_set_bytes": 285548544 + }, + { + "query": "SigV4 signing HTTP requests in Rust", + "ranked": [ + "aws-presigned-urls", + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CH6AWWAV734TFW14SHW0", + "id": "01M1Y0DPW97FZV1J2Q6J8B90E9", + "kind": "memory", + "score": 0.9992632269859314, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + }, + { + "expansion_handle": "memory:01M1Y0CDSEBRPP3R78RVYDDGZA", + "id": "01M1Y0DPW9AYNNAEDC2PB5TDH3", + "kind": "memory", + "score": 0.9991399049758912, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1Y0CDKZJ260T9MFP5RRHHQY", + "id": "01M1Y0DPW97N3QKFJH7XHGZ2RC", + "kind": "memory", + "score": 0.9803794622421264, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 0.5, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 918.5545000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2840, + "mcp_result_bytes": 2985, + "wire_bytes": 3021, + "reported_used_tokens": 2985, + "working_set_bytes": 284852224, + "peak_working_set_bytes": 285753344 + }, + { + "query": "cargo test --workspace feature flag changes broke my unit tests", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-dev-dep-leak", + "ci-flaky-quarantine" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDJN5BPC6G4D9PSYG7G6", + "id": "01M1Y0DQT36G27QVYPGTKH45YF", + "kind": "memory", + "score": 0.997899889945984, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1Y0CEHKWTJ8ZMXANWDJAMCD", + "id": "01M1Y0DQT3VZ88PGCCTCDSHVE1", + "kind": "memory", + "score": 0.9901249408721924, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + }, + { + "expansion_handle": "memory:01M1Y0CHEJR8TEQHZ0MBKNP52Z", + "id": "01M1Y0DQT39MT42KJQX9747CNG", + "kind": "memory", + "score": 0.835382342338562, + "summary": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal \u2014 a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 820.0164, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2383, + "mcp_result_bytes": 2504, + "wire_bytes": 2540, + "reported_used_tokens": 2504, + "working_set_bytes": 284860416, + "peak_working_set_bytes": 285777920 + }, + { + "query": "how do I make pasta carbonara?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 830.4524, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 284913664, + "peak_working_set_bytes": 285831168 + }, + { + "query": "what is the offside rule in football?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 1091.7950999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 285040640, + "peak_working_set_bytes": 285954048 + }, + { + "query": "best way to train for a half marathon", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 1100.6146, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 285044736, + "peak_working_set_bytes": 285970432 + }, + { + "query": "my test passes when I run it alone but fails under cargo test --workspace", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDJN5BPC6G4D9PSYG7G6", + "id": "01M1Y0DVH288951P7QTVK3SBF1", + "kind": "memory", + "score": 0.9907942414283752, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1Y0CEHKWTJ8ZMXANWDJAMCD", + "id": "01M1Y0DVH2KNZHAQ8MFMZMPS3Q", + "kind": "memory", + "score": 0.986136794090271, + "summary": "project:fact - [2026-09-07] [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1037.2329, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1863, + "mcp_result_bytes": 1966, + "wire_bytes": 2002, + "reported_used_tokens": 1966, + "working_set_bytes": 285491200, + "peak_working_set_bytes": 286412800 + }, + { + "query": "all the project tests started hanging forever after I added my new test", + "ranked": [ + "cargo-feature-unification-embeddings", + "tokio-runtime-in-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDJN5BPC6G4D9PSYG7G6", + "id": "01M1Y0DWHQ62H6P95BKTTTJHBP", + "kind": "memory", + "score": 0.774284839630127, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1Y0CG5NP8CQF81Q3J12R0GS", + "id": "01M1Y0DWHQ9RGHM9D0DNTMFFVM", + "kind": "memory", + "score": 0.33030807971954346, + "summary": "project:fact - [2026-09-07] [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 961.9590999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1763, + "mcp_result_bytes": 1874, + "wire_bytes": 1910, + "reported_used_tokens": 1874, + "working_set_bytes": 285491200, + "peak_working_set_bytes": 286412800 + }, + { + "query": "my integration test silently wrote memories into my real home brain instead of the temp workspace", + "ranked": [ + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDW1WMXYA9MGV0TK68J6", + "id": "01M1Y0DXFHAW2K3QSY7075SSZD", + "kind": "memory", + "score": 0.9922831654548644, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 972.419, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 780, + "mcp_result_bytes": 861, + "wire_bytes": 897, + "reported_used_tokens": 861, + "working_set_bytes": 285499392, + "peak_working_set_bytes": 286420992 + }, + { + "query": "where should the env-var opt-out check live for a cleanup feature triggered from a hot code path", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDV606AJC3CB2VR63EVV", + "id": "01M1Y0DYECAJ2TA320T87AAGTY", + "kind": "memory", + "score": 0.9952055215835572, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1034.8117000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 761, + "mcp_result_bytes": 842, + "wire_bytes": 878, + "reported_used_tokens": 842, + "working_set_bytes": 285499392, + "peak_working_set_bytes": 286420992 + }, + { + "query": "the brain database file stays huge on Windows even after deleting most rows", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1007.3049000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 285519872, + "peak_working_set_bytes": 286445568 + }, + { + "query": "re-importing the same exported memories file counts them as new instead of deduplicated", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE0BVW8K017DXQ2P7M8E", + "id": "01M1Y0E0EQ183NWF18MNJ3GPH0", + "kind": "memory", + "score": 0.9878425598144532, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount \u2014 both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1068.6028999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 965, + "mcp_result_bytes": 1046, + "wire_bytes": 1082, + "reported_used_tokens": 1046, + "working_set_bytes": 285519872, + "peak_working_set_bytes": 286445568 + }, + { + "query": "a helper function only called on Unix at runtime fails the dead-code lint on the Windows build", + "ranked": [ + "cfg-cross-platform-dead-code", + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE53127TY01EC3730MH1", + "id": "01M1Y0E1F8KJKSWB0XJYN3V39W", + "kind": "memory", + "score": 0.9971064925193788, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + }, + { + "expansion_handle": "memory:01M1Y0CE3VN854570296AQJ8B8", + "id": "01M1Y0E1F8EYBH6V453FXNZV3V", + "kind": "memory", + "score": 0.427912950515747, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 970.3475999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1728, + "mcp_result_bytes": 1827, + "wire_bytes": 1863, + "reported_used_tokens": 1827, + "working_set_bytes": 285974528, + "peak_working_set_bytes": 286900224 + }, + { + "query": "the second Terminal-Bench trial always crashes even though the first one passes", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDY5Q7FP1HV3ZB904S29", + "id": "01M1Y0E2DN3ZXN8X2B7NRN2KJ7", + "kind": "memory", + "score": 0.9963042736053468, + "summary": "project:fact - [2026-09-07] [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1018.6896999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1038, + "mcp_result_bytes": 1119, + "wire_bytes": 1155, + "reported_used_tokens": 1119, + "working_set_bytes": 286003200, + "peak_working_set_bytes": 286920704 + }, + { + "query": "how does doctor tell a running MCP server process is older than the kimetsu binary on disk", + "ranked": [ + "kimetsu-daemon-lifecycle", + "process-start-time-cross-platform", + "mcp-env-propagation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHFNNPJ5W1HT08QTSPX7", + "id": "01M1Y0E3DHFHS9D5SFZ2CEVR9W", + "kind": "memory", + "score": 0.9985345602035522, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1Y0CE2KCEZ4A9F2TGGTDYZY", + "id": "01M1Y0E3DH5FK3FB3D7P3RP8ZE", + "kind": "memory", + "score": 0.9438157677650452, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + }, + { + "expansion_handle": "memory:01M1Y0CGY1YEEKFJHX4JCZYHY6", + "id": "01M1Y0E3DHH12MTDY16PJ9W82E", + "kind": "memory", + "score": 0.33611738681793213, + "summary": "project:fact - [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment \u2014 changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 0.5, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1054.404, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1936, + "mcp_result_bytes": 2061, + "wire_bytes": 2097, + "reported_used_tokens": 2061, + "working_set_bytes": 286007296, + "peak_working_set_bytes": 286928896 + }, + { + "query": "the self-update preflight needs the list of running kimetsu processes without re-running the OS query", + "ranked": [ + "windows-update-process-locking", + "kimetsu-daemon-lifecycle" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE3VN854570296AQJ8B8", + "id": "01M1Y0E4EMDXN756B9WMDBD22S", + "kind": "memory", + "score": 0.9972410202026368, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + }, + { + "expansion_handle": "memory:01M1Y0CHFNNPJ5W1HT08QTSPX7", + "id": "01M1Y0E4EM7KX11PW8ND1QR36K", + "kind": "memory", + "score": 0.8902595043182373, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 956.3525000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1658, + "mcp_result_bytes": 1757, + "wire_bytes": 1793, + "reported_used_tokens": 1757, + "working_set_bytes": 286007296, + "peak_working_set_bytes": 286928896 + }, + { + "query": "parsing the WMI DMTF CreationDate timestamp into epoch seconds without extra crates", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE2KCEZ4A9F2TGGTDYZY", + "id": "01M1Y0E5CMMWWQYJ29ZW7APRA9", + "kind": "memory", + "score": 0.9258026480674744, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1038.5942, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 924, + "mcp_result_bytes": 1013, + "wire_bytes": 1049, + "reported_used_tokens": 1013, + "working_set_bytes": 286007296, + "peak_working_set_bytes": 286928896 + }, + { + "query": "calling Bedrock InvokeModel from blocking reqwest without the aws sdk", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking", + "aws-region-resolution", + "aws-retry-throttling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDKZJ260T9MFP5RRHHQY", + "id": "01M1Y0E6CZY1X879JY6V9N38JW", + "kind": "memory", + "score": 0.9991798996925354, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1Y0CDSEBRPP3R78RVYDDGZA", + "id": "01M1Y0E6CZ9MKM4657HS57R62K", + "kind": "memory", + "score": 0.999082326889038, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1Y0CH4021WNS0JP2HVKJDXQ", + "id": "01M1Y0E6CZTQBZDPWZ0VX9K36F", + "kind": "memory", + "score": 0.8391201496124268, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1Y0CH5512SPEE30NNSG0SKK", + "id": "01M1Y0E6CZ9221FD20BQKB2T08", + "kind": "memory", + "score": 0.4906356632709503, + "summary": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with \u00b125% jitter." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 998.2014, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3330, + "mcp_result_bytes": 3509, + "wire_bytes": 3545, + "reported_used_tokens": 3509, + "working_set_bytes": 286007296, + "peak_working_set_bytes": 286928896 + }, + { + "query": "how do I rotate the encryption key protecting the kimetsu brain database", + "ranked": [ + "kimetsu-eval-fixture-shape" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHVB50Y5BXTDMZRNKFGR", + "id": "01M1Y0E7CBH0STYAZ53HGHCQYB", + "kind": "memory", + "score": 0.8046634197235107, + "summary": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` \u2014 a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases)." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 1012.8033, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 817, + "mcp_result_bytes": 942, + "wire_bytes": 978, + "reported_used_tokens": 942, + "working_set_bytes": 286007296, + "peak_working_set_bytes": 286928896 + }, + { + "query": "which tokio runtime worker-thread settings does the kimetsu MCP server use", + "ranked": [ + "tokio-blocking-in-async", + "tokio-runtime-in-tests", + "mcp-stdout-protocol" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CG4GXEDWCNR553B345ZY", + "id": "01M1Y0E8BVCW4EJ0A3XWYPXZTG", + "kind": "memory", + "score": 0.9973159432411194, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + }, + { + "expansion_handle": "memory:01M1Y0CG5NP8CQF81Q3J12R0GS", + "id": "01M1Y0E8BV7BCNX6367TS3JCQN", + "kind": "memory", + "score": 0.8583173155784607, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + }, + { + "expansion_handle": "memory:01M1Y0CGVVT9QFVZB1R92FVJYX", + "id": "01M1Y0E8BVFCS9H76KNT186YPB", + "kind": "memory", + "score": 0.8141786456108093, + "summary": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 1055.815, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1847, + "mcp_result_bytes": 1972, + "wire_bytes": 2008, + "reported_used_tokens": 1972, + "working_set_bytes": 287473664, + "peak_working_set_bytes": 288391168 + }, + { + "query": "how does kimetsu sync memories between two machines over the network", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 1042.1079, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 287506432, + "peak_working_set_bytes": 288423936 + }, + { + "query": "recovering a corrupted usearch ANN index after a power loss", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 912.1238000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 287608832, + "peak_working_set_bytes": 288526336 + }, + { + "query": "what postgres schema should I use to store kimetsu memories", + "ranked": [ + "kimetsu-memory-scopes", + "testing-fixture-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHHMQS2VGXDD5KZTKWV1", + "id": "01M1Y0EB9ZNZF9VM3Q5CT8J01Q", + "kind": "memory", + "score": 0.9890244603157043, + "summary": "project:fact - [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available \u2014 if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope." + }, + { + "expansion_handle": "memory:01M1Y0CGTZS0E9FZN5W5KHQQXA", + "id": "01M1Y0EB9Z1H4TD2Y703EF5XAF", + "kind": "memory", + "score": 0.8922504782676697, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 988.5531, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1389, + "mcp_result_bytes": 1488, + "wire_bytes": 1524, + "reported_used_tokens": 1488, + "working_set_bytes": 287924224, + "peak_working_set_bytes": 288829440 + }, + { + "query": "the whole CI job just froze forever with no failure output after my latest test PR", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1006.6881000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 287936512, + "peak_working_set_bytes": 288894976 + }, + { + "query": "running the test suite left junk state in my home directory", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1045.4680999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 287936512, + "peak_working_set_bytes": 288894976 + }, + { + "query": "I deleted a bunch of old rows but the file on disk is still the same size", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1001.1015000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 287940608, + "peak_working_set_bytes": 288894976 + }, + { + "query": "adding one new crate quietly changed how the whole workspace builds", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-lockfile-drift", + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDJN5BPC6G4D9PSYG7G6", + "id": "01M1Y0EF880EV4WPEJTPA6F29A", + "kind": "memory", + "score": 0.9941080808639526, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1Y0CEFG82GG4CW59T7HTY9Z", + "id": "01M1Y0EF88GDFA6D7HGF6XYXGB", + "kind": "memory", + "score": 0.9717232584953308, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this \u2014 it errors on any lockfile diff." + }, + { + "expansion_handle": "memory:01M1Y0CEHKWTJ8ZMXANWDJAMCD", + "id": "01M1Y0EF880CPJ868MP49PK53E", + "kind": "memory", + "score": 0.9183088541030884, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1029.3306, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2374, + "mcp_result_bytes": 2495, + "wire_bytes": 2531, + "reported_used_tokens": 2495, + "working_set_bytes": 288346112, + "peak_working_set_bytes": 289271808 + }, + { + "query": "we cannot pull an async runtime into the agent just to talk to AWS", + "ranked": [ + "tokio-blocking-in-async", + "tokio-runtime-in-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CG4GXEDWCNR553B345ZY", + "id": "01M1Y0EG8RTXQ7A41780DS162G", + "kind": "memory", + "score": 0.7520647644996643, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + }, + { + "expansion_handle": "memory:01M1Y0CG5NP8CQF81Q3J12R0GS", + "id": "01M1Y0EG8R31Y1RXJ4G2M14H48", + "kind": "memory", + "score": 0.7233642935752869, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1045.6879999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1369, + "mcp_result_bytes": 1476, + "wire_bytes": 1512, + "reported_used_tokens": 1476, + "working_set_bytes": 288346112, + "peak_working_set_bytes": 289271808 + }, + { + "query": "users should be able to tell which build variant they installed from the version output", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1028.4616, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288346112, + "peak_working_set_bytes": 289271808 + }, + { + "query": "what gotchas should I expect writing process-inspection code that works on both Windows and Unix?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 959.8008, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288350208, + "peak_working_set_bytes": 289275904 + }, + { + "query": "why might tests behave differently on my machine than in the full CI run?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 991.1095, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288727040, + "peak_working_set_bytes": 289648640 + }, + { + "query": "what do I need to know before wiring kimetsu into a brand new host agent?", + "ranked": [ + "bridge-target-enum-seams", + "kimetsu-daemon-lifecycle", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDNPXTVTYW8S244N1GCB", + "id": "01M1Y0EM85D3W91YD12FBR5Q4N", + "kind": "memory", + "score": 0.9741999506950378, + "summary": "project:fact - [2026-09-07] [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + }, + { + "expansion_handle": "memory:01M1Y0CHFNNPJ5W1HT08QTSPX7", + "id": "01M1Y0EM8522G45VP6PE5WD40B", + "kind": "memory", + "score": 0.9637662768363952, + "summary": "project:fact - [2026-09-07] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1Y0CDGSHENNVZQKGWMDBNDR", + "id": "01M1Y0EM85VH5WH99KS6N8XJD0", + "kind": "memory", + "score": 0.4149944484233856, + "summary": "project:fact - [2026-09-07] [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 0.6666666666666666, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1124.4542, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2390, + "mcp_result_bytes": 2555, + "wire_bytes": 2591, + "reported_used_tokens": 2555, + "working_set_bytes": 288731136, + "peak_working_set_bytes": 289652736 + }, + { + "query": "tell me everything relevant to running kimetsu against AWS", + "ranked": [ + "kimetsu-mrr-metric", + "aws-credentials-chain", + "cargo-feature-unification-embeddings", + "kimetsu-eval-fixture-shape" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHWG3T7N3ZZ8F2Z1EQHR", + "id": "01M1Y0EN9QMZRSDST2BJC3DXNT", + "kind": "memory", + "score": 0.984548270702362, + "summary": "project:fact - [tags: kimetsu bench mrr recall metrics evaluation] kimetsu bench reports MRR (Mean Reciprocal Rank) and Recall@K. MRR is 1/rank_of_first_relevant_result, averaged across cases; it penalizes models that rank the correct answer 2nd or 3rd. Recall@K is the fraction of cases where at least one relevant answer appears in the top K." + }, + { + "expansion_handle": "memory:01M1Y0CH2MJMDSTCRQ0SR4KPC5", + "id": "01M1Y0EN9Q8YJ03F5FDRK10TBG", + "kind": "memory", + "score": 0.9737622141838074, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + }, + { + "expansion_handle": "memory:01M1Y0CDJN5BPC6G4D9PSYG7G6", + "id": "01M1Y0EN9Q6A02W9FQTNY5A40A", + "kind": "memory", + "score": 0.9726329445838928, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1Y0CHVB50Y5BXTDMZRNKFGR", + "id": "01M1Y0EN9Q2RGKN7DXM8CB7FJD", + "kind": "memory", + "score": 0.9641559720039368, + "summary": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` \u2014 a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases)." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1025.5521999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2883, + "mcp_result_bytes": 3066, + "wire_bytes": 3102, + "reported_used_tokens": 3066, + "working_set_bytes": 288763904, + "peak_working_set_bytes": 289677312 + }, + { + "query": "ingesting a cloned repo when the brain lives under a different root", + "ranked": [ + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDERH0MV40CSBCQDEQYA", + "id": "01M1Y0EP9K1RE5SV3GD0YY7FBS", + "kind": "memory", + "score": 0.9995300769805908, + "summary": "project:fact - [2026-09-07] [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 963.9586999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1274, + "mcp_result_bytes": 1355, + "wire_bytes": 1391, + "reported_used_tokens": 1355, + "working_set_bytes": 288763904, + "peak_working_set_bytes": 289677312 + }, + { + "query": "streamable-http transport entry for openclaw.json with a bearer token", + "ranked": [ + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDGSHENNVZQKGWMDBNDR", + "id": "01M1Y0EQ81SATC515Q8H4CDYEY", + "kind": "memory", + "score": 0.9921918511390686, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 995.9834, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 996, + "mcp_result_bytes": 1125, + "wire_bytes": 1161, + "reported_used_tokens": 1125, + "working_set_bytes": 288763904, + "peak_working_set_bytes": 289685504 + }, + { + "query": "serializing ingests with a tokio mutex to avoid checkout races", + "ranked": [ + "remote-ingest-split-roots", + "testing-serial-vs-parallel", + "tokio-select-cancellation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDERH0MV40CSBCQDEQYA", + "id": "01M1Y0ER7CCPAKQVKCQQ3YSVCG", + "kind": "memory", + "score": 0.9795480966567992, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1Y0CGRCR5ZFR10QAFCTQWHR", + "id": "01M1Y0ER7C65RPR64N1PY7H9ZT", + "kind": "memory", + "score": 0.9425267577171326, + "summary": "project:fact - [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`)." + }, + { + "expansion_handle": "memory:01M1Y0CG6WN1WWXEWWMMGQZ45N", + "id": "01M1Y0ER7CEM3WT8NNMTFVZ306", + "kind": "memory", + "score": 0.5619664192199707, + "summary": "project:fact - [tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1092.3582999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2376, + "mcp_result_bytes": 2493, + "wire_bytes": 2529, + "reported_used_tokens": 2493, + "working_set_bytes": 288763904, + "peak_working_set_bytes": 289689600 + }, + { + "query": "percent-encoding the colon in the bedrock model id for the invoke URL", + "ranked": [ + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDKZJ260T9MFP5RRHHQY", + "id": "01M1Y0ES9R8J4JEECYP5QZX3PA", + "kind": "memory", + "score": 0.8341025710105896, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1005.7819999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1204, + "mcp_result_bytes": 1293, + "wire_bytes": 1329, + "reported_used_tokens": 1293, + "working_set_bytes": 288763904, + "peak_working_set_bytes": 289689600 + }, + { + "query": "deduplicating re-imported memories against pre-existing ids", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE0BVW8K017DXQ2P7M8E", + "id": "01M1Y0ET8JZ1G0R5QHQ6V1DFB2", + "kind": "memory", + "score": 0.9991393089294434, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount \u2014 both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1061.6083, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 966, + "mcp_result_bytes": 1047, + "wire_bytes": 1083, + "reported_used_tokens": 1047, + "working_set_bytes": 288763904, + "peak_working_set_bytes": 289689600 + }, + { + "query": "parsing DMTF datetimes", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE2KCEZ4A9F2TGGTDYZY", + "id": "01M1Y0EVA551X5QT05Y34BBJQQ", + "kind": "memory", + "score": 0.9934942126274108, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 782.3856999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 924, + "mcp_result_bytes": 1013, + "wire_bytes": 1049, + "reported_used_tokens": 1013, + "working_set_bytes": 288763904, + "peak_working_set_bytes": 289689600 + }, + { + "query": "how should install derive a stable identifier from the git remote URL?", + "ranked": [ + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDGSHENNVZQKGWMDBNDR", + "id": "01M1Y0EW26TARBSS8Q9NW03A18", + "kind": "memory", + "score": 0.98285174369812, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 971.8706999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 995, + "mcp_result_bytes": 1124, + "wire_bytes": 1160, + "reported_used_tokens": 1124, + "working_set_bytes": 288768000, + "peak_working_set_bytes": 289689600 + }, + { + "query": "the secret token must not end up written into the host config file", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1072.4509, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288772096, + "peak_working_set_bytes": 289693696 + }, + { + "query": "keep the cleanup logic unit-testable without touching environment variables", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1019.6959999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288772096, + "peak_working_set_bytes": 289693696 + }, + { + "query": "how do we stop the server from cloning arbitrary repos clients request?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1011.4857, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288776192, + "peak_working_set_bytes": 289697792 + }, + { + "query": "make sure a wrong guess about a host plugin API never breaks that host", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDPTB91KV3WJ6A8WXR9N", + "id": "01M1Y0F01ZFR9DPJ4VXBAT6QRE", + "kind": "memory", + "score": 0.928434193134308, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 856.2956, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 803, + "mcp_result_bytes": 892, + "wire_bytes": 928, + "reported_used_tokens": 892, + "working_set_bytes": 288776192, + "peak_working_set_bytes": 289701888 + }, + { + "query": "which wire-format trick lets us reuse the existing Anthropic request builder for AWS?", + "ranked": [ + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDKZJ260T9MFP5RRHHQY", + "id": "01M1Y0F0WEWN0ADWYXDGAHGXVF", + "kind": "memory", + "score": 0.9748817682266236, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1038.5819000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1203, + "mcp_result_bytes": 1292, + "wire_bytes": 1328, + "reported_used_tokens": 1292, + "working_set_bytes": 288976896, + "peak_working_set_bytes": 289898496 + }, + { + "query": "the self-update froze because something was still holding the executable", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1000.2845, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288976896, + "peak_working_set_bytes": 289898496 + }, + { + "query": "our notes about the extension API turned out wrong once we read the actual repo", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1011.5813, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288976896, + "peak_working_set_bytes": 289898496 + }, + { + "query": "half the benchmark trials die right after the first one finishes", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1018.7479, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288976896, + "peak_working_set_bytes": 289898496 + }, + { + "query": "I need this parser visible to tests on every OS even though only one OS calls it", + "ranked": [ + "cfg-cross-platform-dead-code" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE53127TY01EC3730MH1", + "id": "01M1Y0F4VQC9WRC9FG9RXFWDG3", + "kind": "memory", + "score": 0.36490198969841, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 993.4152, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 939, + "reported_used_tokens": 903, + "working_set_bytes": 288976896, + "peak_working_set_bytes": 289898496 + }, + { + "query": "the config file content refuses to parse even though the TOML looks valid", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE1F83ZZ1GGZDPZBKQMF", + "id": "01M1Y0F5WZGWZ43Y5CPB7MN9J5", + "kind": "memory", + "score": 0.6614054441452026, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1106.2243999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 733, + "mcp_result_bytes": 814, + "wire_bytes": 850, + "reported_used_tokens": 814, + "working_set_bytes": 288976896, + "peak_working_set_bytes": 289898496 + }, + { + "query": "the remote server must refresh its checkout before answering file queries", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1056.3914000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288976896, + "peak_working_set_bytes": 289898496 + }, + { + "query": "tests must not climb to a parent git repository when resolving project paths", + "ranked": [ + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDW1WMXYA9MGV0TK68J6", + "id": "01M1Y0F7Y76HKYH87CVDADJ1SV", + "kind": "memory", + "score": 0.9839988350868224, + "summary": "project:fact - [2026-09-07] [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 999.3949, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 794, + "mcp_result_bytes": 875, + "wire_bytes": 911, + "reported_used_tokens": 875, + "working_set_bytes": 288976896, + "peak_working_set_bytes": 289898496 + }, + { + "query": "how do I test request signing deterministically when timestamps change every run?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1005.4948999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 289042432, + "peak_working_set_bytes": 289959936 + }, + { + "query": "adding a new variant to the host target enum - which places will I forget to update?", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDNPXTVTYW8S244N1GCB", + "id": "01M1Y0F9X6YBVZ1A6T9N66Q4VZ", + "kind": "memory", + "score": 0.885076105594635, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1089.1136, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1058, + "mcp_result_bytes": 1139, + "wire_bytes": 1175, + "reported_used_tokens": 1139, + "working_set_bytes": 289050624, + "peak_working_set_bytes": 289968128 + }, + { + "query": "how do I enable GPU acceleration for kimetsu embedding inference", + "ranked": [ + "mcp-tool-timeouts", + "kimetsu-proactive-hooks" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGWYZ09QBQFPX4FEVE70", + "id": "01M1Y0FB06KNNB1JJNJWPEXWQH", + "kind": "memory", + "score": 0.9826309084892272, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + }, + { + "expansion_handle": "memory:01M1Y0CHKTX9JMQPFAQV1QS487", + "id": "01M1Y0FB06EGZW7JSJ62N1D3FR", + "kind": "memory", + "score": 0.8807981610298157, + "summary": "project:fact - [tags: kimetsu proactive hooks context injection] kimetsu's proactive context injection runs before each agent turn (pre-turn hook) and injects relevant memories into the system prompt prefix. The hook invocation adds latency to the first token: embedding inference + vector search + reranking + context formatting. On a cold start, this can be 1-3 seconds." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 1053.7009, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1377, + "mcp_result_bytes": 1476, + "wire_bytes": 1512, + "reported_used_tokens": 1476, + "working_set_bytes": 289050624, + "peak_working_set_bytes": 289968128 + }, + { + "query": "how do I throttle kimetsu API spend per month", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 933.7639, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 289050624, + "peak_working_set_bytes": 289972224 + }, + { + "query": "can the kimetsu brain database be stored in S3 instead of on disk", + "ranked": [ + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CH6AWWAV734TFW14SHW0", + "id": "01M1Y0FCXGK395B7ZSMMVPS3HX", + "kind": "memory", + "score": 0.38596054911613464, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 934.3241, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 875, + "mcp_result_bytes": 956, + "wire_bytes": 992, + "reported_used_tokens": 956, + "working_set_bytes": 289050624, + "peak_working_set_bytes": 289972224 + }, + { + "query": "how do I plug a custom tokenizer into the FTS index", + "ranked": [ + "sqlite-fts5-tokenizer" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE96YPFCCNT1Z92GH3C5", + "id": "01M1Y0FDTWN599XEGBHY3AS6B0", + "kind": "memory", + "score": 0.9691632390022278, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 1047.9415999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 671, + "mcp_result_bytes": 756, + "wire_bytes": 792, + "reported_used_tokens": 756, + "working_set_bytes": 289054720, + "peak_working_set_bytes": 289972224 + }, + { + "query": "what should I check when kimetsu behaves differently on Windows than on Linux?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1004.2382000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 289128448, + "peak_working_set_bytes": 290045952 + }, + { + "query": "what are the moving parts of the kimetsu remote deployment story?", + "ranked": [ + "kimetsu-write-tools-gate", + "ci-secrets-masking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHMVHR9GRRHBAB5NZNDN", + "id": "01M1Y0FFTP5VM3VJWD35D2V9Z9", + "kind": "memory", + "score": 0.9729357361793518, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level \u2014 disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1Y0CHCK07ASK7V6B3J3GTZF", + "id": "01M1Y0FFTP3C24QPDN3Y8GT2TY", + "kind": "memory", + "score": 0.8412115573883057, + "summary": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output \u2014 but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 996.9313, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1410, + "mcp_result_bytes": 1509, + "wire_bytes": 1546, + "reported_used_tokens": 1509, + "working_set_bytes": 289505280, + "peak_working_set_bytes": 290426880 + }, + { + "query": "which lessons cover guarding behavior behind environment variables?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 876.0069000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 289509376, + "peak_working_set_bytes": 290426880 + }, + { + "query": "SQLite BUSY error under concurrent writes", + "ranked": [ + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE63YDGRR9GCFM6A405N", + "id": "01M1Y0FHNBEH06PER26XNPR3ME", + "kind": "memory", + "score": 0.9978362917900084, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 870.2198, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 898, + "mcp_result_bytes": 979, + "wire_bytes": 1016, + "reported_used_tokens": 979, + "working_set_bytes": 289509376, + "peak_working_set_bytes": 290426880 + }, + { + "query": "SQLite WAL mode breaks when the database is on a network share", + "ranked": [ + "sqlite-wal-network-drive", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE84FR94JFPZ2CTRK5KY", + "id": "01M1Y0FJGD50RC9T21JG9KJS3G", + "kind": "memory", + "score": 0.999302864074707, + "summary": "project:fact - [2026-09-07] [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + }, + { + "expansion_handle": "memory:01M1Y0CE63YDGRR9GCFM6A405N", + "id": "01M1Y0FJGD5SD54SYG8A5BFDYA", + "kind": "memory", + "score": 0.9966553449630736, + "summary": "project:fact - [2026-09-07] [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1017.2783000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1448, + "mcp_result_bytes": 1547, + "wire_bytes": 1584, + "reported_used_tokens": 1547, + "working_set_bytes": 290041856, + "peak_working_set_bytes": 290959360 + }, + { + "query": "my SQLite WAL database causes SQLITE_IOERR_LOCK on a mapped drive", + "ranked": [ + "sqlite-wal-network-drive" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE84FR94JFPZ2CTRK5KY", + "id": "01M1Y0FKGB7BRPR7G7E4GFST05", + "kind": "memory", + "score": 0.99892657995224, + "summary": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1075.3573000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 748, + "mcp_result_bytes": 829, + "wire_bytes": 866, + "reported_used_tokens": 829, + "working_set_bytes": 290058240, + "peak_working_set_bytes": 290971648 + }, + { + "query": "FTS5 tokenizer configuration for Rust identifiers with underscores", + "ranked": [ + "sqlite-fts5-tokenizer", + "kimetsu-query-stemming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE96YPFCCNT1Z92GH3C5", + "id": "01M1Y0FMHYACSP6TR5Y6GMR36K", + "kind": "memory", + "score": 0.998104453086853, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + }, + { + "expansion_handle": "memory:01M1Y0CHR9RRWWX5FQ2Z9H6B27", + "id": "01M1Y0FMHY38F7RQ1HXAC5CZD4", + "kind": "memory", + "score": 0.7023860812187195, + "summary": "project:fact - [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1002.0119000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1212, + "mcp_result_bytes": 1331, + "wire_bytes": 1368, + "reported_used_tokens": 1331, + "working_set_bytes": 290058240, + "peak_working_set_bytes": 290971648 + }, + { + "query": "I switched the FTS5 tokenizer but search stopped returning results", + "ranked": [ + "sqlite-fts5-tokenizer" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE96YPFCCNT1Z92GH3C5", + "id": "01M1Y0FNHPPRFSQ92DMJQPEMBZ", + "kind": "memory", + "score": 0.8194089531898499, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1030.3192000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 670, + "mcp_result_bytes": 755, + "wire_bytes": 792, + "reported_used_tokens": 755, + "working_set_bytes": 290058240, + "peak_working_set_bytes": 290971648 + }, + { + "query": "optimal SQLite page size for storing embedding vectors", + "ranked": [ + "sqlite-page-size", + "onnx-dim-mismatch", + "onnx-cosine-vs-dot" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CEA9QVBBX2QWZ9FEYJAV", + "id": "01M1Y0FPJ4NWKPJN5GSRBSAV17", + "kind": "memory", + "score": 0.9990121126174928, + "summary": "project:fact - [tags: sqlite page_size performance rusqlite] SQLite's default page_size is 4096 bytes. For a write-heavy brain database with large BLOB payloads (embedding vectors), raising page_size to 16384 reduces fragmentation and improves sequential scan throughput. `PRAGMA page_size = 16384;` must be set BEFORE the first table is created \u2014 changing it on an existing database requires a VACUUM afterward to rebuild all pages." + }, + { + "expansion_handle": "memory:01M1Y0CFS76DA1V14BVQR0NX3T", + "id": "01M1Y0FPJ41S8V29JEQ75WK6QW", + "kind": "memory", + "score": 0.9881643056869508, + "summary": "project:fact - [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results \u2014 the ANN index shape mismatch isn't always caught at runtime." + }, + { + "expansion_handle": "memory:01M1Y0CFR66B0CWZXF5MVKF7P0", + "id": "01M1Y0FPJ41JT3MEHXPXGENK51", + "kind": "memory", + "score": 0.9425415992736816, + "summary": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing \u2014 double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 945.2506000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1860, + "mcp_result_bytes": 1977, + "wire_bytes": 2014, + "reported_used_tokens": 1977, + "working_set_bytes": 290058240, + "peak_working_set_bytes": 290971648 + }, + { + "query": "ON DELETE CASCADE in SQLite does nothing \u2014 foreign keys not enforced", + "ranked": [ + "sqlite-foreign-keys-default-off" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CEB89X22N5Z655769N3B", + "id": "01M1Y0FQF1D22DE189D6NY2NWX", + "kind": "memory", + "score": 0.9996858835220336, + "summary": "project:fact - [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting \u2014 every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1061.6822, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 736, + "mcp_result_bytes": 817, + "wire_bytes": 854, + "reported_used_tokens": 817, + "working_set_bytes": 290058240, + "peak_working_set_bytes": 290971648 + }, + { + "query": "indexing a JSON metadata column in SQLite without a schema migration", + "ranked": [ + "sqlite-json1-extract", + "testing-fixture-drift", + "onnx-dim-mismatch", + "sqlite-partial-index" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CEC7GDA4T9BPXBA7ZBTW", + "id": "01M1Y0FRG6AA5XA1RMDSV02485", + "kind": "memory", + "score": 0.9955366849899292, + "summary": "project:fact - [tags: sqlite json1 json_extract rusqlite] SQLite's json1 extension (built in since 3.38.0) lets you index and query JSONB columns with `json_extract(col, '$.field')`. To create a partial index over a JSON field: `CREATE INDEX idx ON memories (json_extract(metadata, '$.scope')) WHERE json_extract(metadata, '$.scope') IS NOT NULL;`. Use `json_each` for array fields." + }, + { + "expansion_handle": "memory:01M1Y0CGTZS0E9FZN5W5KHQQXA", + "id": "01M1Y0FRG6GZ18649MNTJNR0QG", + "kind": "memory", + "score": 0.8227390646934509, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + }, + { + "expansion_handle": "memory:01M1Y0CFS76DA1V14BVQR0NX3T", + "id": "01M1Y0FRG6NXN6JZDWVQWHKHEX", + "kind": "memory", + "score": 0.38374292850494385, + "summary": "project:fact - [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results \u2014 the ANN index shape mismatch isn't always caught at runtime." + }, + { + "expansion_handle": "memory:01M1Y0CEEF7Z67N696N9ZMRADF", + "id": "01M1Y0FRG671G963N8FMH97X9N", + "kind": "memory", + "score": 0.3276048004627228, + "summary": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query \u2014 the planner uses the partial index only when the WHERE clause matches." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1004.3054, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2381, + "mcp_result_bytes": 2516, + "wire_bytes": 2553, + "reported_used_tokens": 2516, + "working_set_bytes": 290070528, + "peak_working_set_bytes": 290983936 + }, + { + "query": "prepare() vs prepare_cached() in rusqlite hot insert loop", + "ranked": [ + "sqlite-prepared-stmt-cache" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CEDDG6HQA9D97BHWH7CT", + "id": "01M1Y0FSFNAEFAYVZEXBX3R81D", + "kind": "memory", + "score": 0.9993672966957092, + "summary": "project:fact - [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 976.4654, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 689, + "mcp_result_bytes": 770, + "wire_bytes": 807, + "reported_used_tokens": 770, + "working_set_bytes": 290074624, + "peak_working_set_bytes": 290983936 + }, + { + "query": "speed up bulk memory ingest by caching SQL statements", + "ranked": [ + "sqlite-prepared-stmt-cache" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CEDDG6HQA9D97BHWH7CT", + "id": "01M1Y0FTE4135DKDF9ZWFHZQ19", + "kind": "memory", + "score": 0.9823396801948548, + "summary": "project:fact - [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 987.2556999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 688, + "mcp_result_bytes": 769, + "wire_bytes": 806, + "reported_used_tokens": 769, + "working_set_bytes": 290078720, + "peak_working_set_bytes": 290988032 + }, + { + "query": "partial index on deleted_at IS NULL for faster active memory queries", + "ranked": [ + "sqlite-partial-index" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CEEF7Z67N696N9ZMRADF", + "id": "01M1Y0FVD172JBJ2SAMH48PJ6P", + "kind": "memory", + "score": 0.9988954067230223, + "summary": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query \u2014 the planner uses the partial index only when the WHERE clause matches." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 968.5093999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 794, + "mcp_result_bytes": 875, + "wire_bytes": 912, + "reported_used_tokens": 875, + "working_set_bytes": 290095104, + "peak_working_set_bytes": 291000320 + }, + { + "query": "the brain query is slow because it scans all rows including soft-deleted ones", + "ranked": [ + "sqlite-partial-index" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CEEF7Z67N696N9ZMRADF", + "id": "01M1Y0FWBASSYWCY3RJWGV1ZDG", + "kind": "memory", + "score": 0.5760471224784851, + "summary": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query \u2014 the planner uses the partial index only when the WHERE clause matches." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1059.3857, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 793, + "mcp_result_bytes": 874, + "wire_bytes": 911, + "reported_used_tokens": 874, + "working_set_bytes": 290566144, + "peak_working_set_bytes": 291487744 + }, + { + "query": "Cargo.lock changed unexpectedly after adding a new workspace crate", + "ranked": [ + "cargo-lockfile-drift", + "cargo-feature-unification-embeddings", + "cargo-target-dir-sharing", + "cargo-patch-section" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CEFG82GG4CW59T7HTY9Z", + "id": "01M1Y0FXCNTQMTDWXNZ28NWQB8", + "kind": "memory", + "score": 0.9991374015808104, + "summary": "project:fact - [2026-09-07] [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this \u2014 it errors on any lockfile diff." + }, + { + "expansion_handle": "memory:01M1Y0CDJN5BPC6G4D9PSYG7G6", + "id": "01M1Y0FXCNM9DJBVA73A0586KH", + "kind": "memory", + "score": 0.9968542456626892, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph \u2014 because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1Y0CEJNCGQE145VV7PMAA9T", + "id": "01M1Y0FXCNV78HE76QT6WEZWXN", + "kind": "memory", + "score": 0.9829630851745604, + "summary": "project:fact - [2026-09-07] [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps \u2014 use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + }, + { + "expansion_handle": "memory:01M1Y0CENN1SARTT7M8KTJ5XE9", + "id": "01M1Y0FXCPTJ40T7TQ7VJCQ1VG", + "kind": "memory", + "score": 0.9262890815734864, + "summary": "project:fact - [2026-09-07] [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace \u2014 including transitive deps \u2014 that depend on `my-crate`. Remove the patch before publishing." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 882.3282999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3010, + "mcp_result_bytes": 3153, + "wire_bytes": 3190, + "reported_used_tokens": 3153, + "working_set_bytes": 290570240, + "peak_working_set_bytes": 291491840 + }, + { + "query": "how do I prevent CI from accepting a modified lockfile silently?", + "ranked": [ + "cargo-lockfile-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CEFG82GG4CW59T7HTY9Z", + "id": "01M1Y0FY8926Z3212MP1JSZMPT", + "kind": "memory", + "score": 0.9125379323959352, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this \u2014 it errors on any lockfile diff." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 998.5935999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 765, + "mcp_result_bytes": 846, + "wire_bytes": 883, + "reported_used_tokens": 846, + "working_set_bytes": 290574336, + "peak_working_set_bytes": 291495936 + }, + { + "query": "build.rs reruns on every incremental build even when nothing changed", + "ranked": [ + "cargo-build-script-rerun" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CEGFZFDFJ4ZM064X7YNY", + "id": "01M1Y0FZ88H0FXCXYGG1H0Y9QM", + "kind": "memory", + "score": 0.9996689558029176, + "summary": "project:fact - [2026-09-07] [tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1051.6212, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 698, + "mcp_result_bytes": 779, + "wire_bytes": 816, + "reported_used_tokens": 779, + "working_set_bytes": 290586624, + "peak_working_set_bytes": 291508224 + }, + { + "query": "incremental cargo build is slow because build script runs every time", + "ranked": [ + "cargo-build-script-rerun" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CEGFZFDFJ4ZM064X7YNY", + "id": "01M1Y0G088DWY10HES8XDY085S", + "kind": "memory", + "score": 0.9978280663490297, + "summary": "project:fact - [tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 943.1259, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 685, + "mcp_result_bytes": 766, + "wire_bytes": 803, + "reported_used_tokens": 766, + "working_set_bytes": 290590720, + "peak_working_set_bytes": 291508224 + }, + { + "query": "a dev-dependency is activating an embeddings feature in my production build", + "ranked": [ + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CEHKWTJ8ZMXANWDJAMCD", + "id": "01M1Y0G15MRR51Q8HX4EHQY9RB", + "kind": "memory", + "score": 0.9944193959236144, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1034.071, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 931, + "mcp_result_bytes": 1012, + "wire_bytes": 1049, + "reported_used_tokens": 1012, + "working_set_bytes": 290672640, + "peak_working_set_bytes": 291590144 + }, + { + "query": "how do I prevent a test-only feature from bleeding into the non-test compilation?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 989.1259, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 290684928, + "peak_working_set_bytes": 291602432 + }, + { + "query": "linker errors in target/ caused by antivirus holding the exe file", + "ranked": [ + "windows-file-locking-av", + "cargo-target-dir-sharing" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFCYM1PTCWE8TVCBYW97", + "id": "01M1Y0G36ACK84MR3NJ6SG159H", + "kind": "memory", + "score": 0.9997633099555968, + "summary": "project:fact - [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + }, + { + "expansion_handle": "memory:01M1Y0CEJNCGQE145VV7PMAA9T", + "id": "01M1Y0G36ABH4H90QPF80TG3WJ", + "kind": "memory", + "score": 0.7463976740837097, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps \u2014 use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1041.0261, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1523, + "mcp_result_bytes": 1622, + "wire_bytes": 1659, + "reported_used_tokens": 1622, + "working_set_bytes": 290697216, + "peak_working_set_bytes": 291610624 + }, + { + "query": "Access is denied (os error 5) when linking on Windows \u2014 how do I fix this?", + "ranked": [ + "windows-file-locking-av" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFCYM1PTCWE8TVCBYW97", + "id": "01M1Y0G45TM0EEANVBYCKTA795", + "kind": "memory", + "score": 0.9977193474769592, + "summary": "project:fact - [2026-09-07] [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1014.0079, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 769, + "mcp_result_bytes": 850, + "wire_bytes": 887, + "reported_used_tokens": 850, + "working_set_bytes": 290725888, + "peak_working_set_bytes": 291639296 + }, + { + "query": "incremental build broke with a type mismatch after switching branches", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CEKN0QBDZ73SACBN6Y6X", + "id": "01M1Y0G556GQ68DGHMFZE74QMV", + "kind": "memory", + "score": 0.7971777319908142, + "summary": "project:fact - [2026-09-07] [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1026.0916, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 890, + "mcp_result_bytes": 971, + "wire_bytes": 1008, + "reported_used_tokens": 971, + "working_set_bytes": 290725888, + "peak_working_set_bytes": 291643392 + }, + { + "query": "cargo reports a type error that references a type not in the codebase", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CEKN0QBDZ73SACBN6Y6X", + "id": "01M1Y0G65QGDV6593B7SWB1W1E", + "kind": "memory", + "score": 0.7925198078155518, + "summary": "project:fact - [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 981.2978, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 877, + "mcp_result_bytes": 958, + "wire_bytes": 995, + "reported_used_tokens": 958, + "working_set_bytes": 290725888, + "peak_working_set_bytes": 291651584 + }, + { + "query": "compile fastembed at O2 in debug builds to avoid slow embedding inference", + "ranked": [ + "cargo-profile-override", + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CEMKNXWNXAX5DG21NDNC", + "id": "01M1Y0G74CGZDVCRFEKCEMMZVG", + "kind": "memory", + "score": 0.9932281374931335, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1Y0CGWYZ09QBQFPX4FEVE70", + "id": "01M1Y0G74CES9RC000SXJB7C9V", + "kind": "memory", + "score": 0.987656831741333, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1035.9539, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1322, + "mcp_result_bytes": 1421, + "wire_bytes": 1458, + "reported_used_tokens": 1421, + "working_set_bytes": 290729984, + "peak_working_set_bytes": 291651584 + }, + { + "query": "override compilation profile for a single crate in a Cargo workspace", + "ranked": [ + "cargo-patch-section", + "cargo-profile-override", + "cargo-target-dir-sharing", + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CENN1SARTT7M8KTJ5XE9", + "id": "01M1Y0G85P7HYREM0135CTED6M", + "kind": "memory", + "score": 0.9984123706817628, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace \u2014 including transitive deps \u2014 that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1Y0CEMKNXWNXAX5DG21NDNC", + "id": "01M1Y0G85PG8ER3ZN601ZBM8WV", + "kind": "memory", + "score": 0.9979992508888244, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1Y0CEJNCGQE145VV7PMAA9T", + "id": "01M1Y0G85P47QHKJE6PK9SGSHV", + "kind": "memory", + "score": 0.9956549406051636, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps \u2014 use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + }, + { + "expansion_handle": "memory:01M1Y0CEHKWTJ8ZMXANWDJAMCD", + "id": "01M1Y0G85PBQJ0DBNN3TANAK3F", + "kind": "memory", + "score": 0.9820712208747864, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 0.5, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1072.6632, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2682, + "mcp_result_bytes": 2821, + "wire_bytes": 2858, + "reported_used_tokens": 2821, + "working_set_bytes": 290738176, + "peak_working_set_bytes": 291663872 + }, + { + "query": "[patch.crates-io] workspace dependency override", + "ranked": [ + "cargo-patch-section", + "cargo-lockfile-drift", + "cargo-dev-dep-leak", + "cargo-target-dir-sharing" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CENN1SARTT7M8KTJ5XE9", + "id": "01M1Y0G95VKMXGTSVB3WN9FT2D", + "kind": "memory", + "score": 0.9999405145645142, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace \u2014 including transitive deps \u2014 that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1Y0CEFG82GG4CW59T7HTY9Z", + "id": "01M1Y0G95WH1GWKCD7EAN613PP", + "kind": "memory", + "score": 0.9975811243057252, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this \u2014 it errors on any lockfile diff." + }, + { + "expansion_handle": "memory:01M1Y0CEHKWTJ8ZMXANWDJAMCD", + "id": "01M1Y0G95WRJ4ZSACAF8AMEFMW", + "kind": "memory", + "score": 0.994149684906006, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + }, + { + "expansion_handle": "memory:01M1Y0CEJNCGQE145VV7PMAA9T", + "id": "01M1Y0G95WQNQQ56RNBQKKMGS0", + "kind": "memory", + "score": 0.7471600770950317, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps \u2014 use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 770.3614, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2755, + "mcp_result_bytes": 2894, + "wire_bytes": 2931, + "reported_used_tokens": 2894, + "working_set_bytes": 290738176, + "peak_working_set_bytes": 291663872 + }, + { + "query": "pin minimum supported Rust version in Cargo.toml", + "ranked": [ + "cargo-msrv" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CEPM3E4XMZRG0JF64DYX", + "id": "01M1Y0G9XZ02SXE2C18WAE95FW", + "kind": "memory", + "score": 0.999652862548828, + "summary": "project:fact - [tags: cargo rust msrv edition compatibility] Set `rust-version` in each `Cargo.toml` to declare the minimum supported Rust version (MSRV). Cargo enforces this with `--check`: `cargo check` fails if the toolchain is older than `rust-version`. Keep MSRV as old as your oldest supported deployment target." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 871.2769000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 693, + "mcp_result_bytes": 774, + "wire_bytes": 811, + "reported_used_tokens": 774, + "working_set_bytes": 290738176, + "peak_working_set_bytes": 291663872 + }, + { + "query": "Windows path over 260 characters causes OS error 3 during Cargo build", + "ranked": [ + "windows-long-paths", + "windows-file-locking-av" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFC04YHR8CKTQRYT9FFC", + "id": "01M1Y0GAS70JSGZQ9XZJZGZKD9", + "kind": "memory", + "score": 0.9964189529418944, + "summary": "project:fact - [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe." + }, + { + "expansion_handle": "memory:01M1Y0CFCYM1PTCWE8TVCBYW97", + "id": "01M1Y0GAS7YAV3QP0KY1KBM10T", + "kind": "memory", + "score": 0.9571694135665894, + "summary": "project:fact - [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 894.536, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1297, + "mcp_result_bytes": 1406, + "wire_bytes": 1443, + "reported_used_tokens": 1406, + "working_set_bytes": 290738176, + "peak_working_set_bytes": 291663872 + }, + { + "query": "how do I enable long file paths for Cargo on Windows?", + "ranked": [ + "windows-long-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFC04YHR8CKTQRYT9FFC", + "id": "01M1Y0GBN9ZF7SE49GT26CA9E9", + "kind": "memory", + "score": 0.9998334646224976, + "summary": "project:fact - [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 984.4273999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 769, + "mcp_result_bytes": 860, + "wire_bytes": 897, + "reported_used_tokens": 860, + "working_set_bytes": 290738176, + "peak_working_set_bytes": 291663872 + }, + { + "query": "intermittent sharing violation errors when Rust linker writes the exe on Windows", + "ranked": [ + "windows-file-locking-av", + "windows-long-paths", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFCYM1PTCWE8TVCBYW97", + "id": "01M1Y0GCMBK1EWTDP971DF704T", + "kind": "memory", + "score": 0.999750316143036, + "summary": "project:fact - [2026-09-07] [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + }, + { + "expansion_handle": "memory:01M1Y0CFC04YHR8CKTQRYT9FFC", + "id": "01M1Y0GCMBT13C1BJAAERAHJMR", + "kind": "memory", + "score": 0.4757097661495209, + "summary": "project:fact - [2026-09-07] [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe." + }, + { + "expansion_handle": "memory:01M1Y0CE63YDGRR9GCFM6A405N", + "id": "01M1Y0GCMBF9NENDEKFE998CSM", + "kind": "memory", + "score": 0.38107830286026, + "summary": "project:fact - [2026-09-07] [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 943.9022, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2006, + "mcp_result_bytes": 2133, + "wire_bytes": 2170, + "reported_used_tokens": 2133, + "working_set_bytes": 290738176, + "peak_working_set_bytes": 291663872 + }, + { + "query": "Rust walkdir follows junctions differently from symlinks on Windows", + "ranked": [ + "windows-junctions-vs-symlinks" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFGA9WH2HS25121CG3FH", + "id": "01M1Y0GDHKACVN1SEN9SAA14QR", + "kind": "memory", + "score": 0.9996020197868348, + "summary": "project:fact - [tags: windows junctions symlinks rust std::fs] On Windows, directory junctions (NTFS reparse points) behave like symlinks for directory traversal but `std::fs::symlink_metadata` returns `FileType::is_symlink() = false` for junctions (only true for regular symlinks). Use `std::fs::read_link` \u2014 it succeeds for both junction and symlink. `walkdir` crate's `follow_links` follows both, but its `is_symlink()` method correctly reports only actual symlinks." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 994.0446, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 845, + "mcp_result_bytes": 926, + "wire_bytes": 963, + "reported_used_tokens": 926, + "working_set_bytes": 290742272, + "peak_working_set_bytes": 291663872 + }, + { + "query": "UNC path canonicalize returns verbatim prefix \u2014 how do I strip it?", + "ranked": [ + "windows-unc-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFDY80D7VPYKJS1EQ973", + "id": "01M1Y0GEGZ9HAHM6Z121H4RCC7", + "kind": "memory", + "score": 0.9988629817962646, + "summary": "project:fact - [tags: windows unc-paths rust std::fs] Windows UNC paths (`\\\\server\\share\\...`) are not supported by most Rust `std::fs` operations unless passed through the extended-length prefix `\\\\?\\UNC\\server\\share\\...`. `std::path::Path::new(\"\\\\\\\\server\\\\share\")` works for basic operations but breaks with `canonicalize()` which returns the verbatim prefix form. When walking directory trees that may start on UNC paths, use the `dunce` crate to strip the verbatim prefix before comparing or displaying paths." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1013.0558000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 908, + "mcp_result_bytes": 1025, + "wire_bytes": 1062, + "reported_used_tokens": 1025, + "working_set_bytes": 290742272, + "peak_working_set_bytes": 291672064 + }, + { + "query": "UTF-8 memory text prints as mojibake in the Windows console", + "ranked": [ + "windows-console-encoding" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFF4Q7Y08GK0RN1VKVZV", + "id": "01M1Y0GFGKBCZSEY7MDE9DDE4T", + "kind": "memory", + "score": 0.9996604919433594, + "summary": "project:fact - [tags: windows console encoding utf8 rust] Windows console code page defaults to the system ANSI code page (usually CP1252 or CP932), not UTF-8. Rust's `println!` writes UTF-8 bytes which display as mojibake in a non-UTF-8 console. Fix at process startup: call `SetConsoleOutputCP(65001)` via `winapi` or `windows-sys`, or set `PYTHONUTF8=1`/`RUST_LOG` before launch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 982.9894, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 757, + "mcp_result_bytes": 838, + "wire_bytes": 875, + "reported_used_tokens": 838, + "working_set_bytes": 290742272, + "peak_working_set_bytes": 291672064 + }, + { + "query": "process exit code is 4294967295 instead of -1 on Windows", + "ranked": [ + "windows-exit-codes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFHF017QB143HM13PMSC", + "id": "01M1Y0GGG1W63ZPTQ3KB4RNKXH", + "kind": "memory", + "score": 0.9966622591018676, + "summary": "project:fact - [tags: windows exit-codes rust process child] On Windows, process exit codes are 32-bit unsigned integers (DWORD). Rust's `ExitStatus::code()` returns `Option` \u2014 it's `None` if the process was killed by a signal (which Windows doesn't use; instead, TerminateProcess with a code). Conventional codes: 0=success, 1=generic error, 0xC0000005=access violation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1020.1899999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 753, + "mcp_result_bytes": 834, + "wire_bytes": 871, + "reported_used_tokens": 834, + "working_set_bytes": 290742272, + "peak_working_set_bytes": 291672064 + }, + { + "query": "tokenizer.json must match the ONNX model \u2014 what breaks if it doesn't?", + "ranked": [ + "onnx-tokenizer-mismatch" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFKYR3WDJRKWM69A4J4V", + "id": "01M1Y0GHF210YETVDR62V34R8Q", + "kind": "memory", + "score": 0.9991299510002136, + "summary": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly \u2014 specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings \u2014 cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1052.4279000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 959, + "mcp_result_bytes": 1040, + "wire_bytes": 1077, + "reported_used_tokens": 1040, + "working_set_bytes": 290742272, + "peak_working_set_bytes": 291672064 + }, + { + "query": "embedding quality degraded after I swapped in the INT8 quantized model", + "ranked": [ + "onnx-quantization-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFN2M30C7MN0GWMT2VSV", + "id": "01M1Y0GJFXZ2Z1PR1S5N3ZZWD6", + "kind": "memory", + "score": 0.997980535030365, + "summary": "project:fact - [2026-09-07] [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals \u2014 cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1006.1175, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 990, + "mcp_result_bytes": 1071, + "wire_bytes": 1108, + "reported_used_tokens": 1071, + "working_set_bytes": 290742272, + "peak_working_set_bytes": 291672064 + }, + { + "query": "missing attention mask causes low-norm embeddings in batch inference", + "ranked": [ + "onnx-batch-padding" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFP13FB0MRZX0RQTZRT0", + "id": "01M1Y0GKFABH4YNRKCB2B32WDE", + "kind": "memory", + "score": 0.9998397827148438, + "summary": "project:fact - [tags: onnx batch padding attention-mask embeddings] When running batch inference with an ONNX model, all inputs in the batch must be padded to the same sequence length. The `attention_mask` tensor marks which tokens are real (1) and which are padding (0). Failing to pass `attention_mask` causes the model to average-pool over padding tokens, producing systematically lower-norm embeddings." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1009.9062999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 781, + "mcp_result_bytes": 862, + "wire_bytes": 899, + "reported_used_tokens": 862, + "working_set_bytes": 290742272, + "peak_working_set_bytes": 291672064 + }, + { + "query": "ONNX model download fails in a Docker container with no home directory", + "ranked": [ + "onnx-model-cache-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFQ64J0V9K34YSY0Q2RM", + "id": "01M1Y0GMEZZ92HYXMTQEREMAMQ", + "kind": "memory", + "score": 0.9887272119522096, + "summary": "project:fact - [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1076.8203, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 755, + "mcp_result_bytes": 838, + "wire_bytes": 875, + "reported_used_tokens": 838, + "working_set_bytes": 290713600, + "peak_working_set_bytes": 291672064 + }, + { + "query": "fastembed cache path environment variable for CI", + "ranked": [ + "onnx-model-cache-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFQ64J0V9K34YSY0Q2RM", + "id": "01M1Y0GNGTHSN53HCVFKRAFDA4", + "kind": "memory", + "score": 0.9995118379592896, + "summary": "project:fact - [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1007.6210000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 756, + "mcp_result_bytes": 839, + "wire_bytes": 876, + "reported_used_tokens": 839, + "working_set_bytes": 290713600, + "peak_working_set_bytes": 291672064 + }, + { + "query": "cosine similarity vs dot product for L2-normalized embedding vectors", + "ranked": [ + "onnx-cosine-vs-dot", + "onnx-tokenizer-mismatch", + "onnx-quantization-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFR66B0CWZXF5MVKF7P0", + "id": "01M1Y0GPG368HJ05N67A1F5K9W", + "kind": "memory", + "score": 0.9999407529830932, + "summary": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing \u2014 double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + }, + { + "expansion_handle": "memory:01M1Y0CFKYR3WDJRKWM69A4J4V", + "id": "01M1Y0GPG3W1BGS53DXS7THAEG", + "kind": "memory", + "score": 0.9514977931976318, + "summary": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly \u2014 specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings \u2014 cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo." + }, + { + "expansion_handle": "memory:01M1Y0CFN2M30C7MN0GWMT2VSV", + "id": "01M1Y0GPG3BKE3AM0TCVZ572DH", + "kind": "memory", + "score": 0.941756010055542, + "summary": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals \u2014 cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 971.3107, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2245, + "mcp_result_bytes": 2362, + "wire_bytes": 2399, + "reported_used_tokens": 2362, + "working_set_bytes": 290713600, + "peak_working_set_bytes": 291672064 + }, + { + "query": "stored vectors have wrong dimension after switching embedding models", + "ranked": [ + "onnx-dim-mismatch", + "onnx-cosine-vs-dot", + "onnx-tokenizer-mismatch" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFS76DA1V14BVQR0NX3T", + "id": "01M1Y0GQEPN68GQXR6T7BXQFA4", + "kind": "memory", + "score": 0.9997621178627014, + "summary": "project:fact - [2026-09-07] [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results \u2014 the ANN index shape mismatch isn't always caught at runtime." + }, + { + "expansion_handle": "memory:01M1Y0CFR66B0CWZXF5MVKF7P0", + "id": "01M1Y0GQEPW5HJMG5YN53HAFY7", + "kind": "memory", + "score": 0.997715711593628, + "summary": "project:fact - [2026-09-07] [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing \u2014 double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + }, + { + "expansion_handle": "memory:01M1Y0CFKYR3WDJRKWM69A4J4V", + "id": "01M1Y0GQEPGEM215KG2Y1PZQYY", + "kind": "memory", + "score": 0.9388805031776428, + "summary": "project:fact - [2026-09-07] [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly \u2014 specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings \u2014 cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 830.1889, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2049, + "mcp_result_bytes": 2166, + "wire_bytes": 2203, + "reported_used_tokens": 2166, + "working_set_bytes": 290721792, + "peak_working_set_bytes": 291672064 + }, + { + "query": "E5 and Instructor models need a query prefix \u2014 what happens without it?", + "ranked": [ + "onnx-prefix-instructions", + "onnx-cosine-vs-dot" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFWAMZG7RES52QMQ6ZZ9", + "id": "01M1Y0GR8K28PPWFXEDYFPN6RZ", + "kind": "memory", + "score": 0.996955633163452, + "summary": "project:fact - [tags: onnx embeddings prefix instruction e5 query passage] E5 and Instructor family models require a text prefix on BOTH query and passage sides to produce meaningful similarities: query prefix `\"query: \"`, passage prefix `\"passage: \"`. Omitting the prefix can drop MRR by 10-15 percentage points on out-of-domain datasets. Check the model's README for the exact prefix string \u2014 it varies by model family." + }, + { + "expansion_handle": "memory:01M1Y0CFR66B0CWZXF5MVKF7P0", + "id": "01M1Y0GR8KRENJYF6MGZYD2QKH", + "kind": "memory", + "score": 0.9543967247009276, + "summary": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing \u2014 double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 999.0515, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1339, + "mcp_result_bytes": 1446, + "wire_bytes": 1483, + "reported_used_tokens": 1446, + "working_set_bytes": 290721792, + "peak_working_set_bytes": 291672064 + }, + { + "query": "ORT thread pool contention when running multiple bench processes in parallel", + "ranked": [ + "onnx-ort-threading" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFXBEVH5NSD9GP4MD291", + "id": "01M1Y0GS8BRYMS6JXWW3NP6QJE", + "kind": "memory", + "score": 0.9998078942298888, + "summary": "project:fact - [2026-09-07] [tags: onnx ort thread-pool parallelism cpu] ORT (ONNX Runtime) creates its own inter-op and intra-op thread pools. In a multi-process bench setup, each child inherits these pools and they compete for CPU cores. Set `SessionOptionsBuilder::with_intra_threads(1).with_inter_threads(1)` if you're running many parallel bench processes \u2014 this sacrifices per-inference throughput for lower contention." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1023.4483999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 802, + "mcp_result_bytes": 883, + "wire_bytes": 920, + "reported_used_tokens": 883, + "working_set_bytes": 290721792, + "peak_working_set_bytes": 291672064 + }, + { + "query": "git worktrees share the .kimetsu brain \u2014 how do I isolate test runs?", + "ranked": [ + "git-worktree-brain-isolation", + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFYDVF1XT2NJ4R1CE9KK", + "id": "01M1Y0GT7PJW89EK1FSG47MKPM", + "kind": "memory", + "score": 0.9996256828308104, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root \u2014 if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + }, + { + "expansion_handle": "memory:01M1Y0CDW1WMXYA9MGV0TK68J6", + "id": "01M1Y0GT7P4D8XKC9QP4PRAJT0", + "kind": "memory", + "score": 0.9904396533966064, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 931.3589, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1435, + "mcp_result_bytes": 1534, + "wire_bytes": 1571, + "reported_used_tokens": 1534, + "working_set_bytes": 290725888, + "peak_working_set_bytes": 291672064 + }, + { + "query": "when is it safe to use --no-verify on git commit?", + "ranked": [ + "git-hooks-bypass", + "git-reflog-rescue" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFZD1W15NCZZWH5WJC0K", + "id": "01M1Y0GV50ZB6JXX75YZ92Y8VX", + "kind": "memory", + "score": 0.9956986904144288, + "summary": "project:fact - [2026-09-07] [tags: git hooks bypass pre-commit skip] `git commit --no-verify` skips ALL hooks (pre-commit and commit-msg). Never use this in shared team repos where hooks enforce quality gates (lint, tests, memory harvest). Instead, fix the failing hook." + }, + { + "expansion_handle": "memory:01M1Y0CG3J6F6PE488KD89FHD4", + "id": "01M1Y0GV50489PF5K51JZC79PB", + "kind": "memory", + "score": 0.5084817409515381, + "summary": "project:fact - [2026-09-07] [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone \u2014 they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only \u2014 remote reflog is not accessible via normal git commands." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1046.4164, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1193, + "mcp_result_bytes": 1292, + "wire_bytes": 1329, + "reported_used_tokens": 1292, + "working_set_bytes": 290725888, + "peak_working_set_bytes": 291672064 + }, + { + "query": "reduce clone size and bandwidth for server-side repo ingest", + "ranked": [ + "git-sparse-checkout", + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CG0D8V7JRBSXCYXBRNG3", + "id": "01M1Y0GW5KNMSDZQD7R76J1CW6", + "kind": "memory", + "score": 0.9969936609268188, + "summary": "project:fact - [tags: git sparse-checkout partial-clone bandwidth] `git sparse-checkout init --cone` combined with `git clone --filter=blob:none` (partial clone) fetches only the commit graph and tree objects, not blobs. Individual blobs are fetched on demand when accessed. This cuts clone time for large repos from minutes to seconds." + }, + { + "expansion_handle": "memory:01M1Y0CDERH0MV40CSBCQDEQYA", + "id": "01M1Y0GW5K3SVSK4613C98Y9VF", + "kind": "memory", + "score": 0.8199672698974609, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1052.6349, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1744, + "mcp_result_bytes": 1843, + "wire_bytes": 1880, + "reported_used_tokens": 1843, + "working_set_bytes": 290725888, + "peak_working_set_bytes": 291672064 + }, + { + "query": "spurious diffs from Windows CRLF line ending conversion in git", + "ranked": [ + "git-line-endings-windows" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CG1G409C78CR8V9VBK7M", + "id": "01M1Y0GX6K80R49Q0JRGVD0SRB", + "kind": "memory", + "score": 0.9993343949317932, + "summary": "project:fact - [tags: git line-endings windows crlf autocrlf] On Windows, `core.autocrlf=true` (git's default for Windows installs) converts LF to CRLF on checkout and CRLF to LF on commit. This causes spurious diffs when files are edited on Windows then committed \u2014 the content is identical but the line endings differ in the index vs the working tree. Fix: set `core.autocrlf=false` and `.gitattributes` with `* text=auto eol=lf` for the repo." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1030.3709000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 940, + "reported_used_tokens": 903, + "working_set_bytes": 290725888, + "peak_working_set_bytes": 291672064 + }, + { + "query": "git submodule always gets the wrong commit in CI", + "ranked": [ + "git-submodule-pinning", + "git-hooks-bypass" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CG2J6XC8ARPF2CP7BP61", + "id": "01M1Y0GY6ZCQ52NRG35FJWJDTM", + "kind": "memory", + "score": 0.9992856383323668, + "summary": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip \u2014 this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version." + }, + { + "expansion_handle": "memory:01M1Y0CFZD1W15NCZZWH5WJC0K", + "id": "01M1Y0GY6ZZADQ98YNAMBDGR12", + "kind": "memory", + "score": 0.6295387744903564, + "summary": "project:fact - [tags: git hooks bypass pre-commit skip] `git commit --no-verify` skips ALL hooks (pre-commit and commit-msg). Never use this in shared team repos where hooks enforce quality gates (lint, tests, memory harvest). Instead, fix the failing hook." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1046.2918000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1157, + "mcp_result_bytes": 1256, + "wire_bytes": 1293, + "reported_used_tokens": 1256, + "working_set_bytes": 290865152, + "peak_working_set_bytes": 291782656 + }, + { + "query": "accidentally ran git reset --hard and lost commits \u2014 can I recover?", + "ranked": [ + "git-reflog-rescue" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CG3J6F6PE488KD89FHD4", + "id": "01M1Y0GZ7DC3NV90857AJQCSJW", + "kind": "memory", + "score": 0.9995450377464294, + "summary": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone \u2014 they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only \u2014 remote reflog is not accessible via normal git commands." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1074.79, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 762, + "mcp_result_bytes": 843, + "wire_bytes": 880, + "reported_used_tokens": 843, + "working_set_bytes": 290865152, + "peak_working_set_bytes": 291782656 + }, + { + "query": "blocking SQLite call from an async tokio handler causes latency spikes", + "ranked": [ + "tokio-blocking-in-async" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CG4GXEDWCNR553B345ZY", + "id": "01M1Y0H093GHNQ67XAYDES4SM2", + "kind": "memory", + "score": 0.9996535778045654, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 981.7245999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 766, + "mcp_result_bytes": 847, + "wire_bytes": 884, + "reported_used_tokens": 847, + "working_set_bytes": 290865152, + "peak_working_set_bytes": 291782656 + }, + { + "query": "Cannot start a runtime from within a runtime in a tokio test", + "ranked": [ + "tokio-runtime-in-tests", + "tokio-blocking-in-async" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CG5NP8CQF81Q3J12R0GS", + "id": "01M1Y0H18RHYYNYBAYWH228MFC", + "kind": "memory", + "score": 0.9997126460075378, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + }, + { + "expansion_handle": "memory:01M1Y0CG4GXEDWCNR553B345ZY", + "id": "01M1Y0H18RZ13Z1S6C5EGKSC5Q", + "kind": "memory", + "score": 0.5779464840888977, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1025.6133, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1370, + "mcp_result_bytes": 1477, + "wire_bytes": 1514, + "reported_used_tokens": 1477, + "working_set_bytes": 290865152, + "peak_working_set_bytes": 291782656 + }, + { + "query": "tokio select cancels the other branch and loses the value in the channel", + "ranked": [ + "tokio-select-cancellation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CG6WN1WWXEWWMMGQZ45N", + "id": "01M1Y0H28R59NDC67STRXS6CJ3", + "kind": "memory", + "score": 0.9981033802032472, + "summary": "project:fact - [tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1049.9551000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 751, + "mcp_result_bytes": 832, + "wire_bytes": 869, + "reported_used_tokens": 832, + "working_set_bytes": 291049472, + "peak_working_set_bytes": 291971072 + }, + { + "query": "mpsc channel backpressure causing senders to stall", + "ranked": [ + "tokio-channel-backpressure" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CG8017ZQQBB08PZX3NPY", + "id": "01M1Y0H38P029YWM3EB8700Z9K", + "kind": "memory", + "score": 0.9999104738235474, + "summary": "project:fact - [tags: tokio mpsc channel backpressure async rust] `tokio::sync::mpsc::channel(N)` with a bounded buffer provides backpressure: senders block when the buffer is full. This prevents unbounded memory growth but can cause sender tasks to stall. Choosing N: too small causes frequent backpressure (throughput drops); too large defeats the purpose." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1090.0527, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 733, + "mcp_result_bytes": 814, + "wire_bytes": 851, + "reported_used_tokens": 814, + "working_set_bytes": 291131392, + "peak_working_set_bytes": 292040704 + }, + { + "query": "overhead from calling spawn_blocking on every single query request", + "ranked": [ + "tokio-spawn-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CG92HHK7CQ8GSK4Z8RT3", + "id": "01M1Y0H4ARH7YXHDJ0SBNY21TE", + "kind": "memory", + "score": 0.9961729645729064, + "summary": "project:fact - [tags: tokio spawn_blocking thread-pool rust blocking] `tokio::task::spawn_blocking` places work on a dedicated blocking thread pool (default up to 512 threads, configurable via `Builder::max_blocking_threads`). Each call creates or reuses a thread \u2014 there's no true pooling, threads may be created on demand. For many short-duration blocking calls (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1009.5518000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 746, + "mcp_result_bytes": 827, + "wire_bytes": 864, + "reported_used_tokens": 827, + "working_set_bytes": 291266560, + "peak_working_set_bytes": 292179968 + }, + { + "query": "axum server panics during shutdown because the DB pool is already closed", + "ranked": [ + "tokio-shutdown-ordering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGCDX7QHSQ0KP7DJW3ES", + "id": "01M1Y0H5AQZ4MXKJBHFZWWJH80", + "kind": "memory", + "score": 0.98052579164505, + "summary": "project:fact - [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries \u2014 the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1048.2772, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 931, + "mcp_result_bytes": 1012, + "wire_bytes": 1049, + "reported_used_tokens": 1012, + "working_set_bytes": 291266560, + "peak_working_set_bytes": 292188160 + }, + { + "query": "reqwest Client created per-request defeats connection pooling", + "ranked": [ + "http-connection-pooling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGDFD07QWYC4G8JDJGYZ", + "id": "01M1Y0H6BBATNTWBZYM00KPQ6N", + "kind": "memory", + "score": 0.9998082518577576, + "summary": "project:fact - [tags: http reqwest connection-pool keep-alive rust] reqwest's `Client` holds a connection pool; always create ONE `Client` instance and clone it for each handler \u2014 cloning is cheap (Arc under the hood). Creating a `Client::new()` per request defeats connection pooling and causes TCP connection exhaustion under load. The default pool settings: max_idle_per_host=usize::MAX (unbounded), idle_timeout=90s." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 970.3035, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 797, + "mcp_result_bytes": 878, + "wire_bytes": 915, + "reported_used_tokens": 878, + "working_set_bytes": 291274752, + "peak_working_set_bytes": 292188160 + }, + { + "query": "LLM request times out during streaming \u2014 which timeout setting applies?", + "ranked": [ + "http-timeout-layering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGEM6S4YH3PHRN8PN7TH", + "id": "01M1Y0H79QHSB9HHFG8WK2XR0Q", + "kind": "memory", + "score": 0.9987107515335084, + "summary": "project:fact - [tags: http reqwest timeout connect read total rust] reqwest has three distinct timeout knobs: `connect_timeout`, `read_timeout`, and `timeout` (total). They compose: if all three are set, the request fails at whichever fires first. For LLM API calls with streaming responses, `read_timeout` must be larger than the slowest expected token (often 30-60s) while `connect_timeout` can be tight (3-5s)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 851.7888, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 788, + "mcp_result_bytes": 869, + "wire_bytes": 906, + "reported_used_tokens": 869, + "working_set_bytes": 291278848, + "peak_working_set_bytes": 292196352 + }, + { + "query": "how do I safely retry a POST to the LLM API without creating duplicates?", + "ranked": [ + "http-retry-idempotency" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGFQX2AFH3HY29EDD86W", + "id": "01M1Y0H8462YGZXYB0F645CY9S", + "kind": "memory", + "score": 0.9995805621147156, + "summary": "project:fact - [tags: http retry idempotency post put reqwest] Only retry idempotent requests automatically. GET, HEAD, PUT, DELETE are idempotent. POST is NOT \u2014 retrying a POST may create duplicate resources." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1083.9113, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 585, + "mcp_result_bytes": 666, + "wire_bytes": 703, + "reported_used_tokens": 666, + "working_set_bytes": 291278848, + "peak_working_set_bytes": 292200448 + }, + { + "query": "custom enterprise root CA not trusted by rustls on Windows", + "ranked": [ + "http-tls-roots", + "http-proxy-env" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGGWEEXGNT0936BMB92X", + "id": "01M1Y0H968Y7W7F5BVNZP14KG9", + "kind": "memory", + "score": 0.9998220801353456, + "summary": "project:fact - [tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle \u2014 the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle." + }, + { + "expansion_handle": "memory:01M1Y0CGK17KMR7W6MAVHR6YND", + "id": "01M1Y0H9682ZH5ZVS617KPZABZ", + "kind": "memory", + "score": 0.38715291023254395, + "summary": "project:fact - [tags: http proxy environment reqwest rust corporate] reqwest respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` environment variables by default (with `default-tls` or `rustls-tls`). In a corporate network, these may redirect traffic through an intercepting proxy that breaks mTLS or adds latency. To disable proxy usage entirely: `reqwest::ClientBuilder::no_proxy()`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1054.9298000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1311, + "mcp_result_bytes": 1410, + "wire_bytes": 1447, + "reported_used_tokens": 1410, + "working_set_bytes": 291278848, + "peak_working_set_bytes": 292200448 + }, + { + "query": "parsing server-sent events when a single TCP chunk contains a partial SSE frame", + "ranked": [ + "http-streaming-bodies" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGHXSFNGV11ZX9ABYM9G", + "id": "01M1Y0HA70PV7RKHKN3C9MS82D", + "kind": "memory", + "score": 0.9667426943778992, + "summary": "project:fact - [2026-09-07] [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding \u2014 a chunk may split across frame boundaries." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 926.7712, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 859, + "mcp_result_bytes": 940, + "wire_bytes": 977, + "reported_used_tokens": 940, + "working_set_bytes": 291278848, + "peak_working_set_bytes": 292200448 + }, + { + "query": "reqwest does not use the system proxy settings on Windows", + "ranked": [ + "http-proxy-env", + "http-tls-roots", + "http-connection-pooling", + "http-streaming-bodies" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGK17KMR7W6MAVHR6YND", + "id": "01M1Y0HB4GBXVB1AB6A3ZNANS5", + "kind": "memory", + "score": 0.9997830986976624, + "summary": "project:fact - [tags: http proxy environment reqwest rust corporate] reqwest respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` environment variables by default (with `default-tls` or `rustls-tls`). In a corporate network, these may redirect traffic through an intercepting proxy that breaks mTLS or adds latency. To disable proxy usage entirely: `reqwest::ClientBuilder::no_proxy()`." + }, + { + "expansion_handle": "memory:01M1Y0CGGWEEXGNT0936BMB92X", + "id": "01M1Y0HB4GB3TNGHEQ42CKN4PN", + "kind": "memory", + "score": 0.9808586239814758, + "summary": "project:fact - [tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle \u2014 the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle." + }, + { + "expansion_handle": "memory:01M1Y0CGDFD07QWYC4G8JDJGYZ", + "id": "01M1Y0HB4G2NTH3JAB1TDZXF39", + "kind": "memory", + "score": 0.719273030757904, + "summary": "project:fact - [tags: http reqwest connection-pool keep-alive rust] reqwest's `Client` holds a connection pool; always create ONE `Client` instance and clone it for each handler \u2014 cloning is cheap (Arc under the hood). Creating a `Client::new()` per request defeats connection pooling and causes TCP connection exhaustion under load. The default pool settings: max_idle_per_host=usize::MAX (unbounded), idle_timeout=90s." + }, + { + "expansion_handle": "memory:01M1Y0CGHXSFNGV11ZX9ABYM9G", + "id": "01M1Y0HB4G5JPQJK5SN4T9EG6V", + "kind": "memory", + "score": 0.7009692192077637, + "summary": "project:fact - [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding \u2014 a chunk may split across frame boundaries." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1058.9818, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2497, + "mcp_result_bytes": 2632, + "wire_bytes": 2669, + "reported_used_tokens": 2632, + "working_set_bytes": 291332096, + "peak_working_set_bytes": 292245504 + }, + { + "query": "insta snapshot tests fail in CI because output includes a timestamp", + "ranked": [ + "testing-snapshot-churn", + "ci-flaky-quarantine" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGM2F54Y82Z0FVGTW56Q", + "id": "01M1Y0HC5365DC0N73T8Z16Y2R", + "kind": "memory", + "score": 0.999855637550354, + "summary": "project:fact - [tags: testing snapshot insta assert churn rust] Snapshot tests (e.g. with the `insta` crate) fail whenever the output changes, even for intended changes. In CI, they fail loudly; locally, `cargo insta review` walks you through accepting or rejecting changes." + }, + { + "expansion_handle": "memory:01M1Y0CHEJR8TEQHZ0MBKNP52Z", + "id": "01M1Y0HC53E242SEPH98FDCQZ6", + "kind": "memory", + "score": 0.5997360348701477, + "summary": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal \u2014 a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 932.3747, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1196, + "mcp_result_bytes": 1295, + "wire_bytes": 1332, + "reported_used_tokens": 1295, + "working_set_bytes": 291606528, + "peak_working_set_bytes": 292524032 + }, + { + "query": "two test workers writing to the same temp directory path race each other", + "ranked": [ + "testing-temp-dirs-ci" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGN6SREKHFEAXY7YMS2G", + "id": "01M1Y0HD27CYGEY2V12PE4R92M", + "kind": "memory", + "score": 0.9889234900474548, + "summary": "project:fact - [tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 947.5802, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 755, + "mcp_result_bytes": 836, + "wire_bytes": 873, + "reported_used_tokens": 836, + "working_set_bytes": 291606528, + "peak_working_set_bytes": 292524032 + }, + { + "query": "test passes locally but fails on a slow CI runner due to a 100ms sleep", + "ranked": [ + "testing-time-dependent-flakes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGPCZQS2GMYCWTQKJMA0", + "id": "01M1Y0HDZW394K1X8EYZW0R700", + "kind": "memory", + "score": 0.808289110660553, + "summary": "project:fact - [tags: testing time flaky clock mock rust] Tests that depend on wall-clock time are inherently flaky under load (slow CI runners, GC pauses). Abstract time behind a trait (`Clock: Fn() -> SystemTime`) injected at construction, and supply a fake in tests. For tests checking that something happened \"within N seconds\", use a generous multiple of the expected duration (10x is not unreasonable for CI)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1065.7621, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 790, + "mcp_result_bytes": 875, + "wire_bytes": 912, + "reported_used_tokens": 875, + "working_set_bytes": 291622912, + "peak_working_set_bytes": 292548608 + }, + { + "query": "proptest found a hash collision in text normalization that example tests missed", + "ranked": [ + "testing-property-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGQD6AS5EN1NBKC3MEW7", + "id": "01M1Y0HF15YXKTWV2S8A77JQS0", + "kind": "memory", + "score": 0.9994783997535706, + "summary": "project:fact - [tags: testing property-based proptest quickcheck rust] Property-based tests (proptest, quickcheck) find edge cases that example-based tests miss. For kimetsu's memory text normalization, proptest found that zero-width joiner characters and right-to-left marks caused hash collisions. Run proptest with `PROPTEST_CASES=10000` in CI for thorough coverage." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1025.3103999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 744, + "mcp_result_bytes": 825, + "wire_bytes": 862, + "reported_used_tokens": 825, + "working_set_bytes": 291622912, + "peak_working_set_bytes": 292548608 + }, + { + "query": "set_var in tests races when cargo test runs them in parallel", + "ranked": [ + "testing-serial-vs-parallel" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGRCR5ZFR10QAFCTQWHR", + "id": "01M1Y0HG1KRPM4XC3PZ6Y103SX", + "kind": "memory", + "score": 0.9997344613075256, + "summary": "project:fact - [2026-09-07] [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1033.924, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 832, + "mcp_result_bytes": 913, + "wire_bytes": 950, + "reported_used_tokens": 913, + "working_set_bytes": 291639296, + "peak_working_set_bytes": 292564992 + }, + { + "query": "hardcoded JSON fixtures broke after a schema migration", + "ranked": [ + "testing-fixture-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGTZS0E9FZN5W5KHQQXA", + "id": "01M1Y0HH1MHSPDK99AYHGB910X", + "kind": "memory", + "score": 0.9998371601104736, + "summary": "project:fact - [2026-09-07] [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 834.7506, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 783, + "mcp_result_bytes": 864, + "wire_bytes": 901, + "reported_used_tokens": 864, + "working_set_bytes": 291655680, + "peak_working_set_bytes": 292569088 + }, + { + "query": "debug print in the MCP handler corrupts the JSON-Lines protocol stream", + "ranked": [ + "mcp-stdout-protocol" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGVVT9QFVZB1R92FVJYX", + "id": "01M1Y0HHVYHPWDC4QK31ZCWWP9", + "kind": "memory", + "score": 0.9997472167015076, + "summary": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1020.4103, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 705, + "mcp_result_bytes": 786, + "wire_bytes": 823, + "reported_used_tokens": 786, + "working_set_bytes": 291790848, + "peak_working_set_bytes": 292708352 + }, + { + "query": "kimetsu MCP tool call times out because embedding model is re-initialized every call", + "ranked": [ + "mcp-tool-timeouts", + "mcp-schema-validation", + "kimetsu-bench-remote-embedder-singleton" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGWYZ09QBQFPX4FEVE70", + "id": "01M1Y0HJVMK2P5VB95RV2JFNRB", + "kind": "memory", + "score": 0.9995898604393004, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + }, + { + "expansion_handle": "memory:01M1Y0CGZ4ETMHT4DV0PTAD4FW", + "id": "01M1Y0HJVN8AWP004G1J238T26", + "kind": "memory", + "score": 0.6027993559837341, + "summary": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array \u2014 omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error." + }, + { + "expansion_handle": "memory:01M1Y0CHTA3AQK16G6EE1QCJKZ", + "id": "01M1Y0HJVN9ZC52Y5F1PKX55BP", + "kind": "memory", + "score": 0.5117799639701843, + "summary": "project:fact - [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 917.2564, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2085, + "mcp_result_bytes": 2202, + "wire_bytes": 2239, + "reported_used_tokens": 2202, + "working_set_bytes": 292052992, + "peak_working_set_bytes": 292970496 + }, + { + "query": "env var set after host launch is not visible to the MCP server process", + "ranked": [ + "mcp-env-propagation", + "kimetsu-daemon-lifecycle" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGY1YEEKFJHX4JCZYHY6", + "id": "01M1Y0HKRVC9MN1NMSBYF4EXZD", + "kind": "memory", + "score": 0.9984827637672424, + "summary": "project:fact - [2026-09-07] [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment \u2014 changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate." + }, + { + "expansion_handle": "memory:01M1Y0CHFNNPJ5W1HT08QTSPX7", + "id": "01M1Y0HKRVR71DCK0DABGZ4HRT", + "kind": "memory", + "score": 0.9977922439575196, + "summary": "project:fact - [2026-09-07] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1096.9579, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1267, + "mcp_result_bytes": 1366, + "wire_bytes": 1403, + "reported_used_tokens": 1366, + "working_set_bytes": 292093952, + "peak_working_set_bytes": 293019648 + }, + { + "query": "MCP tool call fails because a required field is missing from the JSON input", + "ranked": [ + "mcp-schema-validation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGZ4ETMHT4DV0PTAD4FW", + "id": "01M1Y0HMTMM69S0EZHKPWDS0V7", + "kind": "memory", + "score": 0.998538613319397, + "summary": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array \u2014 omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 985.3119, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 798, + "mcp_result_bytes": 879, + "wire_bytes": 916, + "reported_used_tokens": 879, + "working_set_bytes": 292315136, + "peak_working_set_bytes": 293232640 + }, + { + "query": "Claude Code rejects the tool name with a hyphen in it", + "ranked": [ + "mcp-tool-naming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CH08W8KRWZHZ6QYESXCW", + "id": "01M1Y0HNSC2ZZVFFTPVYVVQ576", + "kind": "memory", + "score": 0.9982439279556274, + "summary": "project:fact - [tags: mcp tool naming convention kimetsu] MCP tool names must be valid identifiers for all host agents. Claude Code restricts tool names to `[a-zA-Z0-9_-]` and max 64 chars. Use `snake_case` (kimetsu_brain_context, kimetsu_brain_record) \u2014 hyphen is technically allowed but some hosts reject it." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 969.4044, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 687, + "mcp_result_bytes": 768, + "wire_bytes": 805, + "reported_used_tokens": 768, + "working_set_bytes": 292732928, + "peak_working_set_bytes": 293650432 + }, + { + "query": "MCP response path uses backslashes and the host rejects it", + "ranked": [ + "mcp-transcript-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CH1D4KC6M56416X92HX3", + "id": "01M1Y0HPQRV95FWGKF1JRE5SJV", + "kind": "memory", + "score": 0.9984637498855592, + "summary": "project:fact - [tags: mcp transcript paths kimetsu hooks runs] kimetsu writes run transcripts to `/.kimetsu/runs//`. The post-session hook reads the latest run's transcript to trigger memory harvest. On Windows, the path uses backslashes internally but the MCP JSON must use forward slashes or the host may reject path-type arguments." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 999.9429, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 724, + "mcp_result_bytes": 805, + "wire_bytes": 842, + "reported_used_tokens": 805, + "working_set_bytes": 292777984, + "peak_working_set_bytes": 293691392 + }, + { + "query": "AWS credentials not found \u2014 which env var does kimetsu read for Bedrock?", + "ranked": [ + "aws-credentials-chain", + "aws-region-resolution", + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CH2MJMDSTCRQ0SR4KPC5", + "id": "01M1Y0HQQ398V77CSDZW4EZD8P", + "kind": "memory", + "score": 0.9990235567092896, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + }, + { + "expansion_handle": "memory:01M1Y0CH4021WNS0JP2HVKJDXQ", + "id": "01M1Y0HQQ37Y71X2M3JX910V15", + "kind": "memory", + "score": 0.9968422651290894, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1Y0CDKZJ260T9MFP5RRHHQY", + "id": "01M1Y0HQQ368KCNEAFQTHC0KH4", + "kind": "memory", + "score": 0.9849756360054016, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) \u2014 for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1Y0CDSEBRPP3R78RVYDDGZA", + "id": "01M1Y0HQQ3NKKRBFHX5B9PTF3A", + "kind": "memory", + "score": 0.9203452467918396, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1063.1221, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3455, + "mcp_result_bytes": 3618, + "wire_bytes": 3655, + "reported_used_tokens": 3618, + "working_set_bytes": 292786176, + "peak_working_set_bytes": 293703680 + }, + { + "query": "Bedrock InvokeModel fails because the region is not configured", + "ranked": [ + "aws-region-resolution", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CH4021WNS0JP2HVKJDXQ", + "id": "01M1Y0HRRHCAC0BZVVRY4J0N27", + "kind": "memory", + "score": 0.99688321352005, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1Y0CDSEBRPP3R78RVYDDGZA", + "id": "01M1Y0HRRH1M1CCVSAB131AVXB", + "kind": "memory", + "score": 0.6450709104537964, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1074.4991, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1810, + "mcp_result_bytes": 1929, + "wire_bytes": 1966, + "reported_used_tokens": 1929, + "working_set_bytes": 292790272, + "peak_working_set_bytes": 293711872 + }, + { + "query": "how do I handle ThrottlingException from Bedrock with exponential backoff?", + "ranked": [ + "aws-retry-throttling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CH5512SPEE30NNSG0SKK", + "id": "01M1Y0HSSWGMZNXARFSF14ZT2C", + "kind": "memory", + "score": 0.9997082352638244, + "summary": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with \u00b125% jitter." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1095.8706, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 771, + "mcp_result_bytes": 868, + "wire_bytes": 905, + "reported_used_tokens": 868, + "working_set_bytes": 292864000, + "peak_working_set_bytes": 293769216 + }, + { + "query": "generating a presigned S3 URL for brain export without exposing credentials", + "ranked": [ + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CH6AWWAV734TFW14SHW0", + "id": "01M1Y0HTW6E6X8RQ9KX4V2W2NH", + "kind": "memory", + "score": 0.9990487694740297, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time \u2014 clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1040.3455999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 875, + "mcp_result_bytes": 956, + "wire_bytes": 993, + "reported_used_tokens": 956, + "working_set_bytes": 292958208, + "peak_working_set_bytes": 293875712 + }, + { + "query": "IMDSv2 token required for instance metadata \u2014 PUT before GET", + "ranked": [ + "aws-instance-metadata" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CH7CGS47V8TBVANKJECQ", + "id": "01M1Y0HVWN30PE4C29789AMEBQ", + "kind": "memory", + "score": 0.9997182488441468, + "summary": "project:fact - [2026-09-07] [tags: aws imds instance-metadata ec2 token] The AWS Instance Metadata Service v2 (IMDSv2) requires a session token: PUT `http://169.254.169.254/latest/api/token` with `X-aws-ec2-metadata-token-ttl-seconds: 21600` to get a token, then GET metadata with `X-aws-ec2-metadata-token: `. IMDSv1 (no token) is disabled on hardened instances. The metadata endpoint is only reachable from within EC2 \u2014 a connection timeout means you're not on EC2." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1043.1979000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 851, + "mcp_result_bytes": 932, + "wire_bytes": 969, + "reported_used_tokens": 932, + "working_set_bytes": 292986880, + "peak_working_set_bytes": 293904384 + }, + { + "query": "Cargo cache key strategy for GitHub Actions to avoid toolchain version collisions", + "ranked": [ + "ci-cache-keys" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHAQ3R9BP3JD09Z9JTHP", + "id": "01M1Y0HWXH9FRTKWNN4FDMZKVN", + "kind": "memory", + "score": 0.998869240283966, + "summary": "project:fact - [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key \u2014 macOS and Windows have incompatible artifact formats." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1106.5658, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 788, + "mcp_result_bytes": 869, + "wire_bytes": 906, + "reported_used_tokens": 869, + "working_set_bytes": 292986880, + "peak_working_set_bytes": 293904384 + }, + { + "query": "CI matrix has 18 jobs and costs too much \u2014 how do I reduce it?", + "ranked": [ + "ci-matrix-explosion" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHBK2CM26GS0FWYJYK27", + "id": "01M1Y0HY0HXB66Q4JBQP9AAPM8", + "kind": "memory", + "score": 0.999057948589325, + "summary": "project:fact - [tags: ci github-actions matrix jobs resources] A CI matrix combining OS (3) x Rust toolchain (3) x features (2) = 18 jobs. Each spawns a runner; at $0.008/min for Ubuntu and $0.016/min for Windows, a 10-minute build costs $2.40 per push. Reduce: test the full matrix only on PRs to main; on feature branches, test only Linux+stable." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1091.4141, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 722, + "mcp_result_bytes": 803, + "wire_bytes": 840, + "reported_used_tokens": 803, + "working_set_bytes": 293109760, + "peak_working_set_bytes": 294031360 + }, + { + "query": "GitHub Actions secret accidentally printed in build logs", + "ranked": [ + "ci-secrets-masking", + "ci-cache-keys", + "ci-artifact-retention" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHCK07ASK7V6B3J3GTZF", + "id": "01M1Y0HZ22003GCZFW4K0C89FR", + "kind": "memory", + "score": 0.9963951706886292, + "summary": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output \u2014 but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable." + }, + { + "expansion_handle": "memory:01M1Y0CHAQ3R9BP3JD09Z9JTHP", + "id": "01M1Y0HZ2235HE1B7HFM2QEGGD", + "kind": "memory", + "score": 0.4342843890190125, + "summary": "project:fact - [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key \u2014 macOS and Windows have incompatible artifact formats." + }, + { + "expansion_handle": "memory:01M1Y0CHDPAW207Z23THV886HR", + "id": "01M1Y0HZ22HKS1STVK0RH3FMTA", + "kind": "memory", + "score": 0.3422144949436188, + "summary": "project:fact - [tags: ci github-actions artifacts retention benchmark] GitHub Actions artifacts are retained for 90 days (default). For benchmark results, use `actions/upload-artifact` with `retention-days: 365` for long-term tracking. The free tier has 500MB storage \u2014 per-combo JSON files from kimetsu bench (each ~60KB) add up fast if you upload them on every push." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1024.6078, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1791, + "mcp_result_bytes": 1908, + "wire_bytes": 1945, + "reported_used_tokens": 1908, + "working_set_bytes": 293412864, + "peak_working_set_bytes": 294330368 + }, + { + "query": "how long do GitHub Actions artifacts persist and what's the storage limit?", + "ranked": [ + "ci-artifact-retention" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHDPAW207Z23THV886HR", + "id": "01M1Y0J0220H0YWQNY8G84V9BT", + "kind": "memory", + "score": 0.999624252319336, + "summary": "project:fact - [tags: ci github-actions artifacts retention benchmark] GitHub Actions artifacts are retained for 90 days (default). For benchmark results, use `actions/upload-artifact` with `retention-days: 365` for long-term tracking. The free tier has 500MB storage \u2014 per-combo JSON files from kimetsu bench (each ~60KB) add up fast if you upload them on every push." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1105.3394, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 744, + "mcp_result_bytes": 825, + "wire_bytes": 862, + "reported_used_tokens": 825, + "working_set_bytes": 293425152, + "peak_working_set_bytes": 294346752 + }, + { + "query": "timing-based test flake in CI \u2014 quarantine or fix?", + "ranked": [ + "ci-flaky-quarantine", + "testing-time-dependent-flakes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHEJR8TEQHZ0MBKNP52Z", + "id": "01M1Y0J14TTNJHCB99A75JT2Z5", + "kind": "memory", + "score": 0.9994743466377258, + "summary": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal \u2014 a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output." + }, + { + "expansion_handle": "memory:01M1Y0CGPCZQS2GMYCWTQKJMA0", + "id": "01M1Y0J14TXMJQK6F1R9H3TZ3V", + "kind": "memory", + "score": 0.9849997162818908, + "summary": "project:fact - [tags: testing time flaky clock mock rust] Tests that depend on wall-clock time are inherently flaky under load (slow CI runners, GC pauses). Abstract time behind a trait (`Clock: Fn() -> SystemTime`) injected at construction, and supply a fake in tests. For tests checking that something happened \"within N seconds\", use a generous multiple of the expected duration (10x is not unreasonable for CI)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1041.5246, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1340, + "mcp_result_bytes": 1443, + "wire_bytes": 1480, + "reported_used_tokens": 1443, + "working_set_bytes": 293679104, + "peak_working_set_bytes": 294604800 + }, + { + "query": "kimetsu doctor says the MCP server is running \u2014 how do I stop it before an update?", + "ranked": [ + "kimetsu-daemon-lifecycle", + "mcp-env-propagation", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHFNNPJ5W1HT08QTSPX7", + "id": "01M1Y0J25HH8E2RMMASRF5AAQ9", + "kind": "memory", + "score": 0.9989782571792604, + "summary": "project:fact - [2026-09-07] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1Y0CGY1YEEKFJHX4JCZYHY6", + "id": "01M1Y0J25H7C70P1W6CG68EXZ4", + "kind": "memory", + "score": 0.9049031734466552, + "summary": "project:fact - [2026-09-07] [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment \u2014 changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate." + }, + { + "expansion_handle": "memory:01M1Y0CDGSHENNVZQKGWMDBNDR", + "id": "01M1Y0J25HTAADKFJT5FSKY3V3", + "kind": "memory", + "score": 0.4812128245830536, + "summary": "project:fact - [2026-09-07] [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1053.3948, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2046, + "mcp_result_bytes": 2211, + "wire_bytes": 2248, + "reported_used_tokens": 2211, + "working_set_bytes": 293687296, + "peak_working_set_bytes": 294604800 + }, + { + "query": "noise capsules consuming token budget without contributing retrieval signal", + "ranked": [ + "kimetsu-capsule-budgets" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHGPK9383N7Y4V5RY365", + "id": "01M1Y0J3644EMGQZMTFYY9PP4A", + "kind": "memory", + "score": 0.9997420907020568, + "summary": "project:fact - [tags: kimetsu capsule tokens budget retrieval] kimetsu retrieval enforces a token budget per capsule type: memory capsules are capped at 6000 tokens total (across all retrieved memories), file capsules at 3000 tokens. When a memory is large and would exceed the budget, it is truncated at a sentence boundary. The budget is enforced AFTER reranking \u2014 reranking may reorder results so that a truncated high-ranked memory displaces a full lower-ranked one." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 819.1426, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 847, + "mcp_result_bytes": 928, + "wire_bytes": 965, + "reported_used_tokens": 928, + "working_set_bytes": 293687296, + "peak_working_set_bytes": 294604800 + }, + { + "query": "kimetsu_brain_record writes to the wrong brain location \u2014 user vs project scope", + "ranked": [ + "kimetsu-memory-scopes", + "kimetsu-write-tools-gate", + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHHMQS2VGXDD5KZTKWV1", + "id": "01M1Y0J402MT89QAK5X7RV7TT9", + "kind": "memory", + "score": 0.999030828475952, + "summary": "project:fact - [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available \u2014 if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope." + }, + { + "expansion_handle": "memory:01M1Y0CHMVHR9GRRHBAB5NZNDN", + "id": "01M1Y0J4033EFDRJXJFBGSAARK", + "kind": "memory", + "score": 0.9838979840278624, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level \u2014 disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1Y0CDW1WMXYA9MGV0TK68J6", + "id": "01M1Y0J403AT897EV9WNEXC9RY", + "kind": "memory", + "score": 0.3852712512016296, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1017.4761000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2098, + "mcp_result_bytes": 2215, + "wire_bytes": 2252, + "reported_used_tokens": 2215, + "working_set_bytes": 293687296, + "peak_working_set_bytes": 294604800 + }, + { + "query": "how do I configure kimetsu to use Claude Haiku for harvesting but Opus for the agent?", + "ranked": [ + "kimetsu-distiller-config" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHJRD9YZNN0K6RBP37DH", + "id": "01M1Y0J4ZQZGZHSDQGAJKR4B54", + "kind": "memory", + "score": 0.9989088773727416, + "summary": "project:fact - [tags: kimetsu distiller harvest config provider] The kimetsu distiller (auto-harvester) uses a SEPARATE provider configuration from the main agent: `distiller.provider`, `distiller.model`, `distiller.api_key`. This allows running the agent on an expensive model (Claude Opus) while harvesting with a cheap model (Claude Haiku). If `distiller.provider` is not set, it inherits `provider`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1046.5562, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 778, + "mcp_result_bytes": 859, + "wire_bytes": 896, + "reported_used_tokens": 859, + "working_set_bytes": 293703680, + "peak_working_set_bytes": 294621184 + }, + { + "query": "first agent turn is slow because kimetsu proactive hook runs embedding inference", + "ranked": [ + "kimetsu-proactive-hooks", + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHKTX9JMQPFAQV1QS487", + "id": "01M1Y0J60W733WBNTEYV12MTVM", + "kind": "memory", + "score": 0.999568521976471, + "summary": "project:fact - [2026-09-07] [tags: kimetsu proactive hooks context injection] kimetsu's proactive context injection runs before each agent turn (pre-turn hook) and injects relevant memories into the system prompt prefix. The hook invocation adds latency to the first token: embedding inference + vector search + reranking + context formatting. On a cold start, this can be 1-3 seconds." + }, + { + "expansion_handle": "memory:01M1Y0CGWYZ09QBQFPX4FEVE70", + "id": "01M1Y0J60X8ZQFEZWFD2JPX44Q", + "kind": "memory", + "score": 0.9405298233032228, + "summary": "project:fact - [2026-09-07] [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1104.3481, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1403, + "mcp_result_bytes": 1502, + "wire_bytes": 1539, + "reported_used_tokens": 1502, + "working_set_bytes": 293703680, + "peak_working_set_bytes": 294621184 + }, + { + "query": "make the kimetsu brain read-only for certain repos on a shared remote server", + "ranked": [ + "kimetsu-write-tools-gate", + "remote-ingest-split-roots", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHMVHR9GRRHBAB5NZNDN", + "id": "01M1Y0J72ZD54011XJVEBC504F", + "kind": "memory", + "score": 0.997682809829712, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level \u2014 disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1Y0CDERH0MV40CSBCQDEQYA", + "id": "01M1Y0J72ZW401EAWKGM35EPNB", + "kind": "memory", + "score": 0.9957050681114196, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo \u2014 lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1Y0CDGSHENNVZQKGWMDBNDR", + "id": "01M1Y0J72ZYP6RWND2YZYTN3FA", + "kind": "memory", + "score": 0.9909282326698304, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 955.7925, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2725, + "mcp_result_bytes": 2890, + "wire_bytes": 2927, + "reported_used_tokens": 2890, + "working_set_bytes": 293724160, + "peak_working_set_bytes": 294641664 + }, + { + "query": "kimetsu FTS search misses 'deadlocking' when memory says 'deadlock'", + "ranked": [ + "kimetsu-query-stemming", + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHR9RRWWX5FQ2Z9H6B27", + "id": "01M1Y0J80S1SQ9MW4Z8H77X8HE", + "kind": "memory", + "score": 0.9904030561447144, + "summary": "project:fact - [2026-09-07] [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression." + }, + { + "expansion_handle": "memory:01M1Y0CDDKV46DFCQB5VK0TWX5", + "id": "01M1Y0J80ST8HAEZF5F6K7R0DQ", + "kind": "memory", + "score": 0.91664320230484, + "summary": "project:fact - [2026-09-07] [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure \u2014 `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 937.5399, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1363, + "mcp_result_bytes": 1478, + "wire_bytes": 1515, + "reported_used_tokens": 1478, + "working_set_bytes": 293744640, + "peak_working_set_bytes": 294658048 + }, + { + "query": "how does pool size affect retrieval recall and latency in the bench?", + "ranked": [ + "kimetsu-rerank-pool" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHSBCRWVC89BQK1ZX1Z7", + "id": "01M1Y0J8Y4275MZ24TP2QA28BK", + "kind": "memory", + "score": 0.9998373985290528, + "summary": "project:fact - [tags: kimetsu reranker pool size ann retrieval] kimetsu's retrieval pipeline: ANN (approximate nearest neighbor) retrieves a pool of candidates, then the reranker reorders them, then the top-K are returned. The pool size (default 6 for production, 12 in bench) controls the recall-latency tradeoff: larger pool = higher recall = more reranker calls = more latency. For the jina-tiny reranker, pool 12 adds ~80ms vs pool 6." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1016.9318000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 813, + "mcp_result_bytes": 894, + "wire_bytes": 931, + "reported_used_tokens": 894, + "working_set_bytes": 293756928, + "peak_working_set_bytes": 294678528 + }, + { + "query": "second embedder in a remote bench run gets worse results than the first", + "ranked": [ + "kimetsu-bench-remote-embedder-singleton" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHTA3AQK16G6EE1QCJKZ", + "id": "01M1Y0J9XWJGPSV3QKW8K6080B", + "kind": "memory", + "score": 0.9939629435539246, + "summary": "project:fact - [2026-09-07] [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1042.4408, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 895, + "mcp_result_bytes": 976, + "wire_bytes": 1013, + "reported_used_tokens": 976, + "working_set_bytes": 293756928, + "peak_working_set_bytes": 294678528 + }, + { + "query": "what is the expected JSON schema for kimetsu brain bench dataset files?", + "ranked": [ + "kimetsu-eval-fixture-shape", + "testing-fixture-drift", + "kimetsu-mrr-metric", + "mcp-schema-validation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHVB50Y5BXTDMZRNKFGR", + "id": "01M1Y0JAYSRYC3MS1F9ZA4ND8Z", + "kind": "memory", + "score": 0.9996767044067384, + "summary": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` \u2014 a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases)." + }, + { + "expansion_handle": "memory:01M1Y0CGTZS0E9FZN5W5KHQQXA", + "id": "01M1Y0JAYS490H835J6MHDP267", + "kind": "memory", + "score": 0.9682880640029908, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + }, + { + "expansion_handle": "memory:01M1Y0CHWG3T7N3ZZ8F2Z1EQHR", + "id": "01M1Y0JAYS6V03BPW4DHTGP8MF", + "kind": "memory", + "score": 0.8818408250808716, + "summary": "project:fact - [tags: kimetsu bench mrr recall metrics evaluation] kimetsu bench reports MRR (Mean Reciprocal Rank) and Recall@K. MRR is 1/rank_of_first_relevant_result, averaged across cases; it penalizes models that rank the correct answer 2nd or 3rd. Recall@K is the fraction of cases where at least one relevant answer appears in the top K." + }, + { + "expansion_handle": "memory:01M1Y0CGZ4ETMHT4DV0PTAD4FW", + "id": "01M1Y0JAYSB72ZBGPSP5DT8HP0", + "kind": "memory", + "score": 0.6527947187423706, + "summary": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array \u2014 omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1067.3744000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2424, + "mcp_result_bytes": 2603, + "wire_bytes": 2640, + "reported_used_tokens": 2603, + "working_set_bytes": 293752832, + "peak_working_set_bytes": 294678528 + }, + { + "query": "what does MRR mean and how do I interpret a 0.01 difference between combos?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1027.9297000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 293752832, + "peak_working_set_bytes": 294678528 + }, + { + "query": "SQLITE_BUSY keeps appearing even with WAL mode enabled", + "ranked": [ + "sqlite-busy-timeout-wal", + "sqlite-wal-network-drive" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE63YDGRR9GCFM6A405N", + "id": "01M1Y0JD04D9Z44NWBTF4ETWT4", + "kind": "memory", + "score": 0.9982662796974182, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + }, + { + "expansion_handle": "memory:01M1Y0CE84FR94JFPZ2CTRK5KY", + "id": "01M1Y0JD057X5VQCJATC8Y7GNA", + "kind": "memory", + "score": 0.7844027280807495, + "summary": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1094.0042999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1423, + "mcp_result_bytes": 1522, + "wire_bytes": 1559, + "reported_used_tokens": 1522, + "working_set_bytes": 293773312, + "peak_working_set_bytes": 294690816 + }, + { + "query": "my brain file got huge again right after I compacted it", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 985.3113999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 293773312, + "peak_working_set_bytes": 294690816 + }, + { + "query": "all my FTS queries stopped returning results after I changed the tokenizer config", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1052.9347, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 293773312, + "peak_working_set_bytes": 294690816 + }, + { + "query": "something is preventing the kimetsu binary from being replaced during update", + "ranked": [ + "kimetsu-daemon-lifecycle", + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHFNNPJ5W1HT08QTSPX7", + "id": "01M1Y0JG2DKPRY5W4Z0MBNBAMM", + "kind": "memory", + "score": 0.9678457975387572, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed \u2014 the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1Y0CE3VN854570296AQJ8B8", + "id": "01M1Y0JG2EAX2663C2P9991NE0", + "kind": "memory", + "score": 0.9395453929901124, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics \u2014 mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 0.6666666666666666, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 942.629, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1657, + "mcp_result_bytes": 1756, + "wire_bytes": 1793, + "reported_used_tokens": 1756, + "working_set_bytes": 293773312, + "peak_working_set_bytes": 294690816 + }, + { + "query": "tool call results not appearing in the context \u2014 is the semantic floor too high?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1074.0403000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 293781504, + "peak_working_set_bytes": 294707200 + }, + { + "query": "CARGO_INCREMENTAL=0 in CI prevents a class of spurious compilation errors", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CEKN0QBDZ73SACBN6Y6X", + "id": "01M1Y0JJ11K46MD3BXJC930ZAC", + "kind": "memory", + "score": 0.7995238304138184, + "summary": "project:fact - [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1013.2102999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 877, + "mcp_result_bytes": 958, + "wire_bytes": 995, + "reported_used_tokens": 958, + "working_set_bytes": 293826560, + "peak_working_set_bytes": 294748160 + }, + { + "query": "how do I check whether my Cargo workspace respects the MSRV constraint?", + "ranked": [ + "cargo-msrv", + "cargo-dev-dep-leak", + "cargo-patch-section", + "cargo-target-dir-sharing" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CEPM3E4XMZRG0JF64DYX", + "id": "01M1Y0JK0NAYC2Y2XGXPCERRXX", + "kind": "memory", + "score": 0.9921064376831056, + "summary": "project:fact - [tags: cargo rust msrv edition compatibility] Set `rust-version` in each `Cargo.toml` to declare the minimum supported Rust version (MSRV). Cargo enforces this with `--check`: `cargo check` fails if the toolchain is older than `rust-version`. Keep MSRV as old as your oldest supported deployment target." + }, + { + "expansion_handle": "memory:01M1Y0CEHKWTJ8ZMXANWDJAMCD", + "id": "01M1Y0JK0PSEF7D2VKF306HW81", + "kind": "memory", + "score": 0.887407660484314, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + }, + { + "expansion_handle": "memory:01M1Y0CENN1SARTT7M8KTJ5XE9", + "id": "01M1Y0JK0PB1YAZVJD33VRQFW0", + "kind": "memory", + "score": 0.7220955491065979, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace \u2014 including transitive deps \u2014 that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1Y0CEJNCGQE145VV7PMAA9T", + "id": "01M1Y0JK0PYA75CWSCY9RQM5HR", + "kind": "memory", + "score": 0.4095200598239898, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps \u2014 use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1017.9978999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2682, + "mcp_result_bytes": 2821, + "wire_bytes": 2858, + "reported_used_tokens": 2821, + "working_set_bytes": 293826560, + "peak_working_set_bytes": 294752256 + }, + { + "query": "rusqlite connection opened but ON DELETE CASCADE cascade never fires", + "ranked": [ + "sqlite-foreign-keys-default-off" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CEB89X22N5Z655769N3B", + "id": "01M1Y0JM10ZQQCM3J90RAVCQAM", + "kind": "memory", + "score": 0.9922945499420166, + "summary": "project:fact - [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting \u2014 every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1019.9111, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 735, + "mcp_result_bytes": 816, + "wire_bytes": 853, + "reported_used_tokens": 816, + "working_set_bytes": 293826560, + "peak_working_set_bytes": 294752256 + }, + { + "query": "I cannot connect to kimetsu-remote \u2014 something about TLS cert validation failed", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 976.5345, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 293826560, + "peak_working_set_bytes": 294752256 + }, + { + "query": "graceful shutdown fails because in-flight SQLite queries are still running when pool closes", + "ranked": [ + "tokio-shutdown-ordering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGCDX7QHSQ0KP7DJW3ES", + "id": "01M1Y0JNYYNT98GS85HHZ8ZRXA", + "kind": "memory", + "score": 0.9996342658996582, + "summary": "project:fact - [2026-09-07] [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries \u2014 the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 962.4521000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 947, + "mcp_result_bytes": 1028, + "wire_bytes": 1065, + "reported_used_tokens": 1028, + "working_set_bytes": 293826560, + "peak_working_set_bytes": 294752256 + }, + { + "query": "kimetsu-remote response takes 8 seconds \u2014 which stage is slow?", + "ranked": [ + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGWYZ09QBQFPX4FEVE70", + "id": "01M1Y0JPX6BP5GH2AYQZESWGBR", + "kind": "memory", + "score": 0.9876242876052856, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking \u2014 in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize \u2014 keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1071.5462, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 858, + "mcp_result_bytes": 939, + "wire_bytes": 976, + "reported_used_tokens": 939, + "working_set_bytes": 293826560, + "peak_working_set_bytes": 294752256 + }, + { + "query": "git reflog to rescue accidentally deleted branch", + "ranked": [ + "git-reflog-rescue" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CG3J6F6PE488KD89FHD4", + "id": "01M1Y0JQZ0JVWA00YKB1H9GBGP", + "kind": "memory", + "score": 0.998464822769165, + "summary": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone \u2014 they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only \u2014 remote reflog is not accessible via normal git commands." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1096.6826, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 761, + "mcp_result_bytes": 842, + "wire_bytes": 879, + "reported_used_tokens": 842, + "working_set_bytes": 293826560, + "peak_working_set_bytes": 294752256 + }, + { + "query": "git submodule --remote advances the pinned SHA unexpectedly", + "ranked": [ + "git-submodule-pinning", + "git-reflog-rescue", + "ci-secrets-masking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CG2J6XC8ARPF2CP7BP61", + "id": "01M1Y0JS1KPJK0G5YQKRPCTMKY", + "kind": "memory", + "score": 0.9998551607131958, + "summary": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip \u2014 this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version." + }, + { + "expansion_handle": "memory:01M1Y0CG3J6F6PE488KD89FHD4", + "id": "01M1Y0JS1KGCJPKHN9BVZJ7ED4", + "kind": "memory", + "score": 0.8857361078262329, + "summary": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone \u2014 they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only \u2014 remote reflog is not accessible via normal git commands." + }, + { + "expansion_handle": "memory:01M1Y0CHCK07ASK7V6B3J3GTZF", + "id": "01M1Y0JS1K0RTSZMMFTC19D17B", + "kind": "memory", + "score": 0.8434544205665588, + "summary": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output \u2014 but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 995.8891, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1771, + "mcp_result_bytes": 1888, + "wire_bytes": 1925, + "reported_used_tokens": 1888, + "working_set_bytes": 293826560, + "peak_working_set_bytes": 294752256 + }, + { + "query": "axum SSE streaming drops the last event when client disconnects", + "ranked": [ + "http-streaming-bodies" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGHXSFNGV11ZX9ABYM9G", + "id": "01M1Y0JT06VA79FNGD2B30AQE9", + "kind": "memory", + "score": 0.9926375150680542, + "summary": "project:fact - [2026-09-07] [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding \u2014 a chunk may split across frame boundaries." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1047.3868, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 859, + "mcp_result_bytes": 940, + "wire_bytes": 977, + "reported_used_tokens": 940, + "working_set_bytes": 293826560, + "peak_working_set_bytes": 294752256 + }, + { + "query": "how do I detect that I am running inside a git worktree vs the main checkout?", + "ranked": [ + "git-worktree-brain-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFYDVF1XT2NJ4R1CE9KK", + "id": "01M1Y0JV20CS3ZXDBNP9KMQ8AF", + "kind": "memory", + "score": 0.9857924580574036, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root \u2014 if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1080.4377, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 881, + "mcp_result_bytes": 962, + "wire_bytes": 999, + "reported_used_tokens": 962, + "working_set_bytes": 293826560, + "peak_working_set_bytes": 294752256 + }, + { + "query": "ONNX Runtime intra-op threads causing CPU contention during parallel bench", + "ranked": [ + "onnx-ort-threading", + "tokio-blocking-in-async" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFXBEVH5NSD9GP4MD291", + "id": "01M1Y0JW2T99XCP5XBFGPH7H44", + "kind": "memory", + "score": 0.9999210834503174, + "summary": "project:fact - [tags: onnx ort thread-pool parallelism cpu] ORT (ONNX Runtime) creates its own inter-op and intra-op thread pools. In a multi-process bench setup, each child inherits these pools and they compete for CPU cores. Set `SessionOptionsBuilder::with_intra_threads(1).with_inter_threads(1)` if you're running many parallel bench processes \u2014 this sacrifices per-inference throughput for lower contention." + }, + { + "expansion_handle": "memory:01M1Y0CG4GXEDWCNR553B345ZY", + "id": "01M1Y0JW2VP1TCSPS0G30X7782", + "kind": "memory", + "score": 0.5390238761901855, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking \u2014 never call rusqlite directly from an async fn without spawn_blocking." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 947.4401, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1328, + "mcp_result_bytes": 1427, + "wire_bytes": 1464, + "reported_used_tokens": 1427, + "working_set_bytes": 293826560, + "peak_working_set_bytes": 294752256 + }, + { + "query": "what is the right way to supply AWS session token alongside access key and secret?", + "ranked": [ + "aws-credentials-chain" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CH2MJMDSTCRQ0SR4KPC5", + "id": "01M1Y0JX0DQ8K13P0MV9FK08DW", + "kind": "memory", + "score": 0.9493365287780762, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1018.4159, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 895, + "mcp_result_bytes": 976, + "wire_bytes": 1013, + "reported_used_tokens": 976, + "working_set_bytes": 293953536, + "peak_working_set_bytes": 294866944 + } + ], + "id": "existing-development-100", + "dimension": "retrieval", + "tier": "hard", + "score": 0.8182539682539681, + "skipped": false, + "detail": "positive-recall@4=0.84 mrr=0.85 stale-hit=n/a resolution=n/a false-injection=0.538 (n=13) positive-n=197 negative-n=13 (210 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 0.8182539682539681, + 1 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 0.8182539682539681, + "n": 1, + "ci95": null + } + }, + "overall_index": 0.8182539682539681, + "scenario_weighted_index": 0.8182539682539681 +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-structured-facts/results/development/1-candidate.stderr.log b/docs/audits/2026-09-07-structured-facts/results/development/1-candidate.stderr.log new file mode 100644 index 0000000..56e4172 --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/results/development/1-candidate.stderr.log @@ -0,0 +1,4 @@ +brainbench: 1 scenario(s) to run + [1/1] existing-development-100 | dim=retrieval tier=hard ... + -> score=0.82 | positive-recall@4=0.84 mrr=0.85 stale-hit=n/a resolution=n/a false-injection=0.538 (n=13) positive-n=197 negative-n=13 (210 queries) +kbench brainbench: report saved -> E:\tmp\kimetsu-brain-hardening\bench\local\runs\brainbench\2026-09-07T13-23-10.2781908Z.json diff --git a/docs/audits/2026-09-07-structured-facts/results/development/1-candidate.stdout.log b/docs/audits/2026-09-07-structured-facts/results/development/1-candidate.stdout.log new file mode 100644 index 0000000..e50ffdf --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/results/development/1-candidate.stdout.log @@ -0,0 +1,6811 @@ +{ + "generated_at": "2026-09-07T13:23:10.2766037Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-retrieval\\development-100.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "test_env_lock inside with_user_brain_disabled deadlock", + "ranked": [ + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDDKV46DFCQB5VK0TWX5", + "id": "01M1Y0CJM7QHXE6WM12F244071", + "kind": "memory", + "score": 0.9999488592147828, + "summary": "project:fact - [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure — `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1064.4432, + "first_query": true, + "server_startup_ms": 74.8257, + "model_text_bytes": 796, + "mcp_result_bytes": 877, + "wire_bytes": 912, + "reported_used_tokens": 877, + "working_set_bytes": 226983936, + "peak_working_set_bytes": 248229888 + }, + { + "query": "why does my test hang after calling with_user_brain_disabled when I also lock test_env_lock?", + "ranked": [ + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDDKV46DFCQB5VK0TWX5", + "id": "01M1Y0CKA74STYV56A8DWW7RHE", + "kind": "memory", + "score": 0.9990190267562866, + "summary": "project:fact - [2026-09-07] [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure — `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 811.5477, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 808, + "mcp_result_bytes": 889, + "wire_bytes": 924, + "reported_used_tokens": 889, + "working_set_bytes": 229031936, + "peak_working_set_bytes": 248229888 + }, + { + "query": "ingest_repo_at_root brain_root files_root kimetsu remote", + "ranked": [ + "remote-ingest-split-roots", + "kimetsu-write-tools-gate", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDERH0MV40CSBCQDEQYA", + "id": "01M1Y0CM3S0J02KTEWJZHJDH9F", + "kind": "memory", + "score": 0.999886393547058, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1Y0CHMVHR9GRRHBAB5NZNDN", + "id": "01M1Y0CM3SPDAC35Y97RRGSCBV", + "kind": "memory", + "score": 0.8439717888832092, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level — disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1Y0CDGSHENNVZQKGWMDBNDR", + "id": "01M1Y0CM3SN1TT7HY4H6941GZW", + "kind": "memory", + "score": 0.8363722562789917, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 922.0899000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2726, + "mcp_result_bytes": 2891, + "wire_bytes": 2926, + "reported_used_tokens": 2891, + "working_set_bytes": 251822080, + "peak_working_set_bytes": 252735488 + }, + { + "query": "why does the remote server index the wrong directory when I run kimetsu brain ingest?", + "ranked": [ + "remote-ingest-split-roots", + "onnx-dim-mismatch" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDERH0MV40CSBCQDEQYA", + "id": "01M1Y0CN0JZ8HA05XDW2W68JQC", + "kind": "memory", + "score": 0.9836117625236512, + "summary": "project:fact - [2026-09-07] [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1Y0CFS76DA1V14BVQR0NX3T", + "id": "01M1Y0CN0J8S4QAJACBX273RGC", + "kind": "memory", + "score": 0.3657674789428711, + "summary": "project:fact - [2026-09-07] [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results — the ANN index shape mismatch isn't always caught at runtime." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1032.2575000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1800, + "mcp_result_bytes": 1899, + "wire_bytes": 1934, + "reported_used_tokens": 1899, + "working_set_bytes": 257736704, + "peak_working_set_bytes": 258662400 + }, + { + "query": "kimetsu plugin install --remote mcp.json authorization bearer token", + "ranked": [ + "remote-mcp-host-wiring", + "mcp-stdout-protocol" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDGSHENNVZQKGWMDBNDR", + "id": "01M1Y0CP1BRYA9951N71M38SFA", + "kind": "memory", + "score": 0.999605119228363, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + }, + { + "expansion_handle": "memory:01M1Y0CGVVT9QFVZB1R92FVJYX", + "id": "01M1Y0CP1BCB7WC7ZK46P0CGMX", + "kind": "memory", + "score": 0.3375842869281769, + "summary": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 981.4427999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1472, + "mcp_result_bytes": 1619, + "wire_bytes": 1654, + "reported_used_tokens": 1619, + "working_set_bytes": 258039808, + "peak_working_set_bytes": 258961408 + }, + { + "query": "how do I wire a remote kimetsu brain into Claude Code without storing the token in the config file?", + "ranked": [ + "remote-mcp-host-wiring", + "mcp-tool-naming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDGSHENNVZQKGWMDBNDR", + "id": "01M1Y0CPZMR9A8KFDHVS9YKHG6", + "kind": "memory", + "score": 0.9963359832763672, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + }, + { + "expansion_handle": "memory:01M1Y0CH08W8KRWZHZ6QYESXCW", + "id": "01M1Y0CPZMGCAQDK432HMY5V8X", + "kind": "memory", + "score": 0.831425666809082, + "summary": "project:fact - [tags: mcp tool naming convention kimetsu] MCP tool names must be valid identifiers for all host agents. Claude Code restricts tool names to `[a-zA-Z0-9_-]` and max 64 chars. Use `snake_case` (kimetsu_brain_context, kimetsu_brain_record) — hyphen is technically allowed but some hosts reject it." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 937.6639, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1454, + "mcp_result_bytes": 1601, + "wire_bytes": 1636, + "reported_used_tokens": 1601, + "working_set_bytes": 258412544, + "peak_working_set_bytes": 259342336 + }, + { + "query": "cargo feature unification kimetsu-brain embeddings fastembed test failure", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-profile-override", + "clap-version-build-flavor" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDJN5BPC6G4D9PSYG7G6", + "id": "01M1Y0CQWXE5KMM65QSW8F48CG", + "kind": "memory", + "score": 0.9996790885925292, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1Y0CEMKNXWNXAX5DG21NDNC", + "id": "01M1Y0CQWXYR9D300CHN24980Y", + "kind": "memory", + "score": 0.9923595786094666, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1Y0CDWXMHS387P46NG7NJCM", + "id": "01M1Y0CQWX2ZZ7984M1QXQVCSH", + "kind": "memory", + "score": 0.585203230381012, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 882.5066, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2387, + "mcp_result_bytes": 2524, + "wire_bytes": 2559, + "reported_used_tokens": 2524, + "working_set_bytes": 259944448, + "peak_working_set_bytes": 260882432 + }, + { + "query": "my integration tests pass in isolation but break when I run cargo test --workspace — embedder changed?", + "ranked": [ + "cargo-feature-unification-embeddings", + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDJN5BPC6G4D9PSYG7G6", + "id": "01M1Y0CRRJ8XEC8QS787DX7638", + "kind": "memory", + "score": 0.9943140745162964, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1Y0CDW1WMXYA9MGV0TK68J6", + "id": "01M1Y0CRRJG5ZP054Y59DYQ7T9", + "kind": "memory", + "score": 0.31398114562034607, + "summary": "project:fact - [2026-09-07] [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 984.525, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1714, + "mcp_result_bytes": 1817, + "wire_bytes": 1852, + "reported_used_tokens": 1817, + "working_set_bytes": 260771840, + "peak_working_set_bytes": 261697536 + }, + { + "query": "build_anthropic_body bedrock-2023-05-31 InvokeModel blocking reqwest", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDKZJ260T9MFP5RRHHQY", + "id": "01M1Y0CSQ8W9YNNCYP7YYN4YWS", + "kind": "memory", + "score": 0.9973788261413574, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1Y0CDSEBRPP3R78RVYDDGZA", + "id": "01M1Y0CSQ9VMEHFTXZP7JME0GS", + "kind": "memory", + "score": 0.6916899085044861, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 738.1855, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2193, + "mcp_result_bytes": 2320, + "wire_bytes": 2356, + "reported_used_tokens": 2320, + "working_set_bytes": 261148672, + "peak_working_set_bytes": 262066176 + }, + { + "query": "how do I add AWS Bedrock as a model provider in Kimetsu without pulling in the aws-sdk?", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-region-resolution", + "aws-credentials-chain", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDKZJ260T9MFP5RRHHQY", + "id": "01M1Y0CTEK467TJQ479NG6P6CB", + "kind": "memory", + "score": 0.9998898506164552, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1Y0CH4021WNS0JP2HVKJDXQ", + "id": "01M1Y0CTEK2ET2AF229KAFJXAC", + "kind": "memory", + "score": 0.995676338672638, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1Y0CH2MJMDSTCRQ0SR4KPC5", + "id": "01M1Y0CTEK1ZCKVZ1GRAAVJ4AK", + "kind": "memory", + "score": 0.987064242362976, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + }, + { + "expansion_handle": "memory:01M1Y0CDSEBRPP3R78RVYDDGZA", + "id": "01M1Y0CTEKQBB5EFBD1DWRCH9E", + "kind": "memory", + "score": 0.9493880867958068, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 978.8104, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3455, + "mcp_result_bytes": 3618, + "wire_bytes": 3654, + "reported_used_tokens": 3618, + "working_set_bytes": 269479936, + "peak_working_set_bytes": 270401536 + }, + { + "query": "BridgeTarget enum seams plugin_install_inner plugin_status_inner resolve_setup_hosts", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDNPXTVTYW8S244N1GCB", + "id": "01M1Y0CVD1BH6XMPAQ1MYVFGJ6", + "kind": "memory", + "score": 0.9997583031654358, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 763.8307, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1060, + "mcp_result_bytes": 1141, + "wire_bytes": 1177, + "reported_used_tokens": 1141, + "working_set_bytes": 279420928, + "peak_working_set_bytes": 280338432 + }, + { + "query": "I added a new host to the bridge enum but cargo gives me compile errors in five different match arms — what did I miss?", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDNPXTVTYW8S244N1GCB", + "id": "01M1Y0CW5K74PVV8TYTJRCYGTK", + "kind": "memory", + "score": 0.9977060556411744, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1082.3542, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1059, + "mcp_result_bytes": 1140, + "wire_bytes": 1176, + "reported_used_tokens": 1140, + "working_set_bytes": 279846912, + "peak_working_set_bytes": 280764416 + }, + { + "query": "Pi extension factory defineExtension agent_end session_shutdown kimetsu.ts", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDPTB91KV3WJ6A8WXR9N", + "id": "01M1Y0CX6S0GRS2WEETJG72QC1", + "kind": "memory", + "score": 0.9990354776382446, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1031.424, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 804, + "mcp_result_bytes": 893, + "wire_bytes": 929, + "reported_used_tokens": 893, + "working_set_bytes": 280129536, + "peak_working_set_bytes": 281047040 + }, + { + "query": "how does Pi (earendil-works/pi) load plugins and what lifecycle hooks does it expose?", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDPTB91KV3WJ6A8WXR9N", + "id": "01M1Y0CY7FJH9ANK3RJRFGD691", + "kind": "memory", + "score": 0.9934834837913512, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1114.2602, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 803, + "mcp_result_bytes": 892, + "wire_bytes": 928, + "reported_used_tokens": 892, + "working_set_bytes": 280248320, + "peak_working_set_bytes": 281161728 + }, + { + "query": "aws-sigv4 SigningParams apply_to_request_http1x reqwest sign-http", + "ranked": [ + "aws-sigv4-bedrock-blocking", + "aws-presigned-urls", + "bedrock-kimetsu-provider", + "aws-credentials-chain" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDSEBRPP3R78RVYDDGZA", + "id": "01M1Y0CZ9RGP3WXV6QJCT2GH9C", + "kind": "memory", + "score": 0.9995608925819396, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1Y0CH6AWWAV734TFW14SHW0", + "id": "01M1Y0CZ9RHYE1WF09KKZWP39B", + "kind": "memory", + "score": 0.984916627407074, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time — clock skew > 15 minutes causes `RequestTimeTooSkewed`." + }, + { + "expansion_handle": "memory:01M1Y0CDKZJ260T9MFP5RRHHQY", + "id": "01M1Y0CZ9RT7C943Q6SHTXA3RA", + "kind": "memory", + "score": 0.983895778656006, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1Y0CH2MJMDSTCRQ0SR4KPC5", + "id": "01M1Y0CZ9RMMWN3A7P9XSZG2CH", + "kind": "memory", + "score": 0.8592692017555237, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 798.3355, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3507, + "mcp_result_bytes": 3670, + "wire_bytes": 3706, + "reported_used_tokens": 3670, + "working_set_bytes": 280408064, + "peak_working_set_bytes": 281313280 + }, + { + "query": "how do I sign a Bedrock InvokeModel request with aws-sigv4 in blocking Rust?", + "ranked": [ + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider", + "aws-region-resolution", + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDSEBRPP3R78RVYDDGZA", + "id": "01M1Y0D02WVA4D5V36SQEGFZEX", + "kind": "memory", + "score": 0.9998323917388916, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1Y0CDKZJ260T9MFP5RRHHQY", + "id": "01M1Y0D02WDDA9Z8T7PJR6M3AQ", + "kind": "memory", + "score": 0.9970844388008118, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1Y0CH4021WNS0JP2HVKJDXQ", + "id": "01M1Y0D02W17W5FJPPQ7V8MH1S", + "kind": "memory", + "score": 0.9468621611595154, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1Y0CH6AWWAV734TFW14SHW0", + "id": "01M1Y0D02W0EZET761XESTVBAE", + "kind": "memory", + "score": 0.9210098385810852, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time — clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 938.9264000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3434, + "mcp_result_bytes": 3597, + "wire_bytes": 3633, + "reported_used_tokens": 3597, + "working_set_bytes": 280866816, + "peak_working_set_bytes": 281788416 + }, + { + "query": "KIMETSU_RUNS_GC env opt-out TraceWriter create gc_old_runs caller", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDV606AJC3CB2VR63EVV", + "id": "01M1Y0D106DG8W1P6VXDHR0463", + "kind": "memory", + "score": 0.999936580657959, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 863.3108000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 761, + "mcp_result_bytes": 842, + "wire_bytes": 878, + "reported_used_tokens": 842, + "working_set_bytes": 281141248, + "peak_working_set_bytes": 282058752 + }, + { + "query": "where should I put the KIMETSU_RUNS_GC=0 guard — inside the GC function or at the call site?", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDV606AJC3CB2VR63EVV", + "id": "01M1Y0D1V8EAWC1EPQEHRS8QAZ", + "kind": "memory", + "score": 0.9971211552619934, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1037.4566, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 762, + "mcp_result_bytes": 843, + "wire_bytes": 879, + "reported_used_tokens": 843, + "working_set_bytes": 281636864, + "peak_working_set_bytes": 282562560 + }, + { + "query": "git_init_boundary ProjectPaths::discover temp dir user brain isolation", + "ranked": [ + "init-project-git-boundary", + "git-worktree-brain-isolation", + "testing-temp-dirs-ci", + "kimetsu-memory-scopes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDW1WMXYA9MGV0TK68J6", + "id": "01M1Y0D2VWEFWPXTPVP0M4TWX6", + "kind": "memory", + "score": 0.9997712969779968, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + }, + { + "expansion_handle": "memory:01M1Y0CFYDVF1XT2NJ4R1CE9KK", + "id": "01M1Y0D2VW6E1GRPN56JC33YPS", + "kind": "memory", + "score": 0.9962491393089294, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root — if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + }, + { + "expansion_handle": "memory:01M1Y0CGN6SREKHFEAXY7YMS2G", + "id": "01M1Y0D2VW7R73GB9VD7ZTBQXY", + "kind": "memory", + "score": 0.9682154655456544, + "summary": "project:fact - [tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure." + }, + { + "expansion_handle": "memory:01M1Y0CHHMQS2VGXDD5KZTKWV1", + "id": "01M1Y0D2VW3V695TPW4MMBD054", + "kind": "memory", + "score": 0.3057229816913605, + "summary": "project:fact - [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available — if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 853.662, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2580, + "mcp_result_bytes": 2715, + "wire_bytes": 2751, + "reported_used_tokens": 2715, + "working_set_bytes": 281747456, + "peak_working_set_bytes": 282664960 + }, + { + "query": "my test calls init_project but it writes to the real ~/.kimetsu instead of the temp folder — why?", + "ranked": [ + "init-project-git-boundary", + "cargo-feature-unification-embeddings", + "testing-fixture-drift", + "tokio-runtime-in-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDW1WMXYA9MGV0TK68J6", + "id": "01M1Y0D3PAAFQN4MZZ3Z477RZD", + "kind": "memory", + "score": 0.9995088577270508, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + }, + { + "expansion_handle": "memory:01M1Y0CDJN5BPC6G4D9PSYG7G6", + "id": "01M1Y0D3PAY4HY9F3VMB05GMGM", + "kind": "memory", + "score": 0.7287850975990295, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1Y0CGTZS0E9FZN5W5KHQQXA", + "id": "01M1Y0D3PB235DQBJCX1N0SJ1X", + "kind": "memory", + "score": 0.6596062183380127, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + }, + { + "expansion_handle": "memory:01M1Y0CG5NP8CQF81Q3J12R0GS", + "id": "01M1Y0D3PAE7PKXEPQAR22R060", + "kind": "memory", + "score": 0.3297702968120575, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1012.4678, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2833, + "mcp_result_bytes": 2980, + "wire_bytes": 3016, + "reported_used_tokens": 2980, + "working_set_bytes": 281931776, + "peak_working_set_bytes": 282849280 + }, + { + "query": "clap command version KIMETSU_VERSION_DISPLAY cfg feature embeddings", + "ranked": [ + "clap-version-build-flavor", + "cargo-feature-unification-embeddings" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDWXMHS387P46NG7NJCM", + "id": "01M1Y0D4NZ7DE236QD317K4WE5", + "kind": "memory", + "score": 0.9996613264083862, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + }, + { + "expansion_handle": "memory:01M1Y0CDJN5BPC6G4D9PSYG7G6", + "id": "01M1Y0D4NZBW0C1PKMXK1GZ3D6", + "kind": "memory", + "score": 0.3973360061645508, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 786.9559, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1922, + "mcp_result_bytes": 2041, + "wire_bytes": 2077, + "reported_used_tokens": 2041, + "working_set_bytes": 282095616, + "peak_working_set_bytes": 283004928 + }, + { + "query": "how do I show the build flavor (lean vs embeddings) in the kimetsu --version output?", + "ranked": [ + "clap-version-build-flavor", + "cargo-feature-unification-embeddings", + "onnx-quantization-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDWXMHS387P46NG7NJCM", + "id": "01M1Y0D5EN6DCGHN0M3WM6SFEK", + "kind": "memory", + "score": 0.9978312849998474, + "summary": "project:fact - [tags: rust clap features version kimetsu-cli] To expose a build flavor in `kimetsu --version` with clap: (1) define `const VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \" (flavor)\")` gated by `#[cfg(feature = \"embeddings\")]` / `#[cfg(not(...))]`; (2) set `#[command(version = VERSION)]` on the top-level clap struct. Keep `env!(\"CARGO_PKG_VERSION\")` bare in update.rs so semver comparisons aren't broken by the suffix. The kimetsu-cli `[features]` already has `embeddings = [...]` with `default = []` (lean default), so the same cfg gate works for both `--features embeddings` and `--no-default-features` builds." + }, + { + "expansion_handle": "memory:01M1Y0CDJN5BPC6G4D9PSYG7G6", + "id": "01M1Y0D5EPTKPBG3MQMKHJDPPW", + "kind": "memory", + "score": 0.8926984667778015, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1Y0CFN2M30C7MN0GWMT2VSV", + "id": "01M1Y0D5EP55XJSCA230CPNEHR", + "kind": "memory", + "score": 0.8877003192901611, + "summary": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals — cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1021.3448999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2672, + "mcp_result_bytes": 2809, + "wire_bytes": 2845, + "reported_used_tokens": 2809, + "working_set_bytes": 282415104, + "peak_working_set_bytes": 283336704 + }, + { + "query": "Harbor pyiceberg os.getcwd stale WSL2 DrvFs worker-result subprocess re-exec", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDY5Q7FP1HV3ZB904S29", + "id": "01M1Y0D6F5TS3DFJ5D19B06K4P", + "kind": "memory", + "score": 0.9998155236244202, + "summary": "project:fact - [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1011.3393, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1026, + "mcp_result_bytes": 1107, + "wire_bytes": 1143, + "reported_used_tokens": 1107, + "working_set_bytes": 282464256, + "peak_working_set_bytes": 283377664 + }, + { + "query": "why does my kbench sweep crash after the first trial with 'result.json missing' on WSL2?", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDY5Q7FP1HV3ZB904S29", + "id": "01M1Y0D7F1ND19CEVGVNYRBZ0M", + "kind": "memory", + "score": 0.998451828956604, + "summary": "project:fact - [2026-09-07] [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1058.056, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1038, + "mcp_result_bytes": 1119, + "wire_bytes": 1155, + "reported_used_tokens": 1119, + "working_set_bytes": 282484736, + "peak_working_set_bytes": 283410432 + }, + { + "query": "rusqlite VACUUM transaction WAL checkpoint wal_checkpoint TRUNCATE", + "ranked": [ + "sqlite-vacuum-wal-checkpoint", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDZHQMF7CEANBH94ZC5Z", + "id": "01M1Y0D8F9BY75EVPB6HRM9GGN", + "kind": "memory", + "score": 0.9996871948242188, + "summary": "project:fact - [tags: rust sqlite vacuum rusqlite windows] When implementing SQLite VACUUM in rusqlite: VACUUM cannot run inside a transaction. rusqlite's Connection does not hold an implicit transaction, so `conn.execute_batch(\"VACUUM;\")` works directly. After VACUUM, run `PRAGMA wal_checkpoint(TRUNCATE);` before measuring file size — on Windows the WAL file can hold significant space that isn't reflected in the main db file until the checkpoint runs." + }, + { + "expansion_handle": "memory:01M1Y0CE63YDGRR9GCFM6A405N", + "id": "01M1Y0D8F9YPYS62RR1D2MHJ37", + "kind": "memory", + "score": 0.5274003744125366, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 817.7686, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1507, + "mcp_result_bytes": 1610, + "wire_bytes": 1646, + "reported_used_tokens": 1610, + "working_set_bytes": 282497024, + "peak_working_set_bytes": 283410432 + }, + { + "query": "my SQLite VACUUM reports the file shrank but the disk usage stayed the same — Windows WAL?", + "ranked": [ + "sqlite-vacuum-wal-checkpoint", + "sqlite-wal-network-drive" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDZHQMF7CEANBH94ZC5Z", + "id": "01M1Y0D98VHE5EWCVEJNAK7MSN", + "kind": "memory", + "score": 0.9155893921852112, + "summary": "project:fact - [tags: rust sqlite vacuum rusqlite windows] When implementing SQLite VACUUM in rusqlite: VACUUM cannot run inside a transaction. rusqlite's Connection does not hold an implicit transaction, so `conn.execute_batch(\"VACUUM;\")` works directly. After VACUUM, run `PRAGMA wal_checkpoint(TRUNCATE);` before measuring file size — on Windows the WAL file can hold significant space that isn't reflected in the main db file until the checkpoint runs." + }, + { + "expansion_handle": "memory:01M1Y0CE84FR94JFPZ2CTRK5KY", + "id": "01M1Y0D98V4P0N87DPVB34XF67", + "kind": "memory", + "score": 0.902395486831665, + "summary": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1077.2008, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1357, + "mcp_result_bytes": 1460, + "wire_bytes": 1496, + "reported_used_tokens": 1460, + "working_set_bytes": 282669056, + "peak_working_set_bytes": 283590656 + }, + { + "query": "add_memory import dedup seen_ids snapshot pre-existing active memory IDs", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE0BVW8K017DXQ2P7M8E", + "id": "01M1Y0DAAJEKKFCA7MGYXEDTS3", + "kind": "memory", + "score": 0.9999133348464966, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount — both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 907.7742, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 966, + "mcp_result_bytes": 1047, + "wire_bytes": 1083, + "reported_used_tokens": 1047, + "working_set_bytes": 282882048, + "peak_working_set_bytes": 283795456 + }, + { + "query": "brain import re-imports the same JSON file but the deduplication counter is wrong — why?", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE0BVW8K017DXQ2P7M8E", + "id": "01M1Y0DB8HZWN7T2JWHYTSSGNR", + "kind": "memory", + "score": 0.9254016876220704, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount — both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1019.945, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 965, + "mcp_result_bytes": 1046, + "wire_bytes": 1082, + "reported_used_tokens": 1046, + "working_set_bytes": 283058176, + "peak_working_set_bytes": 283979776 + }, + { + "query": "toml::from_str Value parse document unexpected content str.parse", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE1F83ZZ1GGZDPZBKQMF", + "id": "01M1Y0DC7MZ20J9R7DKMFEP28K", + "kind": "memory", + "score": 0.9991866946220398, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 879.7893, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 734, + "mcp_result_bytes": 815, + "wire_bytes": 851, + "reported_used_tokens": 815, + "working_set_bytes": 283099136, + "peak_working_set_bytes": 284016640 + }, + { + "query": "how do I parse a TOML configuration file into a toml::Value in toml 0.9?", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE1F83ZZ1GGZDPZBKQMF", + "id": "01M1Y0DD2SM1DA3X3ZSTV9JCH7", + "kind": "memory", + "score": 0.9992641806602478, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 997.8065, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 733, + "mcp_result_bytes": 814, + "wire_bytes": 850, + "reported_used_tokens": 814, + "working_set_bytes": 283111424, + "peak_working_set_bytes": 284028928 + }, + { + "query": "CIM CreationDate DMTF WMI ps etimes started_at assess_mcp_skew", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE2KCEZ4A9F2TGGTDYZY", + "id": "01M1Y0DE1J612YQEW7WCZ2N26H", + "kind": "memory", + "score": 0.9957948923110962, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 772.7819999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 924, + "mcp_result_bytes": 1013, + "wire_bytes": 1049, + "reported_used_tokens": 1013, + "working_set_bytes": 283140096, + "peak_working_set_bytes": 284049408 + }, + { + "query": "how do I read a process start time on both Windows and Linux in pure Rust?", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE2KCEZ4A9F2TGGTDYZY", + "id": "01M1Y0DETTT2CVKEXKVT1YZZXQ", + "kind": "memory", + "score": 0.99687659740448, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1018.7578, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 921, + "mcp_result_bytes": 1010, + "wire_bytes": 1046, + "reported_used_tokens": 1010, + "working_set_bytes": 283447296, + "peak_working_set_bytes": 284377088 + }, + { + "query": "processes_locking_target decide_preflight_action BufRead Write update.rs", + "ranked": [ + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE3VN854570296AQJ8B8", + "id": "01M1Y0DFTMTXS9AWBGGRZEHYRT", + "kind": "memory", + "score": 0.9995336532592772, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics — mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 870.0039, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1133, + "mcp_result_bytes": 1214, + "wire_bytes": 1250, + "reported_used_tokens": 1214, + "working_set_bytes": 283475968, + "peak_working_set_bytes": 284397568 + }, + { + "query": "how should I reuse the existing process enumerator in the update preflight check to avoid a second PowerShell query?", + "ranked": [ + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE3VN854570296AQJ8B8", + "id": "01M1Y0DGMTBQT39PZ6ZP7V9ZRR", + "kind": "memory", + "score": 0.9973384737968444, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics — mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1006.9813, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1132, + "mcp_result_bytes": 1213, + "wire_bytes": 1249, + "reported_used_tokens": 1213, + "working_set_bytes": 283926528, + "peak_working_set_bytes": 284848128 + }, + { + "query": "cfg_attr windows allow dead_code parse_unix_ps cross-platform tests", + "ranked": [ + "cfg-cross-platform-dead-code", + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE53127TY01EC3730MH1", + "id": "01M1Y0DHMBH8F2M6HT83FNSR7C", + "kind": "memory", + "score": 0.9999476671218872, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + }, + { + "expansion_handle": "memory:01M1Y0CE2KCEZ4A9F2TGGTDYZY", + "id": "01M1Y0DHMBJT8846ACDPFS8VZY", + "kind": "memory", + "score": 0.9764312505722046, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 788.6767, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1518, + "mcp_result_bytes": 1625, + "wire_bytes": 1661, + "reported_used_tokens": 1625, + "working_set_bytes": 283942912, + "peak_working_set_bytes": 284860416 + }, + { + "query": "how do I keep a function that is only called on Unix from triggering dead_code warnings on Windows?", + "ranked": [ + "cfg-cross-platform-dead-code" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE53127TY01EC3730MH1", + "id": "01M1Y0DJD1HXD976S6WPWCBM2X", + "kind": "memory", + "score": 0.9988092184066772, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 989.4556, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 939, + "reported_used_tokens": 903, + "working_set_bytes": 284200960, + "peak_working_set_bytes": 285118464 + }, + { + "query": "deadlocking a Rust mutex in integration tests", + "ranked": [ + "mutex-deadlock-user-brain-disabled", + "testing-serial-vs-parallel", + "kimetsu-query-stemming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDDKV46DFCQB5VK0TWX5", + "id": "01M1Y0DKBXJ2C1PM8XMZZBT6X5", + "kind": "memory", + "score": 0.9997490048408508, + "summary": "project:fact - [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure — `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + }, + { + "expansion_handle": "memory:01M1Y0CGRCR5ZFR10QAFCTQWHR", + "id": "01M1Y0DKBX36W96TMKHRAMCTP2", + "kind": "memory", + "score": 0.9057517647743224, + "summary": "project:fact - [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`)." + }, + { + "expansion_handle": "memory:01M1Y0CHR9RRWWX5FQ2Z9H6B27", + "id": "01M1Y0DKBYVYX3N3R9YJBBW2C1", + "kind": "memory", + "score": 0.4889622032642365, + "summary": "project:fact - [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 945.5635, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1930, + "mcp_result_bytes": 2063, + "wire_bytes": 2099, + "reported_used_tokens": 2063, + "working_set_bytes": 284217344, + "peak_working_set_bytes": 285134848 + }, + { + "query": "benchmarking retrieval quality across embedders", + "ranked": [ + "kimetsu-bench-remote-embedder-singleton", + "onnx-quantization-drift", + "cargo-feature-unification-embeddings" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHTA3AQK16G6EE1QCJKZ", + "id": "01M1Y0DM9PK9ST4HHFNG3ZFNP6", + "kind": "memory", + "score": 0.988014280796051, + "summary": "project:fact - [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval." + }, + { + "expansion_handle": "memory:01M1Y0CFN2M30C7MN0GWMT2VSV", + "id": "01M1Y0DM9PT5RMH0HYDBM0GADE", + "kind": "memory", + "score": 0.985597550868988, + "summary": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals — cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + }, + { + "expansion_handle": "memory:01M1Y0CDJN5BPC6G4D9PSYG7G6", + "id": "01M1Y0DM9QW0D5ENBTQ9BA68W7", + "kind": "memory", + "score": 0.5341982841491699, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 806.3176, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2537, + "mcp_result_bytes": 2658, + "wire_bytes": 2694, + "reported_used_tokens": 2658, + "working_set_bytes": 284217344, + "peak_working_set_bytes": 285134848 + }, + { + "query": "process memory working set RSS peak measurement Windows", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 985.7869999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 284487680, + "peak_working_set_bytes": 285384704 + }, + { + "query": "cloning a git repository server-side into a managed checkout", + "ranked": [ + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDERH0MV40CSBCQDEQYA", + "id": "01M1Y0DP1T4TTQJRQSZTS93J0E", + "kind": "memory", + "score": 0.9466677904129028, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 848.966, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1261, + "mcp_result_bytes": 1342, + "wire_bytes": 1378, + "reported_used_tokens": 1342, + "working_set_bytes": 284639232, + "peak_working_set_bytes": 285548544 + }, + { + "query": "SigV4 signing HTTP requests in Rust", + "ranked": [ + "aws-presigned-urls", + "aws-sigv4-bedrock-blocking", + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CH6AWWAV734TFW14SHW0", + "id": "01M1Y0DPW97FZV1J2Q6J8B90E9", + "kind": "memory", + "score": 0.9992632269859314, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time — clock skew > 15 minutes causes `RequestTimeTooSkewed`." + }, + { + "expansion_handle": "memory:01M1Y0CDSEBRPP3R78RVYDDGZA", + "id": "01M1Y0DPW9AYNNAEDC2PB5TDH3", + "kind": "memory", + "score": 0.9991399049758912, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1Y0CDKZJ260T9MFP5RRHHQY", + "id": "01M1Y0DPW97N3QKFJH7XHGZ2RC", + "kind": "memory", + "score": 0.9803794622421264, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 0.5, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 918.5545000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2840, + "mcp_result_bytes": 2985, + "wire_bytes": 3021, + "reported_used_tokens": 2985, + "working_set_bytes": 284852224, + "peak_working_set_bytes": 285753344 + }, + { + "query": "cargo test --workspace feature flag changes broke my unit tests", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-dev-dep-leak", + "ci-flaky-quarantine" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDJN5BPC6G4D9PSYG7G6", + "id": "01M1Y0DQT36G27QVYPGTKH45YF", + "kind": "memory", + "score": 0.997899889945984, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1Y0CEHKWTJ8ZMXANWDJAMCD", + "id": "01M1Y0DQT3VZ88PGCCTCDSHVE1", + "kind": "memory", + "score": 0.9901249408721924, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + }, + { + "expansion_handle": "memory:01M1Y0CHEJR8TEQHZ0MBKNP52Z", + "id": "01M1Y0DQT39MT42KJQX9747CNG", + "kind": "memory", + "score": 0.835382342338562, + "summary": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal — a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 820.0164, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2383, + "mcp_result_bytes": 2504, + "wire_bytes": 2540, + "reported_used_tokens": 2504, + "working_set_bytes": 284860416, + "peak_working_set_bytes": 285777920 + }, + { + "query": "how do I make pasta carbonara?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 830.4524, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 284913664, + "peak_working_set_bytes": 285831168 + }, + { + "query": "what is the offside rule in football?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 1091.7950999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 285040640, + "peak_working_set_bytes": 285954048 + }, + { + "query": "best way to train for a half marathon", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 1100.6146, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 285044736, + "peak_working_set_bytes": 285970432 + }, + { + "query": "my test passes when I run it alone but fails under cargo test --workspace", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDJN5BPC6G4D9PSYG7G6", + "id": "01M1Y0DVH288951P7QTVK3SBF1", + "kind": "memory", + "score": 0.9907942414283752, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1Y0CEHKWTJ8ZMXANWDJAMCD", + "id": "01M1Y0DVH2KNZHAQ8MFMZMPS3Q", + "kind": "memory", + "score": 0.986136794090271, + "summary": "project:fact - [2026-09-07] [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1037.2329, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1863, + "mcp_result_bytes": 1966, + "wire_bytes": 2002, + "reported_used_tokens": 1966, + "working_set_bytes": 285491200, + "peak_working_set_bytes": 286412800 + }, + { + "query": "all the project tests started hanging forever after I added my new test", + "ranked": [ + "cargo-feature-unification-embeddings", + "tokio-runtime-in-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDJN5BPC6G4D9PSYG7G6", + "id": "01M1Y0DWHQ62H6P95BKTTTJHBP", + "kind": "memory", + "score": 0.774284839630127, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1Y0CG5NP8CQF81Q3J12R0GS", + "id": "01M1Y0DWHQ9RGHM9D0DNTMFFVM", + "kind": "memory", + "score": 0.33030807971954346, + "summary": "project:fact - [2026-09-07] [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 961.9590999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1763, + "mcp_result_bytes": 1874, + "wire_bytes": 1910, + "reported_used_tokens": 1874, + "working_set_bytes": 285491200, + "peak_working_set_bytes": 286412800 + }, + { + "query": "my integration test silently wrote memories into my real home brain instead of the temp workspace", + "ranked": [ + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDW1WMXYA9MGV0TK68J6", + "id": "01M1Y0DXFHAW2K3QSY7075SSZD", + "kind": "memory", + "score": 0.9922831654548644, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 972.419, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 780, + "mcp_result_bytes": 861, + "wire_bytes": 897, + "reported_used_tokens": 861, + "working_set_bytes": 285499392, + "peak_working_set_bytes": 286420992 + }, + { + "query": "where should the env-var opt-out check live for a cleanup feature triggered from a hot code path", + "ranked": [ + "gc-trace-env-guard-placement" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDV606AJC3CB2VR63EVV", + "id": "01M1Y0DYECAJ2TA320T87AAGTY", + "kind": "memory", + "score": 0.9952055215835572, + "summary": "project:fact - [tags: rust kimetsu gc trace testing] When adding opportunistic GC triggered from a hot code path (TraceWriter::create), the env-var opt-out check (KIMETSU_RUNS_GC=0) must be in the caller (TraceWriter::create), not inside the GC function itself, so the pure GC function (gc_old_runs) stays testable without env manipulation and the opt-out is visible at the trigger site." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1034.8117000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 761, + "mcp_result_bytes": 842, + "wire_bytes": 878, + "reported_used_tokens": 842, + "working_set_bytes": 285499392, + "peak_working_set_bytes": 286420992 + }, + { + "query": "the brain database file stays huge on Windows even after deleting most rows", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1007.3049000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 285519872, + "peak_working_set_bytes": 286445568 + }, + { + "query": "re-importing the same exported memories file counts them as new instead of deduplicated", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE0BVW8K017DXQ2P7M8E", + "id": "01M1Y0E0EQ183NWF18MNJ3GPH0", + "kind": "memory", + "score": 0.9878425598144532, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount — both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1068.6028999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 965, + "mcp_result_bytes": 1046, + "wire_bytes": 1082, + "reported_used_tokens": 1046, + "working_set_bytes": 285519872, + "peak_working_set_bytes": 286445568 + }, + { + "query": "a helper function only called on Unix at runtime fails the dead-code lint on the Windows build", + "ranked": [ + "cfg-cross-platform-dead-code", + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE53127TY01EC3730MH1", + "id": "01M1Y0E1F8KJKSWB0XJYN3V39W", + "kind": "memory", + "score": 0.9971064925193788, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + }, + { + "expansion_handle": "memory:01M1Y0CE3VN854570296AQJ8B8", + "id": "01M1Y0E1F8EYBH6V453FXNZV3V", + "kind": "memory", + "score": 0.427912950515747, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics — mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 970.3475999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1728, + "mcp_result_bytes": 1827, + "wire_bytes": 1863, + "reported_used_tokens": 1827, + "working_set_bytes": 285974528, + "peak_working_set_bytes": 286900224 + }, + { + "query": "the second Terminal-Bench trial always crashes even though the first one passes", + "ranked": [ + "harbor-terminal-bench-subprocess-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDY5Q7FP1HV3ZB904S29", + "id": "01M1Y0E2DN3ZXN8X2B7NRN2KJ7", + "kind": "memory", + "score": 0.9963042736053468, + "summary": "project:fact - [2026-09-07] [tags: harbor terminal-bench wsl2 kbench subprocess-isolation] Harbor 0.8.0 (Terminal-Bench) crashes on the 2nd+ invocation within ONE process on WSL2/DrvFs: pyiceberg imports call os.getcwd() at import time and the inherited cwd handle goes stale after the 1st run's Docker churn (FileNotFoundError -> 'result.json missing', Harbor exit 1). Setting child cmd.current_dir(...) to a fresh dir is UNRELIABLE (behavior varied run-to-run). The reliable fix is process isolation: have the runner (kbench) re-exec itself once per (task,agent) in a hidden --worker-result mode so each Harbor invocation gets a fresh process (fresh valid cwd)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1018.6896999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1038, + "mcp_result_bytes": 1119, + "wire_bytes": 1155, + "reported_used_tokens": 1119, + "working_set_bytes": 286003200, + "peak_working_set_bytes": 286920704 + }, + { + "query": "how does doctor tell a running MCP server process is older than the kimetsu binary on disk", + "ranked": [ + "kimetsu-daemon-lifecycle", + "process-start-time-cross-platform", + "mcp-env-propagation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHFNNPJ5W1HT08QTSPX7", + "id": "01M1Y0E3DHFHS9D5SFZ2CEVR9W", + "kind": "memory", + "score": 0.9985345602035522, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1Y0CE2KCEZ4A9F2TGGTDYZY", + "id": "01M1Y0E3DH5FK3FB3D7P3RP8ZE", + "kind": "memory", + "score": 0.9438157677650452, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + }, + { + "expansion_handle": "memory:01M1Y0CGY1YEEKFJHX4JCZYHY6", + "id": "01M1Y0E3DHH12MTDY16PJ9W82E", + "kind": "memory", + "score": 0.33611738681793213, + "summary": "project:fact - [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment — changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 0.5, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1054.404, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1936, + "mcp_result_bytes": 2061, + "wire_bytes": 2097, + "reported_used_tokens": 2061, + "working_set_bytes": 286007296, + "peak_working_set_bytes": 286928896 + }, + { + "query": "the self-update preflight needs the list of running kimetsu processes without re-running the OS query", + "ranked": [ + "windows-update-process-locking", + "kimetsu-daemon-lifecycle" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE3VN854570296AQJ8B8", + "id": "01M1Y0E4EMDXN756B9WMDBD22S", + "kind": "memory", + "score": 0.9972410202026368, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics — mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + }, + { + "expansion_handle": "memory:01M1Y0CHFNNPJ5W1HT08QTSPX7", + "id": "01M1Y0E4EM7KX11PW8ND1QR36K", + "kind": "memory", + "score": 0.8902595043182373, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 956.3525000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1658, + "mcp_result_bytes": 1757, + "wire_bytes": 1793, + "reported_used_tokens": 1757, + "working_set_bytes": 286007296, + "peak_working_set_bytes": 286928896 + }, + { + "query": "parsing the WMI DMTF CreationDate timestamp into epoch seconds without extra crates", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE2KCEZ4A9F2TGGTDYZY", + "id": "01M1Y0E5CMMWWQYJ29ZW7APRA9", + "kind": "memory", + "score": 0.9258026480674744, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1038.5942, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 924, + "mcp_result_bytes": 1013, + "wire_bytes": 1049, + "reported_used_tokens": 1013, + "working_set_bytes": 286007296, + "peak_working_set_bytes": 286928896 + }, + { + "query": "calling Bedrock InvokeModel from blocking reqwest without the aws sdk", + "ranked": [ + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking", + "aws-region-resolution", + "aws-retry-throttling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDKZJ260T9MFP5RRHHQY", + "id": "01M1Y0E6CZY1X879JY6V9N38JW", + "kind": "memory", + "score": 0.9991798996925354, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1Y0CDSEBRPP3R78RVYDDGZA", + "id": "01M1Y0E6CZ9MKM4657HS57R62K", + "kind": "memory", + "score": 0.999082326889038, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + }, + { + "expansion_handle": "memory:01M1Y0CH4021WNS0JP2HVKJDXQ", + "id": "01M1Y0E6CZTQBZDPWZ0VX9K36F", + "kind": "memory", + "score": 0.8391201496124268, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1Y0CH5512SPEE30NNSG0SKK", + "id": "01M1Y0E6CZ9221FD20BQKB2T08", + "kind": "memory", + "score": 0.4906356632709503, + "summary": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with ±25% jitter." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 998.2014, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3330, + "mcp_result_bytes": 3509, + "wire_bytes": 3545, + "reported_used_tokens": 3509, + "working_set_bytes": 286007296, + "peak_working_set_bytes": 286928896 + }, + { + "query": "how do I rotate the encryption key protecting the kimetsu brain database", + "ranked": [ + "kimetsu-eval-fixture-shape" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHVB50Y5BXTDMZRNKFGR", + "id": "01M1Y0E7CBH0STYAZ53HGHCQYB", + "kind": "memory", + "score": 0.8046634197235107, + "summary": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` — a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases)." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 1012.8033, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 817, + "mcp_result_bytes": 942, + "wire_bytes": 978, + "reported_used_tokens": 942, + "working_set_bytes": 286007296, + "peak_working_set_bytes": 286928896 + }, + { + "query": "which tokio runtime worker-thread settings does the kimetsu MCP server use", + "ranked": [ + "tokio-blocking-in-async", + "tokio-runtime-in-tests", + "mcp-stdout-protocol" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CG4GXEDWCNR553B345ZY", + "id": "01M1Y0E8BVCW4EJ0A3XWYPXZTG", + "kind": "memory", + "score": 0.9973159432411194, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking — never call rusqlite directly from an async fn without spawn_blocking." + }, + { + "expansion_handle": "memory:01M1Y0CG5NP8CQF81Q3J12R0GS", + "id": "01M1Y0E8BV7BCNX6367TS3JCQN", + "kind": "memory", + "score": 0.8583173155784607, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + }, + { + "expansion_handle": "memory:01M1Y0CGVVT9QFVZB1R92FVJYX", + "id": "01M1Y0E8BVFCS9H76KNT186YPB", + "kind": "memory", + "score": 0.8141786456108093, + "summary": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 1055.815, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1847, + "mcp_result_bytes": 1972, + "wire_bytes": 2008, + "reported_used_tokens": 1972, + "working_set_bytes": 287473664, + "peak_working_set_bytes": 288391168 + }, + { + "query": "how does kimetsu sync memories between two machines over the network", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 1042.1079, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 287506432, + "peak_working_set_bytes": 288423936 + }, + { + "query": "recovering a corrupted usearch ANN index after a power loss", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 912.1238000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 287608832, + "peak_working_set_bytes": 288526336 + }, + { + "query": "what postgres schema should I use to store kimetsu memories", + "ranked": [ + "kimetsu-memory-scopes", + "testing-fixture-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHHMQS2VGXDD5KZTKWV1", + "id": "01M1Y0EB9ZNZF9VM3Q5CT8J01Q", + "kind": "memory", + "score": 0.9890244603157043, + "summary": "project:fact - [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available — if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope." + }, + { + "expansion_handle": "memory:01M1Y0CGTZS0E9FZN5W5KHQQXA", + "id": "01M1Y0EB9Z1H4TD2Y703EF5XAF", + "kind": "memory", + "score": 0.8922504782676697, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 988.5531, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1389, + "mcp_result_bytes": 1488, + "wire_bytes": 1524, + "reported_used_tokens": 1488, + "working_set_bytes": 287924224, + "peak_working_set_bytes": 288829440 + }, + { + "query": "the whole CI job just froze forever with no failure output after my latest test PR", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1006.6881000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 287936512, + "peak_working_set_bytes": 288894976 + }, + { + "query": "running the test suite left junk state in my home directory", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1045.4680999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 287936512, + "peak_working_set_bytes": 288894976 + }, + { + "query": "I deleted a bunch of old rows but the file on disk is still the same size", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1001.1015000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 287940608, + "peak_working_set_bytes": 288894976 + }, + { + "query": "adding one new crate quietly changed how the whole workspace builds", + "ranked": [ + "cargo-feature-unification-embeddings", + "cargo-lockfile-drift", + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDJN5BPC6G4D9PSYG7G6", + "id": "01M1Y0EF880EV4WPEJTPA6F29A", + "kind": "memory", + "score": 0.9941080808639526, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1Y0CEFG82GG4CW59T7HTY9Z", + "id": "01M1Y0EF88GDFA6D7HGF6XYXGB", + "kind": "memory", + "score": 0.9717232584953308, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this — it errors on any lockfile diff." + }, + { + "expansion_handle": "memory:01M1Y0CEHKWTJ8ZMXANWDJAMCD", + "id": "01M1Y0EF880CPJ868MP49PK53E", + "kind": "memory", + "score": 0.9183088541030884, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1029.3306, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2374, + "mcp_result_bytes": 2495, + "wire_bytes": 2531, + "reported_used_tokens": 2495, + "working_set_bytes": 288346112, + "peak_working_set_bytes": 289271808 + }, + { + "query": "we cannot pull an async runtime into the agent just to talk to AWS", + "ranked": [ + "tokio-blocking-in-async", + "tokio-runtime-in-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CG4GXEDWCNR553B345ZY", + "id": "01M1Y0EG8RTXQ7A41780DS162G", + "kind": "memory", + "score": 0.7520647644996643, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking — never call rusqlite directly from an async fn without spawn_blocking." + }, + { + "expansion_handle": "memory:01M1Y0CG5NP8CQF81Q3J12R0GS", + "id": "01M1Y0EG8R31Y1RXJ4G2M14H48", + "kind": "memory", + "score": 0.7233642935752869, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1045.6879999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1369, + "mcp_result_bytes": 1476, + "wire_bytes": 1512, + "reported_used_tokens": 1476, + "working_set_bytes": 288346112, + "peak_working_set_bytes": 289271808 + }, + { + "query": "users should be able to tell which build variant they installed from the version output", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1028.4616, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288346112, + "peak_working_set_bytes": 289271808 + }, + { + "query": "what gotchas should I expect writing process-inspection code that works on both Windows and Unix?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 959.8008, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288350208, + "peak_working_set_bytes": 289275904 + }, + { + "query": "why might tests behave differently on my machine than in the full CI run?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 991.1095, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288727040, + "peak_working_set_bytes": 289648640 + }, + { + "query": "what do I need to know before wiring kimetsu into a brand new host agent?", + "ranked": [ + "bridge-target-enum-seams", + "kimetsu-daemon-lifecycle", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDNPXTVTYW8S244N1GCB", + "id": "01M1Y0EM85D3W91YD12FBR5Q4N", + "kind": "memory", + "score": 0.9741999506950378, + "summary": "project:fact - [2026-09-07] [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + }, + { + "expansion_handle": "memory:01M1Y0CHFNNPJ5W1HT08QTSPX7", + "id": "01M1Y0EM8522G45VP6PE5WD40B", + "kind": "memory", + "score": 0.9637662768363952, + "summary": "project:fact - [2026-09-07] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1Y0CDGSHENNVZQKGWMDBNDR", + "id": "01M1Y0EM85VH5WH99KS6N8XJD0", + "kind": "memory", + "score": 0.4149944484233856, + "summary": "project:fact - [2026-09-07] [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 0.6666666666666666, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1124.4542, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2390, + "mcp_result_bytes": 2555, + "wire_bytes": 2591, + "reported_used_tokens": 2555, + "working_set_bytes": 288731136, + "peak_working_set_bytes": 289652736 + }, + { + "query": "tell me everything relevant to running kimetsu against AWS", + "ranked": [ + "kimetsu-mrr-metric", + "aws-credentials-chain", + "cargo-feature-unification-embeddings", + "kimetsu-eval-fixture-shape" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHWG3T7N3ZZ8F2Z1EQHR", + "id": "01M1Y0EN9QMZRSDST2BJC3DXNT", + "kind": "memory", + "score": 0.984548270702362, + "summary": "project:fact - [tags: kimetsu bench mrr recall metrics evaluation] kimetsu bench reports MRR (Mean Reciprocal Rank) and Recall@K. MRR is 1/rank_of_first_relevant_result, averaged across cases; it penalizes models that rank the correct answer 2nd or 3rd. Recall@K is the fraction of cases where at least one relevant answer appears in the top K." + }, + { + "expansion_handle": "memory:01M1Y0CH2MJMDSTCRQ0SR4KPC5", + "id": "01M1Y0EN9Q8YJ03F5FDRK10TBG", + "kind": "memory", + "score": 0.9737622141838074, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + }, + { + "expansion_handle": "memory:01M1Y0CDJN5BPC6G4D9PSYG7G6", + "id": "01M1Y0EN9Q6A02W9FQTNY5A40A", + "kind": "memory", + "score": 0.9726329445838928, + "summary": "project:fact - [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1Y0CHVB50Y5BXTDMZRNKFGR", + "id": "01M1Y0EN9Q2RGKN7DXM8CB7FJD", + "kind": "memory", + "score": 0.9641559720039368, + "summary": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` — a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases)." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1025.5521999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2883, + "mcp_result_bytes": 3066, + "wire_bytes": 3102, + "reported_used_tokens": 3066, + "working_set_bytes": 288763904, + "peak_working_set_bytes": 289677312 + }, + { + "query": "ingesting a cloned repo when the brain lives under a different root", + "ranked": [ + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDERH0MV40CSBCQDEQYA", + "id": "01M1Y0EP9K1RE5SV3GD0YY7FBS", + "kind": "memory", + "score": 0.9995300769805908, + "summary": "project:fact - [2026-09-07] [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 963.9586999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1274, + "mcp_result_bytes": 1355, + "wire_bytes": 1391, + "reported_used_tokens": 1355, + "working_set_bytes": 288763904, + "peak_working_set_bytes": 289677312 + }, + { + "query": "streamable-http transport entry for openclaw.json with a bearer token", + "ranked": [ + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDGSHENNVZQKGWMDBNDR", + "id": "01M1Y0EQ81SATC515Q8H4CDYEY", + "kind": "memory", + "score": 0.9921918511390686, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 995.9834, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 996, + "mcp_result_bytes": 1125, + "wire_bytes": 1161, + "reported_used_tokens": 1125, + "working_set_bytes": 288763904, + "peak_working_set_bytes": 289685504 + }, + { + "query": "serializing ingests with a tokio mutex to avoid checkout races", + "ranked": [ + "remote-ingest-split-roots", + "testing-serial-vs-parallel", + "tokio-select-cancellation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDERH0MV40CSBCQDEQYA", + "id": "01M1Y0ER7CCPAKQVKCQQ3YSVCG", + "kind": "memory", + "score": 0.9795480966567992, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1Y0CGRCR5ZFR10QAFCTQWHR", + "id": "01M1Y0ER7C65RPR64N1PY7H9ZT", + "kind": "memory", + "score": 0.9425267577171326, + "summary": "project:fact - [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`)." + }, + { + "expansion_handle": "memory:01M1Y0CG6WN1WWXEWWMMGQZ45N", + "id": "01M1Y0ER7CEM3WT8NNMTFVZ306", + "kind": "memory", + "score": 0.5619664192199707, + "summary": "project:fact - [tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1092.3582999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2376, + "mcp_result_bytes": 2493, + "wire_bytes": 2529, + "reported_used_tokens": 2493, + "working_set_bytes": 288763904, + "peak_working_set_bytes": 289689600 + }, + { + "query": "percent-encoding the colon in the bedrock model id for the invoke URL", + "ranked": [ + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDKZJ260T9MFP5RRHHQY", + "id": "01M1Y0ES9R8J4JEECYP5QZX3PA", + "kind": "memory", + "score": 0.8341025710105896, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1005.7819999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1204, + "mcp_result_bytes": 1293, + "wire_bytes": 1329, + "reported_used_tokens": 1293, + "working_set_bytes": 288763904, + "peak_working_set_bytes": 289689600 + }, + { + "query": "deduplicating re-imported memories against pre-existing ids", + "ranked": [ + "import-dedup-seen-ids" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE0BVW8K017DXQ2P7M8E", + "id": "01M1Y0ET8JZ1G0R5QHQ6V1DFB2", + "kind": "memory", + "score": 0.9991393089294434, + "summary": "project:fact - [tags: rust kimetsu-brain import dedup add_memory] When implementing import dedup for `add_memory` (which returns the existing ID on a normalized-text collision), a per-call `seen_ids` set starting fresh on each `import_memories` invocation will miscount — both a first-import and a re-import appear identical to the set. The correct pattern is: (1) snapshot all pre-existing active memory IDs via a read-only query BEFORE the loop, (2) maintain a within-batch set for intra-batch dedup. Then `deduped += 1` when the returned ID is in either set; `imported += 1` otherwise." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1061.6083, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 966, + "mcp_result_bytes": 1047, + "wire_bytes": 1083, + "reported_used_tokens": 1047, + "working_set_bytes": 288763904, + "peak_working_set_bytes": 289689600 + }, + { + "query": "parsing DMTF datetimes", + "ranked": [ + "process-start-time-cross-platform" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE2KCEZ4A9F2TGGTDYZY", + "id": "01M1Y0EVA551X5QT05Y34BBJQQ", + "kind": "memory", + "score": 0.9934942126274108, + "summary": "project:fact - [tags: rust process windows doctor cross-platform] When adding process start-time to a cross-platform struct, use Option (epoch secs). On Windows, extend the CIM Select-Object to include CreationDate and parse the WMI DMTF datetime format (YYYYMMDDHHmmss.ffffff+/-UUU) with a no-dep pure-Rust function (days_since_epoch + offset math). On Unix, switch ps from \"pid=,args=\" to \"pid=,etimes=,args=\" and detect etimes by trying to parse the second token as u64; fall back to started_at: None if it's non-numeric (the exe path)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 782.3856999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 924, + "mcp_result_bytes": 1013, + "wire_bytes": 1049, + "reported_used_tokens": 1013, + "working_set_bytes": 288763904, + "peak_working_set_bytes": 289689600 + }, + { + "query": "how should install derive a stable identifier from the git remote URL?", + "ranked": [ + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDGSHENNVZQKGWMDBNDR", + "id": "01M1Y0EW26TARBSS8Q9NW03A18", + "kind": "memory", + "score": 0.98285174369812, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 971.8706999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 995, + "mcp_result_bytes": 1124, + "wire_bytes": 1160, + "reported_used_tokens": 1124, + "working_set_bytes": 288768000, + "peak_working_set_bytes": 289689600 + }, + { + "query": "the secret token must not end up written into the host config file", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1072.4509, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288772096, + "peak_working_set_bytes": 289693696 + }, + { + "query": "keep the cleanup logic unit-testable without touching environment variables", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1019.6959999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288772096, + "peak_working_set_bytes": 289693696 + }, + { + "query": "how do we stop the server from cloning arbitrary repos clients request?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1011.4857, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288776192, + "peak_working_set_bytes": 289697792 + }, + { + "query": "make sure a wrong guess about a host plugin API never breaks that host", + "ranked": [ + "pi-openclaw-extension-api" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDPTB91KV3WJ6A8WXR9N", + "id": "01M1Y0F01ZFR9DPJ4VXBAT6QRE", + "kind": "memory", + "score": 0.928434193134308, + "summary": "project:fact - [tags: pi earendil-works extension host-integration bridge] Pi (earendil-works/pi) extensions use a default-export factory `export default function(pi: ExtensionAPI)`, are auto-discovered from ~/.pi/agent/extensions/ (global) or .pi/extensions/ (project). settings.json registers them via an `\"extensions\": [\"./extensions/kimetsu.ts\"]` array. Lifecycle events: `session_start`, `agent_end`, `session_shutdown`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 856.2956, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 803, + "mcp_result_bytes": 892, + "wire_bytes": 928, + "reported_used_tokens": 892, + "working_set_bytes": 288776192, + "peak_working_set_bytes": 289701888 + }, + { + "query": "which wire-format trick lets us reuse the existing Anthropic request builder for AWS?", + "ranked": [ + "bedrock-kimetsu-provider" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDKZJ260T9MFP5RRHHQY", + "id": "01M1Y0F0WEWN0ADWYXDGAHGXVF", + "kind": "memory", + "score": 0.9748817682266236, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1038.5819000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1203, + "mcp_result_bytes": 1292, + "wire_bytes": 1328, + "reported_used_tokens": 1292, + "working_set_bytes": 288976896, + "peak_working_set_bytes": 289898496 + }, + { + "query": "the self-update froze because something was still holding the executable", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1000.2845, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288976896, + "peak_working_set_bytes": 289898496 + }, + { + "query": "our notes about the extension API turned out wrong once we read the actual repo", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1011.5813, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288976896, + "peak_working_set_bytes": 289898496 + }, + { + "query": "half the benchmark trials die right after the first one finishes", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1018.7479, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288976896, + "peak_working_set_bytes": 289898496 + }, + { + "query": "I need this parser visible to tests on every OS even though only one OS calls it", + "ranked": [ + "cfg-cross-platform-dead-code" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE53127TY01EC3730MH1", + "id": "01M1Y0F4VQC9WRC9FG9RXFWDG3", + "kind": "memory", + "score": 0.36490198969841, + "summary": "project:fact - [tags: rust cfg cross-platform dead_code windows] When adding pure cfg-agnostic parsers that are live-called only on one platform (e.g. parse_unix_ps on Unix only), annotate them with #[cfg_attr(windows, allow(dead_code))] so -D warnings stays clean on Windows without hiding the function from cross-platform tests. This is better than cfg-gating the pub fn itself, which would prevent the test from compiling on the other platform." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 993.4152, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 939, + "reported_used_tokens": 903, + "working_set_bytes": 288976896, + "peak_working_set_bytes": 289898496 + }, + { + "query": "the config file content refuses to parse even though the TOML looks valid", + "ranked": [ + "toml-value-parse" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE1F83ZZ1GGZDPZBKQMF", + "id": "01M1Y0F5WZGWZ43Y5CPB7MN9J5", + "kind": "memory", + "score": 0.6614054441452026, + "summary": "project:fact - [tags: rust toml config kimetsu-cli] In toml 0.9, use `toml::from_str::(&text)` to parse a TOML document into a Value (not `str.parse::()`which expects a bare value). To serialize a Serialize struct into a toml::Value, use `toml::Value::try_from(&cfg)`. The Value variants are Boolean/Integer/Float/String/Array/Table." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1106.2243999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 733, + "mcp_result_bytes": 814, + "wire_bytes": 850, + "reported_used_tokens": 814, + "working_set_bytes": 288976896, + "peak_working_set_bytes": 289898496 + }, + { + "query": "the remote server must refresh its checkout before answering file queries", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1056.3914000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 288976896, + "peak_working_set_bytes": 289898496 + }, + { + "query": "tests must not climb to a parent git repository when resolving project paths", + "ranked": [ + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDW1WMXYA9MGV0TK68J6", + "id": "01M1Y0F7Y76HKYH87CVDADJ1SV", + "kind": "memory", + "score": 0.9839988350868224, + "summary": "project:fact - [2026-09-07] [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 999.3949, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 794, + "mcp_result_bytes": 875, + "wire_bytes": 911, + "reported_used_tokens": 875, + "working_set_bytes": 288976896, + "peak_working_set_bytes": 289898496 + }, + { + "query": "how do I test request signing deterministically when timestamps change every run?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1005.4948999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 289042432, + "peak_working_set_bytes": 289959936 + }, + { + "query": "adding a new variant to the host target enum - which places will I forget to update?", + "ranked": [ + "bridge-target-enum-seams" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CDNPXTVTYW8S244N1GCB", + "id": "01M1Y0F9X6YBVZ1A6T9N66Q4VZ", + "kind": "memory", + "score": 0.885076105594635, + "summary": "project:fact - [tags: kimetsu bridge rust host-integration architecture] When adding a new host to Kimetsu's BridgeTarget enum, the following seams ALL need updating: BridgeTarget enum + parse + as_str, plugin_install_inner match arm, plugin_status_inner loop list + match arm, plugin_uninstall_inner match arm, bridge_export_skill match arm, resolve_setup_hosts (new bool param + detection + TTY prompt + interactive answer), detect_present_hosts (return tuple expands), setup_cmd caller (destructure new tuple), plus all host_label match expressions in main.rs, plus any existing test calls to resolve_setup_hosts (arg count changes). Missing any one of these causes compile errors." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1089.1136, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1058, + "mcp_result_bytes": 1139, + "wire_bytes": 1175, + "reported_used_tokens": 1139, + "working_set_bytes": 289050624, + "peak_working_set_bytes": 289968128 + }, + { + "query": "how do I enable GPU acceleration for kimetsu embedding inference", + "ranked": [ + "mcp-tool-timeouts", + "kimetsu-proactive-hooks" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGWYZ09QBQFPX4FEVE70", + "id": "01M1Y0FB06KNNB1JJNJWPEXWQH", + "kind": "memory", + "score": 0.9826309084892272, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking — in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize — keep it in a process-global `OnceLock`)." + }, + { + "expansion_handle": "memory:01M1Y0CHKTX9JMQPFAQV1QS487", + "id": "01M1Y0FB06EGZW7JSJ62N1D3FR", + "kind": "memory", + "score": 0.8807981610298157, + "summary": "project:fact - [tags: kimetsu proactive hooks context injection] kimetsu's proactive context injection runs before each agent turn (pre-turn hook) and injects relevant memories into the system prompt prefix. The hook invocation adds latency to the first token: embedding inference + vector search + reranking + context formatting. On a cold start, this can be 1-3 seconds." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 1053.7009, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1377, + "mcp_result_bytes": 1476, + "wire_bytes": 1512, + "reported_used_tokens": 1476, + "working_set_bytes": 289050624, + "peak_working_set_bytes": 289968128 + }, + { + "query": "how do I throttle kimetsu API spend per month", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 933.7639, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 289050624, + "peak_working_set_bytes": 289972224 + }, + { + "query": "can the kimetsu brain database be stored in S3 instead of on disk", + "ranked": [ + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CH6AWWAV734TFW14SHW0", + "id": "01M1Y0FCXGK395B7ZSMMVPS3HX", + "kind": "memory", + "score": 0.38596054911613464, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time — clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 934.3241, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 875, + "mcp_result_bytes": 956, + "wire_bytes": 992, + "reported_used_tokens": 956, + "working_set_bytes": 289050624, + "peak_working_set_bytes": 289972224 + }, + { + "query": "how do I plug a custom tokenizer into the FTS index", + "ranked": [ + "sqlite-fts5-tokenizer" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE96YPFCCNT1Z92GH3C5", + "id": "01M1Y0FDTWN599XEGBHY3AS6B0", + "kind": "memory", + "score": 0.9691632390022278, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 1047.9415999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 671, + "mcp_result_bytes": 756, + "wire_bytes": 792, + "reported_used_tokens": 756, + "working_set_bytes": 289054720, + "peak_working_set_bytes": 289972224 + }, + { + "query": "what should I check when kimetsu behaves differently on Windows than on Linux?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1004.2382000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 326, + "reported_used_tokens": 290, + "working_set_bytes": 289128448, + "peak_working_set_bytes": 290045952 + }, + { + "query": "what are the moving parts of the kimetsu remote deployment story?", + "ranked": [ + "kimetsu-write-tools-gate", + "ci-secrets-masking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHMVHR9GRRHBAB5NZNDN", + "id": "01M1Y0FFTP5VM3VJWD35D2V9Z9", + "kind": "memory", + "score": 0.9729357361793518, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level — disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1Y0CHCK07ASK7V6B3J3GTZF", + "id": "01M1Y0FFTP3C24QPDN3Y8GT2TY", + "kind": "memory", + "score": 0.8412115573883057, + "summary": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output — but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 996.9313, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1410, + "mcp_result_bytes": 1509, + "wire_bytes": 1546, + "reported_used_tokens": 1509, + "working_set_bytes": 289505280, + "peak_working_set_bytes": 290426880 + }, + { + "query": "which lessons cover guarding behavior behind environment variables?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 876.0069000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 289509376, + "peak_working_set_bytes": 290426880 + }, + { + "query": "SQLite BUSY error under concurrent writes", + "ranked": [ + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE63YDGRR9GCFM6A405N", + "id": "01M1Y0FHNBEH06PER26XNPR3ME", + "kind": "memory", + "score": 0.9978362917900084, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 870.2198, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 898, + "mcp_result_bytes": 979, + "wire_bytes": 1016, + "reported_used_tokens": 979, + "working_set_bytes": 289509376, + "peak_working_set_bytes": 290426880 + }, + { + "query": "SQLite WAL mode breaks when the database is on a network share", + "ranked": [ + "sqlite-wal-network-drive", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE84FR94JFPZ2CTRK5KY", + "id": "01M1Y0FJGD50RC9T21JG9KJS3G", + "kind": "memory", + "score": 0.999302864074707, + "summary": "project:fact - [2026-09-07] [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + }, + { + "expansion_handle": "memory:01M1Y0CE63YDGRR9GCFM6A405N", + "id": "01M1Y0FJGD5SD54SYG8A5BFDYA", + "kind": "memory", + "score": 0.9966553449630736, + "summary": "project:fact - [2026-09-07] [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1017.2783000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1448, + "mcp_result_bytes": 1547, + "wire_bytes": 1584, + "reported_used_tokens": 1547, + "working_set_bytes": 290041856, + "peak_working_set_bytes": 290959360 + }, + { + "query": "my SQLite WAL database causes SQLITE_IOERR_LOCK on a mapped drive", + "ranked": [ + "sqlite-wal-network-drive" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE84FR94JFPZ2CTRK5KY", + "id": "01M1Y0FKGB7BRPR7G7E4GFST05", + "kind": "memory", + "score": 0.99892657995224, + "summary": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1075.3573000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 748, + "mcp_result_bytes": 829, + "wire_bytes": 866, + "reported_used_tokens": 829, + "working_set_bytes": 290058240, + "peak_working_set_bytes": 290971648 + }, + { + "query": "FTS5 tokenizer configuration for Rust identifiers with underscores", + "ranked": [ + "sqlite-fts5-tokenizer", + "kimetsu-query-stemming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE96YPFCCNT1Z92GH3C5", + "id": "01M1Y0FMHYACSP6TR5Y6GMR36K", + "kind": "memory", + "score": 0.998104453086853, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + }, + { + "expansion_handle": "memory:01M1Y0CHR9RRWWX5FQ2Z9H6B27", + "id": "01M1Y0FMHY38F7RQ1HXAC5CZD4", + "kind": "memory", + "score": 0.7023860812187195, + "summary": "project:fact - [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1002.0119000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1212, + "mcp_result_bytes": 1331, + "wire_bytes": 1368, + "reported_used_tokens": 1331, + "working_set_bytes": 290058240, + "peak_working_set_bytes": 290971648 + }, + { + "query": "I switched the FTS5 tokenizer but search stopped returning results", + "ranked": [ + "sqlite-fts5-tokenizer" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE96YPFCCNT1Z92GH3C5", + "id": "01M1Y0FNHPPRFSQ92DMJQPEMBZ", + "kind": "memory", + "score": 0.8194089531898499, + "summary": "project:fact - [tags: sqlite fts5 tokenizer unicode rust] SQLite FTS5 defaults to the `unicode61` tokenizer which folds case and strips diacritics. If you want prefix search on identifiers (e.g. `cargo::`) use `tokenize='unicode61 tokenchars \"_:\"'` to prevent splitting on underscore and colon." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1030.3192000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 670, + "mcp_result_bytes": 755, + "wire_bytes": 792, + "reported_used_tokens": 755, + "working_set_bytes": 290058240, + "peak_working_set_bytes": 290971648 + }, + { + "query": "optimal SQLite page size for storing embedding vectors", + "ranked": [ + "sqlite-page-size", + "onnx-dim-mismatch", + "onnx-cosine-vs-dot" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CEA9QVBBX2QWZ9FEYJAV", + "id": "01M1Y0FPJ4NWKPJN5GSRBSAV17", + "kind": "memory", + "score": 0.9990121126174928, + "summary": "project:fact - [tags: sqlite page_size performance rusqlite] SQLite's default page_size is 4096 bytes. For a write-heavy brain database with large BLOB payloads (embedding vectors), raising page_size to 16384 reduces fragmentation and improves sequential scan throughput. `PRAGMA page_size = 16384;` must be set BEFORE the first table is created — changing it on an existing database requires a VACUUM afterward to rebuild all pages." + }, + { + "expansion_handle": "memory:01M1Y0CFS76DA1V14BVQR0NX3T", + "id": "01M1Y0FPJ41S8V29JEQ75WK6QW", + "kind": "memory", + "score": 0.9881643056869508, + "summary": "project:fact - [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results — the ANN index shape mismatch isn't always caught at runtime." + }, + { + "expansion_handle": "memory:01M1Y0CFR66B0CWZXF5MVKF7P0", + "id": "01M1Y0FPJ41JT3MEHXPXGENK51", + "kind": "memory", + "score": 0.9425415992736816, + "summary": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing — double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 945.2506000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1860, + "mcp_result_bytes": 1977, + "wire_bytes": 2014, + "reported_used_tokens": 1977, + "working_set_bytes": 290058240, + "peak_working_set_bytes": 290971648 + }, + { + "query": "ON DELETE CASCADE in SQLite does nothing — foreign keys not enforced", + "ranked": [ + "sqlite-foreign-keys-default-off" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CEB89X22N5Z655769N3B", + "id": "01M1Y0FQF1D22DE189D6NY2NWX", + "kind": "memory", + "score": 0.9996858835220336, + "summary": "project:fact - [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting — every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1061.6822, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 736, + "mcp_result_bytes": 817, + "wire_bytes": 854, + "reported_used_tokens": 817, + "working_set_bytes": 290058240, + "peak_working_set_bytes": 290971648 + }, + { + "query": "indexing a JSON metadata column in SQLite without a schema migration", + "ranked": [ + "sqlite-json1-extract", + "testing-fixture-drift", + "onnx-dim-mismatch", + "sqlite-partial-index" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CEC7GDA4T9BPXBA7ZBTW", + "id": "01M1Y0FRG6AA5XA1RMDSV02485", + "kind": "memory", + "score": 0.9955366849899292, + "summary": "project:fact - [tags: sqlite json1 json_extract rusqlite] SQLite's json1 extension (built in since 3.38.0) lets you index and query JSONB columns with `json_extract(col, '$.field')`. To create a partial index over a JSON field: `CREATE INDEX idx ON memories (json_extract(metadata, '$.scope')) WHERE json_extract(metadata, '$.scope') IS NOT NULL;`. Use `json_each` for array fields." + }, + { + "expansion_handle": "memory:01M1Y0CGTZS0E9FZN5W5KHQQXA", + "id": "01M1Y0FRG6GZ18649MNTJNR0QG", + "kind": "memory", + "score": 0.8227390646934509, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + }, + { + "expansion_handle": "memory:01M1Y0CFS76DA1V14BVQR0NX3T", + "id": "01M1Y0FRG6NXN6JZDWVQWHKHEX", + "kind": "memory", + "score": 0.38374292850494385, + "summary": "project:fact - [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results — the ANN index shape mismatch isn't always caught at runtime." + }, + { + "expansion_handle": "memory:01M1Y0CEEF7Z67N696N9ZMRADF", + "id": "01M1Y0FRG671G963N8FMH97X9N", + "kind": "memory", + "score": 0.3276048004627228, + "summary": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query — the planner uses the partial index only when the WHERE clause matches." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1004.3054, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2381, + "mcp_result_bytes": 2516, + "wire_bytes": 2553, + "reported_used_tokens": 2516, + "working_set_bytes": 290070528, + "peak_working_set_bytes": 290983936 + }, + { + "query": "prepare() vs prepare_cached() in rusqlite hot insert loop", + "ranked": [ + "sqlite-prepared-stmt-cache" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CEDDG6HQA9D97BHWH7CT", + "id": "01M1Y0FSFNAEFAYVZEXBX3R81D", + "kind": "memory", + "score": 0.9993672966957092, + "summary": "project:fact - [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 976.4654, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 689, + "mcp_result_bytes": 770, + "wire_bytes": 807, + "reported_used_tokens": 770, + "working_set_bytes": 290074624, + "peak_working_set_bytes": 290983936 + }, + { + "query": "speed up bulk memory ingest by caching SQL statements", + "ranked": [ + "sqlite-prepared-stmt-cache" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CEDDG6HQA9D97BHWH7CT", + "id": "01M1Y0FTE4135DKDF9ZWFHZQ19", + "kind": "memory", + "score": 0.9823396801948548, + "summary": "project:fact - [tags: sqlite prepared-statement rusqlite caching performance] rusqlite's `Connection::prepare` parses and compiles SQL every call. For hot paths (e.g. per-memory insert during ingest), use `Connection::prepare_cached` which stores compiled statements in a per-connection LRU cache (default size 8)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 987.2556999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 688, + "mcp_result_bytes": 769, + "wire_bytes": 806, + "reported_used_tokens": 769, + "working_set_bytes": 290078720, + "peak_working_set_bytes": 290988032 + }, + { + "query": "partial index on deleted_at IS NULL for faster active memory queries", + "ranked": [ + "sqlite-partial-index" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CEEF7Z67N696N9ZMRADF", + "id": "01M1Y0FVD172JBJ2SAMH48PJ6P", + "kind": "memory", + "score": 0.9988954067230223, + "summary": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query — the planner uses the partial index only when the WHERE clause matches." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 968.5093999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 794, + "mcp_result_bytes": 875, + "wire_bytes": 912, + "reported_used_tokens": 875, + "working_set_bytes": 290095104, + "peak_working_set_bytes": 291000320 + }, + { + "query": "the brain query is slow because it scans all rows including soft-deleted ones", + "ranked": [ + "sqlite-partial-index" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CEEF7Z67N696N9ZMRADF", + "id": "01M1Y0FWBASSYWCY3RJWGV1ZDG", + "kind": "memory", + "score": 0.5760471224784851, + "summary": "project:fact - [tags: sqlite partial-index performance schema] A SQLite partial index (`CREATE INDEX ... WHERE condition`) covers only the rows matching the condition, making it smaller and faster to scan for filtered queries. In kimetsu, an index on `(scope, created_at DESC) WHERE deleted_at IS NULL` speeds up the most common retrieval query — the planner uses the partial index only when the WHERE clause matches." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1059.3857, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 793, + "mcp_result_bytes": 874, + "wire_bytes": 911, + "reported_used_tokens": 874, + "working_set_bytes": 290566144, + "peak_working_set_bytes": 291487744 + }, + { + "query": "Cargo.lock changed unexpectedly after adding a new workspace crate", + "ranked": [ + "cargo-lockfile-drift", + "cargo-feature-unification-embeddings", + "cargo-target-dir-sharing", + "cargo-patch-section" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CEFG82GG4CW59T7HTY9Z", + "id": "01M1Y0FXCNTQMTDWXNZ28NWQB8", + "kind": "memory", + "score": 0.9991374015808104, + "summary": "project:fact - [2026-09-07] [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this — it errors on any lockfile diff." + }, + { + "expansion_handle": "memory:01M1Y0CDJN5BPC6G4D9PSYG7G6", + "id": "01M1Y0FXCNM9DJBVA73A0586KH", + "kind": "memory", + "score": 0.9968542456626892, + "summary": "project:fact - [2026-09-07] [tags: rust cargo feature-unification kimetsu testing] Cargo feature unification gotcha: adding a new workspace member crate with `default = [\"embeddings\"]` (forwarding to `kimetsu-brain/embeddings`) silently turned embeddings ON for the ENTIRE `cargo test --workspace` build graph — because cargo unifies features across all packages built in one invocation. This made kimetsu-chat's retrieval tests (written for the Noop/FTS embedder) run against real fastembed and fail with `left: None` (missing response fields), while passing in isolation (`cargo test -p kimetsu-chat`, which uses that crate's own default features). Fix: make the new crate `default = []` (lean), opt into embeddings via `--features embeddings` (matches kimetsu-cli)." + }, + { + "expansion_handle": "memory:01M1Y0CEJNCGQE145VV7PMAA9T", + "id": "01M1Y0FXCNV78HE76QT6WEZWXN", + "kind": "memory", + "score": 0.9829630851745604, + "summary": "project:fact - [2026-09-07] [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps — use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + }, + { + "expansion_handle": "memory:01M1Y0CENN1SARTT7M8KTJ5XE9", + "id": "01M1Y0FXCPTJ40T7TQ7VJCQ1VG", + "kind": "memory", + "score": 0.9262890815734864, + "summary": "project:fact - [2026-09-07] [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace — including transitive deps — that depend on `my-crate`. Remove the patch before publishing." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 882.3282999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3010, + "mcp_result_bytes": 3153, + "wire_bytes": 3190, + "reported_used_tokens": 3153, + "working_set_bytes": 290570240, + "peak_working_set_bytes": 291491840 + }, + { + "query": "how do I prevent CI from accepting a modified lockfile silently?", + "ranked": [ + "cargo-lockfile-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CEFG82GG4CW59T7HTY9Z", + "id": "01M1Y0FY8926Z3212MP1JSZMPT", + "kind": "memory", + "score": 0.9125379323959352, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this — it errors on any lockfile diff." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 998.5935999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 765, + "mcp_result_bytes": 846, + "wire_bytes": 883, + "reported_used_tokens": 846, + "working_set_bytes": 290574336, + "peak_working_set_bytes": 291495936 + }, + { + "query": "build.rs reruns on every incremental build even when nothing changed", + "ranked": [ + "cargo-build-script-rerun" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CEGFZFDFJ4ZM064X7YNY", + "id": "01M1Y0FZ88H0FXCXYGG1H0Y9QM", + "kind": "memory", + "score": 0.9996689558029176, + "summary": "project:fact - [2026-09-07] [tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1051.6212, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 698, + "mcp_result_bytes": 779, + "wire_bytes": 816, + "reported_used_tokens": 779, + "working_set_bytes": 290586624, + "peak_working_set_bytes": 291508224 + }, + { + "query": "incremental cargo build is slow because build script runs every time", + "ranked": [ + "cargo-build-script-rerun" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CEGFZFDFJ4ZM064X7YNY", + "id": "01M1Y0G088DWY10HES8XDY085S", + "kind": "memory", + "score": 0.9978280663490297, + "summary": "project:fact - [tags: cargo build-script build.rs rerun-if-changed] Cargo reruns `build.rs` on every build if you don't emit `cargo:rerun-if-changed=...` directives. Without them, incremental builds get the full build.rs overhead on every `cargo build`. Emit one `rerun-if-changed` per input file or directory." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 943.1259, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 685, + "mcp_result_bytes": 766, + "wire_bytes": 803, + "reported_used_tokens": 766, + "working_set_bytes": 290590720, + "peak_working_set_bytes": 291508224 + }, + { + "query": "a dev-dependency is activating an embeddings feature in my production build", + "ranked": [ + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CEHKWTJ8ZMXANWDJAMCD", + "id": "01M1Y0G15MRR51Q8HX4EHQY9RB", + "kind": "memory", + "score": 0.9944193959236144, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1034.071, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 931, + "mcp_result_bytes": 1012, + "wire_bytes": 1049, + "reported_used_tokens": 1012, + "working_set_bytes": 290672640, + "peak_working_set_bytes": 291590144 + }, + { + "query": "how do I prevent a test-only feature from bleeding into the non-test compilation?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 989.1259, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 290684928, + "peak_working_set_bytes": 291602432 + }, + { + "query": "linker errors in target/ caused by antivirus holding the exe file", + "ranked": [ + "windows-file-locking-av", + "cargo-target-dir-sharing" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFCYM1PTCWE8TVCBYW97", + "id": "01M1Y0G36ACK84MR3NJ6SG159H", + "kind": "memory", + "score": 0.9997633099555968, + "summary": "project:fact - [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + }, + { + "expansion_handle": "memory:01M1Y0CEJNCGQE145VV7PMAA9T", + "id": "01M1Y0G36ABH4H90QPF80TG3WJ", + "kind": "memory", + "score": 0.7463976740837097, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps — use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1041.0261, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1523, + "mcp_result_bytes": 1622, + "wire_bytes": 1659, + "reported_used_tokens": 1622, + "working_set_bytes": 290697216, + "peak_working_set_bytes": 291610624 + }, + { + "query": "Access is denied (os error 5) when linking on Windows — how do I fix this?", + "ranked": [ + "windows-file-locking-av" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFCYM1PTCWE8TVCBYW97", + "id": "01M1Y0G45TM0EEANVBYCKTA795", + "kind": "memory", + "score": 0.9977193474769592, + "summary": "project:fact - [2026-09-07] [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1014.0079, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 769, + "mcp_result_bytes": 850, + "wire_bytes": 887, + "reported_used_tokens": 850, + "working_set_bytes": 290725888, + "peak_working_set_bytes": 291639296 + }, + { + "query": "incremental build broke with a type mismatch after switching branches", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CEKN0QBDZ73SACBN6Y6X", + "id": "01M1Y0G556GQ68DGHMFZE74QMV", + "kind": "memory", + "score": 0.7971777319908142, + "summary": "project:fact - [2026-09-07] [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1026.0916, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 890, + "mcp_result_bytes": 971, + "wire_bytes": 1008, + "reported_used_tokens": 971, + "working_set_bytes": 290725888, + "peak_working_set_bytes": 291643392 + }, + { + "query": "cargo reports a type error that references a type not in the codebase", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CEKN0QBDZ73SACBN6Y6X", + "id": "01M1Y0G65QGDV6593B7SWB1W1E", + "kind": "memory", + "score": 0.7925198078155518, + "summary": "project:fact - [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 981.2978, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 877, + "mcp_result_bytes": 958, + "wire_bytes": 995, + "reported_used_tokens": 958, + "working_set_bytes": 290725888, + "peak_working_set_bytes": 291651584 + }, + { + "query": "compile fastembed at O2 in debug builds to avoid slow embedding inference", + "ranked": [ + "cargo-profile-override", + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CEMKNXWNXAX5DG21NDNC", + "id": "01M1Y0G74CGZDVCRFEKCEMMZVG", + "kind": "memory", + "score": 0.9932281374931335, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1Y0CGWYZ09QBQFPX4FEVE70", + "id": "01M1Y0G74CES9RC000SXJB7C9V", + "kind": "memory", + "score": 0.987656831741333, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking — in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize — keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1035.9539, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1322, + "mcp_result_bytes": 1421, + "wire_bytes": 1458, + "reported_used_tokens": 1421, + "working_set_bytes": 290729984, + "peak_working_set_bytes": 291651584 + }, + { + "query": "override compilation profile for a single crate in a Cargo workspace", + "ranked": [ + "cargo-patch-section", + "cargo-profile-override", + "cargo-target-dir-sharing", + "cargo-dev-dep-leak" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CENN1SARTT7M8KTJ5XE9", + "id": "01M1Y0G85P7HYREM0135CTED6M", + "kind": "memory", + "score": 0.9984123706817628, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace — including transitive deps — that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1Y0CEMKNXWNXAX5DG21NDNC", + "id": "01M1Y0G85PG8ER3ZN601ZBM8WV", + "kind": "memory", + "score": 0.9979992508888244, + "summary": "project:fact - [tags: cargo profile dev release overflow-checks] Cargo profiles can be overridden per-crate in the workspace root `Cargo.toml`: `[profile.dev.package.kimetsu-brain] opt-level = 2` compiles one crate at O2 even in debug builds. This is useful when a dep (e.g. fastembed/ONNX) is painfully slow in debug." + }, + { + "expansion_handle": "memory:01M1Y0CEJNCGQE145VV7PMAA9T", + "id": "01M1Y0G85P47QHKJE6PK9SGSHV", + "kind": "memory", + "score": 0.9956549406051636, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps — use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + }, + { + "expansion_handle": "memory:01M1Y0CEHKWTJ8ZMXANWDJAMCD", + "id": "01M1Y0G85PBQJ0DBNN3TANAK3F", + "kind": "memory", + "score": 0.9820712208747864, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 0.5, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1072.6632, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2682, + "mcp_result_bytes": 2821, + "wire_bytes": 2858, + "reported_used_tokens": 2821, + "working_set_bytes": 290738176, + "peak_working_set_bytes": 291663872 + }, + { + "query": "[patch.crates-io] workspace dependency override", + "ranked": [ + "cargo-patch-section", + "cargo-lockfile-drift", + "cargo-dev-dep-leak", + "cargo-target-dir-sharing" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CENN1SARTT7M8KTJ5XE9", + "id": "01M1Y0G95VKMXGTSVB3WN9FT2D", + "kind": "memory", + "score": 0.9999405145645142, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace — including transitive deps — that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1Y0CEFG82GG4CW59T7HTY9Z", + "id": "01M1Y0G95WH1GWKCD7EAN613PP", + "kind": "memory", + "score": 0.9975811243057252, + "summary": "project:fact - [tags: cargo workspace lockfile drift ci] In a Cargo workspace, `Cargo.lock` is shared across all crates. If you add a new crate to the workspace and its dependency tree resolves to different patch versions than what the root already pins, `cargo build` silently updates the lockfile. In CI, always run `cargo build --locked` to catch this — it errors on any lockfile diff." + }, + { + "expansion_handle": "memory:01M1Y0CEHKWTJ8ZMXANWDJAMCD", + "id": "01M1Y0G95WRJ4ZSACAF8AMEFMW", + "kind": "memory", + "score": 0.994149684906006, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + }, + { + "expansion_handle": "memory:01M1Y0CEJNCGQE145VV7PMAA9T", + "id": "01M1Y0G95WQNQQ56RNBQKKMGS0", + "kind": "memory", + "score": 0.7471600770950317, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps — use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 770.3614, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2755, + "mcp_result_bytes": 2894, + "wire_bytes": 2931, + "reported_used_tokens": 2894, + "working_set_bytes": 290738176, + "peak_working_set_bytes": 291663872 + }, + { + "query": "pin minimum supported Rust version in Cargo.toml", + "ranked": [ + "cargo-msrv" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CEPM3E4XMZRG0JF64DYX", + "id": "01M1Y0G9XZ02SXE2C18WAE95FW", + "kind": "memory", + "score": 0.999652862548828, + "summary": "project:fact - [tags: cargo rust msrv edition compatibility] Set `rust-version` in each `Cargo.toml` to declare the minimum supported Rust version (MSRV). Cargo enforces this with `--check`: `cargo check` fails if the toolchain is older than `rust-version`. Keep MSRV as old as your oldest supported deployment target." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 871.2769000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 693, + "mcp_result_bytes": 774, + "wire_bytes": 811, + "reported_used_tokens": 774, + "working_set_bytes": 290738176, + "peak_working_set_bytes": 291663872 + }, + { + "query": "Windows path over 260 characters causes OS error 3 during Cargo build", + "ranked": [ + "windows-long-paths", + "windows-file-locking-av" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFC04YHR8CKTQRYT9FFC", + "id": "01M1Y0GAS70JSGZQ9XZJZGZKD9", + "kind": "memory", + "score": 0.9964189529418944, + "summary": "project:fact - [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe." + }, + { + "expansion_handle": "memory:01M1Y0CFCYM1PTCWE8TVCBYW97", + "id": "01M1Y0GAS7YAV3QP0KY1KBM10T", + "kind": "memory", + "score": 0.9571694135665894, + "summary": "project:fact - [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 894.536, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1297, + "mcp_result_bytes": 1406, + "wire_bytes": 1443, + "reported_used_tokens": 1406, + "working_set_bytes": 290738176, + "peak_working_set_bytes": 291663872 + }, + { + "query": "how do I enable long file paths for Cargo on Windows?", + "ranked": [ + "windows-long-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFC04YHR8CKTQRYT9FFC", + "id": "01M1Y0GBN9ZF7SE49GT26CA9E9", + "kind": "memory", + "score": 0.9998334646224976, + "summary": "project:fact - [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 984.4273999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 769, + "mcp_result_bytes": 860, + "wire_bytes": 897, + "reported_used_tokens": 860, + "working_set_bytes": 290738176, + "peak_working_set_bytes": 291663872 + }, + { + "query": "intermittent sharing violation errors when Rust linker writes the exe on Windows", + "ranked": [ + "windows-file-locking-av", + "windows-long-paths", + "sqlite-busy-timeout-wal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFCYM1PTCWE8TVCBYW97", + "id": "01M1Y0GCMBK1EWTDP971DF704T", + "kind": "memory", + "score": 0.999750316143036, + "summary": "project:fact - [2026-09-07] [tags: windows antivirus file-locking rust build] Windows Defender and similar AV products hold open handles to newly-created EXEs for scanning. This causes sporadic `Access is denied (os error 5)` or `sharing violation (os error 32)` when Rust linker tries to write `target/debug/program.exe`. Workaround: add `target/` to AV exclusions for the development machine." + }, + { + "expansion_handle": "memory:01M1Y0CFC04YHR8CKTQRYT9FFC", + "id": "01M1Y0GCMBT13C1BJAAERAHJMR", + "kind": "memory", + "score": 0.4757097661495209, + "summary": "project:fact - [2026-09-07] [tags: windows long-paths registry cargo rust] Windows historically caps file paths at MAX_PATH (260 chars). Cargo's deeply nested `target/` paths routinely exceed this. Fix: enable long paths via Group Policy or registry (`HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled = 1`) AND set the manifest `true` in the exe." + }, + { + "expansion_handle": "memory:01M1Y0CE63YDGRR9GCFM6A405N", + "id": "01M1Y0GCMBF9NENDEKFE998CSM", + "kind": "memory", + "score": 0.38107830286026, + "summary": "project:fact - [2026-09-07] [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 943.9022, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2006, + "mcp_result_bytes": 2133, + "wire_bytes": 2170, + "reported_used_tokens": 2133, + "working_set_bytes": 290738176, + "peak_working_set_bytes": 291663872 + }, + { + "query": "Rust walkdir follows junctions differently from symlinks on Windows", + "ranked": [ + "windows-junctions-vs-symlinks" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFGA9WH2HS25121CG3FH", + "id": "01M1Y0GDHKACVN1SEN9SAA14QR", + "kind": "memory", + "score": 0.9996020197868348, + "summary": "project:fact - [tags: windows junctions symlinks rust std::fs] On Windows, directory junctions (NTFS reparse points) behave like symlinks for directory traversal but `std::fs::symlink_metadata` returns `FileType::is_symlink() = false` for junctions (only true for regular symlinks). Use `std::fs::read_link` — it succeeds for both junction and symlink. `walkdir` crate's `follow_links` follows both, but its `is_symlink()` method correctly reports only actual symlinks." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 994.0446, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 845, + "mcp_result_bytes": 926, + "wire_bytes": 963, + "reported_used_tokens": 926, + "working_set_bytes": 290742272, + "peak_working_set_bytes": 291663872 + }, + { + "query": "UNC path canonicalize returns verbatim prefix — how do I strip it?", + "ranked": [ + "windows-unc-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFDY80D7VPYKJS1EQ973", + "id": "01M1Y0GEGZ9HAHM6Z121H4RCC7", + "kind": "memory", + "score": 0.9988629817962646, + "summary": "project:fact - [tags: windows unc-paths rust std::fs] Windows UNC paths (`\\\\server\\share\\...`) are not supported by most Rust `std::fs` operations unless passed through the extended-length prefix `\\\\?\\UNC\\server\\share\\...`. `std::path::Path::new(\"\\\\\\\\server\\\\share\")` works for basic operations but breaks with `canonicalize()` which returns the verbatim prefix form. When walking directory trees that may start on UNC paths, use the `dunce` crate to strip the verbatim prefix before comparing or displaying paths." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1013.0558000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 908, + "mcp_result_bytes": 1025, + "wire_bytes": 1062, + "reported_used_tokens": 1025, + "working_set_bytes": 290742272, + "peak_working_set_bytes": 291672064 + }, + { + "query": "UTF-8 memory text prints as mojibake in the Windows console", + "ranked": [ + "windows-console-encoding" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFF4Q7Y08GK0RN1VKVZV", + "id": "01M1Y0GFGKBCZSEY7MDE9DDE4T", + "kind": "memory", + "score": 0.9996604919433594, + "summary": "project:fact - [tags: windows console encoding utf8 rust] Windows console code page defaults to the system ANSI code page (usually CP1252 or CP932), not UTF-8. Rust's `println!` writes UTF-8 bytes which display as mojibake in a non-UTF-8 console. Fix at process startup: call `SetConsoleOutputCP(65001)` via `winapi` or `windows-sys`, or set `PYTHONUTF8=1`/`RUST_LOG` before launch." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 982.9894, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 757, + "mcp_result_bytes": 838, + "wire_bytes": 875, + "reported_used_tokens": 838, + "working_set_bytes": 290742272, + "peak_working_set_bytes": 291672064 + }, + { + "query": "process exit code is 4294967295 instead of -1 on Windows", + "ranked": [ + "windows-exit-codes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFHF017QB143HM13PMSC", + "id": "01M1Y0GGG1W63ZPTQ3KB4RNKXH", + "kind": "memory", + "score": 0.9966622591018676, + "summary": "project:fact - [tags: windows exit-codes rust process child] On Windows, process exit codes are 32-bit unsigned integers (DWORD). Rust's `ExitStatus::code()` returns `Option` — it's `None` if the process was killed by a signal (which Windows doesn't use; instead, TerminateProcess with a code). Conventional codes: 0=success, 1=generic error, 0xC0000005=access violation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1020.1899999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 753, + "mcp_result_bytes": 834, + "wire_bytes": 871, + "reported_used_tokens": 834, + "working_set_bytes": 290742272, + "peak_working_set_bytes": 291672064 + }, + { + "query": "tokenizer.json must match the ONNX model — what breaks if it doesn't?", + "ranked": [ + "onnx-tokenizer-mismatch" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFKYR3WDJRKWM69A4J4V", + "id": "01M1Y0GHF210YETVDR62V34R8Q", + "kind": "memory", + "score": 0.9991299510002136, + "summary": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly — specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings — cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1052.4279000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 959, + "mcp_result_bytes": 1040, + "wire_bytes": 1077, + "reported_used_tokens": 1040, + "working_set_bytes": 290742272, + "peak_working_set_bytes": 291672064 + }, + { + "query": "embedding quality degraded after I swapped in the INT8 quantized model", + "ranked": [ + "onnx-quantization-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFN2M30C7MN0GWMT2VSV", + "id": "01M1Y0GJFXZ2Z1PR1S5N3ZZWD6", + "kind": "memory", + "score": 0.997980535030365, + "summary": "project:fact - [2026-09-07] [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals — cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1006.1175, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 990, + "mcp_result_bytes": 1071, + "wire_bytes": 1108, + "reported_used_tokens": 1071, + "working_set_bytes": 290742272, + "peak_working_set_bytes": 291672064 + }, + { + "query": "missing attention mask causes low-norm embeddings in batch inference", + "ranked": [ + "onnx-batch-padding" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFP13FB0MRZX0RQTZRT0", + "id": "01M1Y0GKFABH4YNRKCB2B32WDE", + "kind": "memory", + "score": 0.9998397827148438, + "summary": "project:fact - [tags: onnx batch padding attention-mask embeddings] When running batch inference with an ONNX model, all inputs in the batch must be padded to the same sequence length. The `attention_mask` tensor marks which tokens are real (1) and which are padding (0). Failing to pass `attention_mask` causes the model to average-pool over padding tokens, producing systematically lower-norm embeddings." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1009.9062999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 781, + "mcp_result_bytes": 862, + "wire_bytes": 899, + "reported_used_tokens": 862, + "working_set_bytes": 290742272, + "peak_working_set_bytes": 291672064 + }, + { + "query": "ONNX model download fails in a Docker container with no home directory", + "ranked": [ + "onnx-model-cache-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFQ64J0V9K34YSY0Q2RM", + "id": "01M1Y0GMEZZ92HYXMTQEREMAMQ", + "kind": "memory", + "score": 0.9887272119522096, + "summary": "project:fact - [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1076.8203, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 755, + "mcp_result_bytes": 838, + "wire_bytes": 875, + "reported_used_tokens": 838, + "working_set_bytes": 290713600, + "peak_working_set_bytes": 291672064 + }, + { + "query": "fastembed cache path environment variable for CI", + "ranked": [ + "onnx-model-cache-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFQ64J0V9K34YSY0Q2RM", + "id": "01M1Y0GNGTHSN53HCVFKRAFDA4", + "kind": "memory", + "score": 0.9995118379592896, + "summary": "project:fact - [tags: onnx fastembed model-cache path windows] fastembed caches downloaded ONNX models under `~/.cache/fastembed` on Unix or `%LOCALAPPDATA%\\fastembed` on Windows. In a containerized or CI environment with no home directory, set the `FASTEMBED_CACHE_PATH` environment variable to a writable path. Without it, fastembed panics with a permissions error on first use." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1007.6210000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 756, + "mcp_result_bytes": 839, + "wire_bytes": 876, + "reported_used_tokens": 839, + "working_set_bytes": 290713600, + "peak_working_set_bytes": 291672064 + }, + { + "query": "cosine similarity vs dot product for L2-normalized embedding vectors", + "ranked": [ + "onnx-cosine-vs-dot", + "onnx-tokenizer-mismatch", + "onnx-quantization-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFR66B0CWZXF5MVKF7P0", + "id": "01M1Y0GPG368HJ05N67A1F5K9W", + "kind": "memory", + "score": 0.9999407529830932, + "summary": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing — double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + }, + { + "expansion_handle": "memory:01M1Y0CFKYR3WDJRKWM69A4J4V", + "id": "01M1Y0GPG3W1BGS53DXS7THAEG", + "kind": "memory", + "score": 0.9514977931976318, + "summary": "project:fact - [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly — specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings — cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo." + }, + { + "expansion_handle": "memory:01M1Y0CFN2M30C7MN0GWMT2VSV", + "id": "01M1Y0GPG3BKE3AM0TCVZ572DH", + "kind": "memory", + "score": 0.941756010055542, + "summary": "project:fact - [tags: onnx quantization int8 fp16 embeddings accuracy] INT8-quantized ONNX models can produce meaningfully different embeddings from their FP32 originals — cosine similarity between the two variants' outputs for the same input can be as low as 0.92 for some sentence-transformer models. Before swapping a model for its quantized variant, run a small benchmark comparing retrieval quality (MRR) on your dataset. The quantized model is 4x smaller and 3-5x faster on CPU, but if MRR drops more than 2 percentage points it's usually not worth the trade for a knowledge-retrieval use case." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 971.3107, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2245, + "mcp_result_bytes": 2362, + "wire_bytes": 2399, + "reported_used_tokens": 2362, + "working_set_bytes": 290713600, + "peak_working_set_bytes": 291672064 + }, + { + "query": "stored vectors have wrong dimension after switching embedding models", + "ranked": [ + "onnx-dim-mismatch", + "onnx-cosine-vs-dot", + "onnx-tokenizer-mismatch" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFS76DA1V14BVQR0NX3T", + "id": "01M1Y0GQEPN68GQXR6T7BXQFA4", + "kind": "memory", + "score": 0.9997621178627014, + "summary": "project:fact - [2026-09-07] [tags: onnx embedding dim mismatch schema migration] If you switch embedding models (e.g. bge-small-en-v1.5 -> bge-large-en-v1.5), the stored vector dimension changes (384 -> 1024). Trying to load a 1024-dim model and query against 384-dim stored vectors produces silently wrong results — the ANN index shape mismatch isn't always caught at runtime." + }, + { + "expansion_handle": "memory:01M1Y0CFR66B0CWZXF5MVKF7P0", + "id": "01M1Y0GQEPW5HJMG5YN53HAFY7", + "kind": "memory", + "score": 0.997715711593628, + "summary": "project:fact - [2026-09-07] [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing — double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + }, + { + "expansion_handle": "memory:01M1Y0CFKYR3WDJRKWM69A4J4V", + "id": "01M1Y0GQEPGEM215KG2Y1PZQYY", + "kind": "memory", + "score": 0.9388805031776428, + "summary": "project:fact - [2026-09-07] [tags: onnx tokenizer embeddings fastembed mismatch] When loading an ONNX embedding model manually (not through fastembed's model catalog), the tokenizer JSON must match the model exactly — specifically: `add_special_tokens`, `max_length`, and the special token IDs (`[CLS]`=101, `[SEP]`=102 for BERT-family). A mismatched tokenizer silently produces wrong embeddings — cosine similarity will be lower than expected, and the model may produce near-zero vectors for some inputs. Always use the tokenizer.json shipped alongside the model in the same HuggingFace repo." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 830.1889, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2049, + "mcp_result_bytes": 2166, + "wire_bytes": 2203, + "reported_used_tokens": 2166, + "working_set_bytes": 290721792, + "peak_working_set_bytes": 291672064 + }, + { + "query": "E5 and Instructor models need a query prefix — what happens without it?", + "ranked": [ + "onnx-prefix-instructions", + "onnx-cosine-vs-dot" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFWAMZG7RES52QMQ6ZZ9", + "id": "01M1Y0GR8K28PPWFXEDYFPN6RZ", + "kind": "memory", + "score": 0.996955633163452, + "summary": "project:fact - [tags: onnx embeddings prefix instruction e5 query passage] E5 and Instructor family models require a text prefix on BOTH query and passage sides to produce meaningful similarities: query prefix `\"query: \"`, passage prefix `\"passage: \"`. Omitting the prefix can drop MRR by 10-15 percentage points on out-of-domain datasets. Check the model's README for the exact prefix string — it varies by model family." + }, + { + "expansion_handle": "memory:01M1Y0CFR66B0CWZXF5MVKF7P0", + "id": "01M1Y0GR8KRENJYF6MGZYD2QKH", + "kind": "memory", + "score": 0.9543967247009276, + "summary": "project:fact - [tags: onnx embeddings cosine dot-product similarity] Sentence-transformer models trained with cosine similarity loss produce L2-normalized vectors where cosine similarity == dot product. Do NOT normalize again before storing — double normalization is a no-op, but storing unnormalized vectors and querying with dot product gives wrong rankings. For asymmetric models (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 999.0515, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1339, + "mcp_result_bytes": 1446, + "wire_bytes": 1483, + "reported_used_tokens": 1446, + "working_set_bytes": 290721792, + "peak_working_set_bytes": 291672064 + }, + { + "query": "ORT thread pool contention when running multiple bench processes in parallel", + "ranked": [ + "onnx-ort-threading" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFXBEVH5NSD9GP4MD291", + "id": "01M1Y0GS8BRYMS6JXWW3NP6QJE", + "kind": "memory", + "score": 0.9998078942298888, + "summary": "project:fact - [2026-09-07] [tags: onnx ort thread-pool parallelism cpu] ORT (ONNX Runtime) creates its own inter-op and intra-op thread pools. In a multi-process bench setup, each child inherits these pools and they compete for CPU cores. Set `SessionOptionsBuilder::with_intra_threads(1).with_inter_threads(1)` if you're running many parallel bench processes — this sacrifices per-inference throughput for lower contention." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1023.4483999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 802, + "mcp_result_bytes": 883, + "wire_bytes": 920, + "reported_used_tokens": 883, + "working_set_bytes": 290721792, + "peak_working_set_bytes": 291672064 + }, + { + "query": "git worktrees share the .kimetsu brain — how do I isolate test runs?", + "ranked": [ + "git-worktree-brain-isolation", + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFYDVF1XT2NJ4R1CE9KK", + "id": "01M1Y0GT7PJW89EK1FSG47MKPM", + "kind": "memory", + "score": 0.9996256828308104, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root — if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + }, + { + "expansion_handle": "memory:01M1Y0CDW1WMXYA9MGV0TK68J6", + "id": "01M1Y0GT7P4D8XKC9QP4PRAJT0", + "kind": "memory", + "score": 0.9904396533966064, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 931.3589, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1435, + "mcp_result_bytes": 1534, + "wire_bytes": 1571, + "reported_used_tokens": 1534, + "working_set_bytes": 290725888, + "peak_working_set_bytes": 291672064 + }, + { + "query": "when is it safe to use --no-verify on git commit?", + "ranked": [ + "git-hooks-bypass", + "git-reflog-rescue" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFZD1W15NCZZWH5WJC0K", + "id": "01M1Y0GV50ZB6JXX75YZ92Y8VX", + "kind": "memory", + "score": 0.9956986904144288, + "summary": "project:fact - [2026-09-07] [tags: git hooks bypass pre-commit skip] `git commit --no-verify` skips ALL hooks (pre-commit and commit-msg). Never use this in shared team repos where hooks enforce quality gates (lint, tests, memory harvest). Instead, fix the failing hook." + }, + { + "expansion_handle": "memory:01M1Y0CG3J6F6PE488KD89FHD4", + "id": "01M1Y0GV50489PF5K51JZC79PB", + "kind": "memory", + "score": 0.5084817409515381, + "summary": "project:fact - [2026-09-07] [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone — they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only — remote reflog is not accessible via normal git commands." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1046.4164, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1193, + "mcp_result_bytes": 1292, + "wire_bytes": 1329, + "reported_used_tokens": 1292, + "working_set_bytes": 290725888, + "peak_working_set_bytes": 291672064 + }, + { + "query": "reduce clone size and bandwidth for server-side repo ingest", + "ranked": [ + "git-sparse-checkout", + "remote-ingest-split-roots" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CG0D8V7JRBSXCYXBRNG3", + "id": "01M1Y0GW5KNMSDZQD7R76J1CW6", + "kind": "memory", + "score": 0.9969936609268188, + "summary": "project:fact - [tags: git sparse-checkout partial-clone bandwidth] `git sparse-checkout init --cone` combined with `git clone --filter=blob:none` (partial clone) fetches only the commit graph and tree objects, not blobs. Individual blobs are fetched on demand when accessed. This cuts clone time for large repos from minutes to seconds." + }, + { + "expansion_handle": "memory:01M1Y0CDERH0MV40CSBCQDEQYA", + "id": "01M1Y0GW5K3SVSK4613C98Y9VF", + "kind": "memory", + "score": 0.8199672698974609, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1052.6349, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1744, + "mcp_result_bytes": 1843, + "wire_bytes": 1880, + "reported_used_tokens": 1843, + "working_set_bytes": 290725888, + "peak_working_set_bytes": 291672064 + }, + { + "query": "spurious diffs from Windows CRLF line ending conversion in git", + "ranked": [ + "git-line-endings-windows" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CG1G409C78CR8V9VBK7M", + "id": "01M1Y0GX6K80R49Q0JRGVD0SRB", + "kind": "memory", + "score": 0.9993343949317932, + "summary": "project:fact - [tags: git line-endings windows crlf autocrlf] On Windows, `core.autocrlf=true` (git's default for Windows installs) converts LF to CRLF on checkout and CRLF to LF on commit. This causes spurious diffs when files are edited on Windows then committed — the content is identical but the line endings differ in the index vs the working tree. Fix: set `core.autocrlf=false` and `.gitattributes` with `* text=auto eol=lf` for the repo." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1030.3709000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 822, + "mcp_result_bytes": 903, + "wire_bytes": 940, + "reported_used_tokens": 903, + "working_set_bytes": 290725888, + "peak_working_set_bytes": 291672064 + }, + { + "query": "git submodule always gets the wrong commit in CI", + "ranked": [ + "git-submodule-pinning", + "git-hooks-bypass" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CG2J6XC8ARPF2CP7BP61", + "id": "01M1Y0GY6ZCQ52NRG35FJWJDTM", + "kind": "memory", + "score": 0.9992856383323668, + "summary": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip — this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version." + }, + { + "expansion_handle": "memory:01M1Y0CFZD1W15NCZZWH5WJC0K", + "id": "01M1Y0GY6ZZADQ98YNAMBDGR12", + "kind": "memory", + "score": 0.6295387744903564, + "summary": "project:fact - [tags: git hooks bypass pre-commit skip] `git commit --no-verify` skips ALL hooks (pre-commit and commit-msg). Never use this in shared team repos where hooks enforce quality gates (lint, tests, memory harvest). Instead, fix the failing hook." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1046.2918000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1157, + "mcp_result_bytes": 1256, + "wire_bytes": 1293, + "reported_used_tokens": 1256, + "working_set_bytes": 290865152, + "peak_working_set_bytes": 291782656 + }, + { + "query": "accidentally ran git reset --hard and lost commits — can I recover?", + "ranked": [ + "git-reflog-rescue" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CG3J6F6PE488KD89FHD4", + "id": "01M1Y0GZ7DC3NV90857AJQCSJW", + "kind": "memory", + "score": 0.9995450377464294, + "summary": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone — they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only — remote reflog is not accessible via normal git commands." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1074.79, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 762, + "mcp_result_bytes": 843, + "wire_bytes": 880, + "reported_used_tokens": 843, + "working_set_bytes": 290865152, + "peak_working_set_bytes": 291782656 + }, + { + "query": "blocking SQLite call from an async tokio handler causes latency spikes", + "ranked": [ + "tokio-blocking-in-async" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CG4GXEDWCNR553B345ZY", + "id": "01M1Y0H093GHNQ67XAYDES4SM2", + "kind": "memory", + "score": 0.9996535778045654, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking — never call rusqlite directly from an async fn without spawn_blocking." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 981.7245999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 766, + "mcp_result_bytes": 847, + "wire_bytes": 884, + "reported_used_tokens": 847, + "working_set_bytes": 290865152, + "peak_working_set_bytes": 291782656 + }, + { + "query": "Cannot start a runtime from within a runtime in a tokio test", + "ranked": [ + "tokio-runtime-in-tests", + "tokio-blocking-in-async" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CG5NP8CQF81Q3J12R0GS", + "id": "01M1Y0H18RHYYNYBAYWH228MFC", + "kind": "memory", + "score": 0.9997126460075378, + "summary": "project:fact - [tags: tokio testing runtime test async rust] `#[tokio::test]` creates a new runtime per test. If your code under test calls `tokio::runtime::Handle::current()` or assumes a runtime is active, this works. But if you create a `Runtime` manually inside a `#[tokio::test]`, you get a nested runtime panic: \"Cannot start a runtime from within a runtime.\" Use `#[tokio::test(flavor = \"multi_thread\")]` when you need multiple threads in tests." + }, + { + "expansion_handle": "memory:01M1Y0CG4GXEDWCNR553B345ZY", + "id": "01M1Y0H18RZ13Z1S6C5EGKSC5Q", + "kind": "memory", + "score": 0.5779464840888977, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking — never call rusqlite directly from an async fn without spawn_blocking." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1025.6133, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1370, + "mcp_result_bytes": 1477, + "wire_bytes": 1514, + "reported_used_tokens": 1477, + "working_set_bytes": 290865152, + "peak_working_set_bytes": 291782656 + }, + { + "query": "tokio select cancels the other branch and loses the value in the channel", + "ranked": [ + "tokio-select-cancellation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CG6WN1WWXEWWMMGQZ45N", + "id": "01M1Y0H28R59NDC67STRXS6CJ3", + "kind": "memory", + "score": 0.9981033802032472, + "summary": "project:fact - [tags: tokio select cancellation futures drop rust] `tokio::select!` cancels all other branches when one branch completes. The cancelled futures are dropped immediately, which means any state held by a cancelled future is dropped too. If a branch was in the middle of writing to a channel or acquiring a mutex, the half-completed operation is silently discarded." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1049.9551000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 751, + "mcp_result_bytes": 832, + "wire_bytes": 869, + "reported_used_tokens": 832, + "working_set_bytes": 291049472, + "peak_working_set_bytes": 291971072 + }, + { + "query": "mpsc channel backpressure causing senders to stall", + "ranked": [ + "tokio-channel-backpressure" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CG8017ZQQBB08PZX3NPY", + "id": "01M1Y0H38P029YWM3EB8700Z9K", + "kind": "memory", + "score": 0.9999104738235474, + "summary": "project:fact - [tags: tokio mpsc channel backpressure async rust] `tokio::sync::mpsc::channel(N)` with a bounded buffer provides backpressure: senders block when the buffer is full. This prevents unbounded memory growth but can cause sender tasks to stall. Choosing N: too small causes frequent backpressure (throughput drops); too large defeats the purpose." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1090.0527, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 733, + "mcp_result_bytes": 814, + "wire_bytes": 851, + "reported_used_tokens": 814, + "working_set_bytes": 291131392, + "peak_working_set_bytes": 292040704 + }, + { + "query": "overhead from calling spawn_blocking on every single query request", + "ranked": [ + "tokio-spawn-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CG92HHK7CQ8GSK4Z8RT3", + "id": "01M1Y0H4ARH7YXHDJ0SBNY21TE", + "kind": "memory", + "score": 0.9961729645729064, + "summary": "project:fact - [tags: tokio spawn_blocking thread-pool rust blocking] `tokio::task::spawn_blocking` places work on a dedicated blocking thread pool (default up to 512 threads, configurable via `Builder::max_blocking_threads`). Each call creates or reuses a thread — there's no true pooling, threads may be created on demand. For many short-duration blocking calls (e.g." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1009.5518000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 746, + "mcp_result_bytes": 827, + "wire_bytes": 864, + "reported_used_tokens": 827, + "working_set_bytes": 291266560, + "peak_working_set_bytes": 292179968 + }, + { + "query": "axum server panics during shutdown because the DB pool is already closed", + "ranked": [ + "tokio-shutdown-ordering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGCDX7QHSQ0KP7DJW3ES", + "id": "01M1Y0H5AQZ4MXKJBHFZWWJH80", + "kind": "memory", + "score": 0.98052579164505, + "summary": "project:fact - [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries — the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1048.2772, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 931, + "mcp_result_bytes": 1012, + "wire_bytes": 1049, + "reported_used_tokens": 1012, + "working_set_bytes": 291266560, + "peak_working_set_bytes": 292188160 + }, + { + "query": "reqwest Client created per-request defeats connection pooling", + "ranked": [ + "http-connection-pooling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGDFD07QWYC4G8JDJGYZ", + "id": "01M1Y0H6BBATNTWBZYM00KPQ6N", + "kind": "memory", + "score": 0.9998082518577576, + "summary": "project:fact - [tags: http reqwest connection-pool keep-alive rust] reqwest's `Client` holds a connection pool; always create ONE `Client` instance and clone it for each handler — cloning is cheap (Arc under the hood). Creating a `Client::new()` per request defeats connection pooling and causes TCP connection exhaustion under load. The default pool settings: max_idle_per_host=usize::MAX (unbounded), idle_timeout=90s." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 970.3035, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 797, + "mcp_result_bytes": 878, + "wire_bytes": 915, + "reported_used_tokens": 878, + "working_set_bytes": 291274752, + "peak_working_set_bytes": 292188160 + }, + { + "query": "LLM request times out during streaming — which timeout setting applies?", + "ranked": [ + "http-timeout-layering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGEM6S4YH3PHRN8PN7TH", + "id": "01M1Y0H79QHSB9HHFG8WK2XR0Q", + "kind": "memory", + "score": 0.9987107515335084, + "summary": "project:fact - [tags: http reqwest timeout connect read total rust] reqwest has three distinct timeout knobs: `connect_timeout`, `read_timeout`, and `timeout` (total). They compose: if all three are set, the request fails at whichever fires first. For LLM API calls with streaming responses, `read_timeout` must be larger than the slowest expected token (often 30-60s) while `connect_timeout` can be tight (3-5s)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 851.7888, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 788, + "mcp_result_bytes": 869, + "wire_bytes": 906, + "reported_used_tokens": 869, + "working_set_bytes": 291278848, + "peak_working_set_bytes": 292196352 + }, + { + "query": "how do I safely retry a POST to the LLM API without creating duplicates?", + "ranked": [ + "http-retry-idempotency" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGFQX2AFH3HY29EDD86W", + "id": "01M1Y0H8462YGZXYB0F645CY9S", + "kind": "memory", + "score": 0.9995805621147156, + "summary": "project:fact - [tags: http retry idempotency post put reqwest] Only retry idempotent requests automatically. GET, HEAD, PUT, DELETE are idempotent. POST is NOT — retrying a POST may create duplicate resources." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1083.9113, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 585, + "mcp_result_bytes": 666, + "wire_bytes": 703, + "reported_used_tokens": 666, + "working_set_bytes": 291278848, + "peak_working_set_bytes": 292200448 + }, + { + "query": "custom enterprise root CA not trusted by rustls on Windows", + "ranked": [ + "http-tls-roots", + "http-proxy-env" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGGWEEXGNT0936BMB92X", + "id": "01M1Y0H968Y7W7F5BVNZP14KG9", + "kind": "memory", + "score": 0.9998220801353456, + "summary": "project:fact - [tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle — the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle." + }, + { + "expansion_handle": "memory:01M1Y0CGK17KMR7W6MAVHR6YND", + "id": "01M1Y0H9682ZH5ZVS617KPZABZ", + "kind": "memory", + "score": 0.38715291023254395, + "summary": "project:fact - [tags: http proxy environment reqwest rust corporate] reqwest respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` environment variables by default (with `default-tls` or `rustls-tls`). In a corporate network, these may redirect traffic through an intercepting proxy that breaks mTLS or adds latency. To disable proxy usage entirely: `reqwest::ClientBuilder::no_proxy()`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1054.9298000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1311, + "mcp_result_bytes": 1410, + "wire_bytes": 1447, + "reported_used_tokens": 1410, + "working_set_bytes": 291278848, + "peak_working_set_bytes": 292200448 + }, + { + "query": "parsing server-sent events when a single TCP chunk contains a partial SSE frame", + "ranked": [ + "http-streaming-bodies" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGHXSFNGV11ZX9ABYM9G", + "id": "01M1Y0HA70PV7RKHKN3C9MS82D", + "kind": "memory", + "score": 0.9667426943778992, + "summary": "project:fact - [2026-09-07] [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding — a chunk may split across frame boundaries." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 926.7712, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 859, + "mcp_result_bytes": 940, + "wire_bytes": 977, + "reported_used_tokens": 940, + "working_set_bytes": 291278848, + "peak_working_set_bytes": 292200448 + }, + { + "query": "reqwest does not use the system proxy settings on Windows", + "ranked": [ + "http-proxy-env", + "http-tls-roots", + "http-connection-pooling", + "http-streaming-bodies" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGK17KMR7W6MAVHR6YND", + "id": "01M1Y0HB4GBXVB1AB6A3ZNANS5", + "kind": "memory", + "score": 0.9997830986976624, + "summary": "project:fact - [tags: http proxy environment reqwest rust corporate] reqwest respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` environment variables by default (with `default-tls` or `rustls-tls`). In a corporate network, these may redirect traffic through an intercepting proxy that breaks mTLS or adds latency. To disable proxy usage entirely: `reqwest::ClientBuilder::no_proxy()`." + }, + { + "expansion_handle": "memory:01M1Y0CGGWEEXGNT0936BMB92X", + "id": "01M1Y0HB4GB3TNGHEQ42CKN4PN", + "kind": "memory", + "score": 0.9808586239814758, + "summary": "project:fact - [tags: http tls rustls certificates trust-store rust] reqwest with `rustls-tls` bundles Mozilla's CA certificate bundle. On Windows, some enterprise proxies use a custom root CA not in Mozilla's bundle — the request fails with `error: invalid certificate`. Fix: use `reqwest::ClientBuilder::tls_built_in_native_certs(true)` to include the OS trust store in addition to the Mozilla bundle." + }, + { + "expansion_handle": "memory:01M1Y0CGDFD07QWYC4G8JDJGYZ", + "id": "01M1Y0HB4G2NTH3JAB1TDZXF39", + "kind": "memory", + "score": 0.719273030757904, + "summary": "project:fact - [tags: http reqwest connection-pool keep-alive rust] reqwest's `Client` holds a connection pool; always create ONE `Client` instance and clone it for each handler — cloning is cheap (Arc under the hood). Creating a `Client::new()` per request defeats connection pooling and causes TCP connection exhaustion under load. The default pool settings: max_idle_per_host=usize::MAX (unbounded), idle_timeout=90s." + }, + { + "expansion_handle": "memory:01M1Y0CGHXSFNGV11ZX9ABYM9G", + "id": "01M1Y0HB4G5JPQJK5SN4T9EG6V", + "kind": "memory", + "score": 0.7009692192077637, + "summary": "project:fact - [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding — a chunk may split across frame boundaries." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1058.9818, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2497, + "mcp_result_bytes": 2632, + "wire_bytes": 2669, + "reported_used_tokens": 2632, + "working_set_bytes": 291332096, + "peak_working_set_bytes": 292245504 + }, + { + "query": "insta snapshot tests fail in CI because output includes a timestamp", + "ranked": [ + "testing-snapshot-churn", + "ci-flaky-quarantine" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGM2F54Y82Z0FVGTW56Q", + "id": "01M1Y0HC5365DC0N73T8Z16Y2R", + "kind": "memory", + "score": 0.999855637550354, + "summary": "project:fact - [tags: testing snapshot insta assert churn rust] Snapshot tests (e.g. with the `insta` crate) fail whenever the output changes, even for intended changes. In CI, they fail loudly; locally, `cargo insta review` walks you through accepting or rejecting changes." + }, + { + "expansion_handle": "memory:01M1Y0CHEJR8TEQHZ0MBKNP52Z", + "id": "01M1Y0HC53E242SEPH98FDCQZ6", + "kind": "memory", + "score": 0.5997360348701477, + "summary": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal — a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 932.3747, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1196, + "mcp_result_bytes": 1295, + "wire_bytes": 1332, + "reported_used_tokens": 1295, + "working_set_bytes": 291606528, + "peak_working_set_bytes": 292524032 + }, + { + "query": "two test workers writing to the same temp directory path race each other", + "ranked": [ + "testing-temp-dirs-ci" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGN6SREKHFEAXY7YMS2G", + "id": "01M1Y0HD27CYGEY2V12PE4R92M", + "kind": "memory", + "score": 0.9889234900474548, + "summary": "project:fact - [tags: testing temp-dirs ci isolation rust tempfile] Always use `tempfile::tempdir()` (or `tempfile::Builder::new().tempdir_in(std::env::temp_dir())`) for test temporary directories rather than a hardcoded path. On CI, multiple test workers may run in parallel and a shared path causes races. The `TempDir` guard deletes the directory on drop, even on test failure." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 947.5802, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 755, + "mcp_result_bytes": 836, + "wire_bytes": 873, + "reported_used_tokens": 836, + "working_set_bytes": 291606528, + "peak_working_set_bytes": 292524032 + }, + { + "query": "test passes locally but fails on a slow CI runner due to a 100ms sleep", + "ranked": [ + "testing-time-dependent-flakes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGPCZQS2GMYCWTQKJMA0", + "id": "01M1Y0HDZW394K1X8EYZW0R700", + "kind": "memory", + "score": 0.808289110660553, + "summary": "project:fact - [tags: testing time flaky clock mock rust] Tests that depend on wall-clock time are inherently flaky under load (slow CI runners, GC pauses). Abstract time behind a trait (`Clock: Fn() -> SystemTime`) injected at construction, and supply a fake in tests. For tests checking that something happened \"within N seconds\", use a generous multiple of the expected duration (10x is not unreasonable for CI)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1065.7621, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 790, + "mcp_result_bytes": 875, + "wire_bytes": 912, + "reported_used_tokens": 875, + "working_set_bytes": 291622912, + "peak_working_set_bytes": 292548608 + }, + { + "query": "proptest found a hash collision in text normalization that example tests missed", + "ranked": [ + "testing-property-tests" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGQD6AS5EN1NBKC3MEW7", + "id": "01M1Y0HF15YXKTWV2S8A77JQS0", + "kind": "memory", + "score": 0.9994783997535706, + "summary": "project:fact - [tags: testing property-based proptest quickcheck rust] Property-based tests (proptest, quickcheck) find edge cases that example-based tests miss. For kimetsu's memory text normalization, proptest found that zero-width joiner characters and right-to-left marks caused hash collisions. Run proptest with `PROPTEST_CASES=10000` in CI for thorough coverage." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1025.3103999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 744, + "mcp_result_bytes": 825, + "wire_bytes": 862, + "reported_used_tokens": 825, + "working_set_bytes": 291622912, + "peak_working_set_bytes": 292548608 + }, + { + "query": "set_var in tests races when cargo test runs them in parallel", + "ranked": [ + "testing-serial-vs-parallel" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGRCR5ZFR10QAFCTQWHR", + "id": "01M1Y0HG1KRPM4XC3PZ6Y103SX", + "kind": "memory", + "score": 0.9997344613075256, + "summary": "project:fact - [2026-09-07] [tags: testing serial parallel rust nextest shared-state] Rust's default test runner runs tests in the same binary in parallel (per-binary parallelism, not cross-binary). Tests that mutate shared process-global state (env vars, statics, `std::env::set_var`) race against each other. Two patterns: (1) `serial_test` crate with `#[serial]` attribute serializes named tests; (2) a custom `Mutex` guard (kimetsu's `test_env_lock()`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1033.924, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 832, + "mcp_result_bytes": 913, + "wire_bytes": 950, + "reported_used_tokens": 913, + "working_set_bytes": 291639296, + "peak_working_set_bytes": 292564992 + }, + { + "query": "hardcoded JSON fixtures broke after a schema migration", + "ranked": [ + "testing-fixture-drift" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGTZS0E9FZN5W5KHQQXA", + "id": "01M1Y0HH1MHSPDK99AYHGB910X", + "kind": "memory", + "score": 0.9998371601104736, + "summary": "project:fact - [2026-09-07] [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 834.7506, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 783, + "mcp_result_bytes": 864, + "wire_bytes": 901, + "reported_used_tokens": 864, + "working_set_bytes": 291655680, + "peak_working_set_bytes": 292569088 + }, + { + "query": "debug print in the MCP handler corrupts the JSON-Lines protocol stream", + "ranked": [ + "mcp-stdout-protocol" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGVVT9QFVZB1R92FVJYX", + "id": "01M1Y0HHVYHPWDC4QK31ZCWWP9", + "kind": "memory", + "score": 0.9997472167015076, + "summary": "project:fact - [tags: mcp stdio protocol stdout rust] The MCP stdio transport uses newline-delimited JSON on stdout. ANY non-JSON bytes on stdout (debug prints, progress bars, log lines) corrupt the protocol stream and cause the host to close the connection with a parse error. In kimetsu's MCP server, ALL logging goes to stderr." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1020.4103, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 705, + "mcp_result_bytes": 786, + "wire_bytes": 823, + "reported_used_tokens": 786, + "working_set_bytes": 291790848, + "peak_working_set_bytes": 292708352 + }, + { + "query": "kimetsu MCP tool call times out because embedding model is re-initialized every call", + "ranked": [ + "mcp-tool-timeouts", + "mcp-schema-validation", + "kimetsu-bench-remote-embedder-singleton" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGWYZ09QBQFPX4FEVE70", + "id": "01M1Y0HJVMK2P5VB95RV2JFNRB", + "kind": "memory", + "score": 0.9995898604393004, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking — in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize — keep it in a process-global `OnceLock`)." + }, + { + "expansion_handle": "memory:01M1Y0CGZ4ETMHT4DV0PTAD4FW", + "id": "01M1Y0HJVN8AWP004G1J238T26", + "kind": "memory", + "score": 0.6027993559837341, + "summary": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array — omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error." + }, + { + "expansion_handle": "memory:01M1Y0CHTA3AQK16G6EE1QCJKZ", + "id": "01M1Y0HJVN9ZC52Y5F1PKX55BP", + "kind": "memory", + "score": 0.5117799639701843, + "summary": "project:fact - [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 917.2564, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2085, + "mcp_result_bytes": 2202, + "wire_bytes": 2239, + "reported_used_tokens": 2202, + "working_set_bytes": 292052992, + "peak_working_set_bytes": 292970496 + }, + { + "query": "env var set after host launch is not visible to the MCP server process", + "ranked": [ + "mcp-env-propagation", + "kimetsu-daemon-lifecycle" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGY1YEEKFJHX4JCZYHY6", + "id": "01M1Y0HKRVC9MN1NMSBYF4EXZD", + "kind": "memory", + "score": 0.9984827637672424, + "summary": "project:fact - [2026-09-07] [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment — changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate." + }, + { + "expansion_handle": "memory:01M1Y0CHFNNPJ5W1HT08QTSPX7", + "id": "01M1Y0HKRVR71DCK0DABGZ4HRT", + "kind": "memory", + "score": 0.9977922439575196, + "summary": "project:fact - [2026-09-07] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1096.9579, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1267, + "mcp_result_bytes": 1366, + "wire_bytes": 1403, + "reported_used_tokens": 1366, + "working_set_bytes": 292093952, + "peak_working_set_bytes": 293019648 + }, + { + "query": "MCP tool call fails because a required field is missing from the JSON input", + "ranked": [ + "mcp-schema-validation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGZ4ETMHT4DV0PTAD4FW", + "id": "01M1Y0HMTMM69S0EZHKPWDS0V7", + "kind": "memory", + "score": 0.998538613319397, + "summary": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array — omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 985.3119, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 798, + "mcp_result_bytes": 879, + "wire_bytes": 916, + "reported_used_tokens": 879, + "working_set_bytes": 292315136, + "peak_working_set_bytes": 293232640 + }, + { + "query": "Claude Code rejects the tool name with a hyphen in it", + "ranked": [ + "mcp-tool-naming" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CH08W8KRWZHZ6QYESXCW", + "id": "01M1Y0HNSC2ZZVFFTPVYVVQ576", + "kind": "memory", + "score": 0.9982439279556274, + "summary": "project:fact - [tags: mcp tool naming convention kimetsu] MCP tool names must be valid identifiers for all host agents. Claude Code restricts tool names to `[a-zA-Z0-9_-]` and max 64 chars. Use `snake_case` (kimetsu_brain_context, kimetsu_brain_record) — hyphen is technically allowed but some hosts reject it." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 969.4044, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 687, + "mcp_result_bytes": 768, + "wire_bytes": 805, + "reported_used_tokens": 768, + "working_set_bytes": 292732928, + "peak_working_set_bytes": 293650432 + }, + { + "query": "MCP response path uses backslashes and the host rejects it", + "ranked": [ + "mcp-transcript-paths" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CH1D4KC6M56416X92HX3", + "id": "01M1Y0HPQRV95FWGKF1JRE5SJV", + "kind": "memory", + "score": 0.9984637498855592, + "summary": "project:fact - [tags: mcp transcript paths kimetsu hooks runs] kimetsu writes run transcripts to `/.kimetsu/runs//`. The post-session hook reads the latest run's transcript to trigger memory harvest. On Windows, the path uses backslashes internally but the MCP JSON must use forward slashes or the host may reject path-type arguments." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 999.9429, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 724, + "mcp_result_bytes": 805, + "wire_bytes": 842, + "reported_used_tokens": 805, + "working_set_bytes": 292777984, + "peak_working_set_bytes": 293691392 + }, + { + "query": "AWS credentials not found — which env var does kimetsu read for Bedrock?", + "ranked": [ + "aws-credentials-chain", + "aws-region-resolution", + "bedrock-kimetsu-provider", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CH2MJMDSTCRQ0SR4KPC5", + "id": "01M1Y0HQQ398V77CSDZW4EZD8P", + "kind": "memory", + "score": 0.9990235567092896, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + }, + { + "expansion_handle": "memory:01M1Y0CH4021WNS0JP2HVKJDXQ", + "id": "01M1Y0HQQ37Y71X2M3JX910V15", + "kind": "memory", + "score": 0.9968422651290894, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1Y0CDKZJ260T9MFP5RRHHQY", + "id": "01M1Y0HQQ368KCNEAFQTHC0KH4", + "kind": "memory", + "score": 0.9849756360054016, + "summary": "project:fact - [tags: kimetsu bedrock aws-sigv4 model-provider rust] To add an AWS Bedrock model provider to Kimetsu without pulling in aws-sdk/tokio (the pipeline is blocking reqwest): reuse the Anthropic wire format (Anthropic-models-on-Bedrock). Factor anthropic.rs's body-builder + response parser to pub(crate), parameterize the builder as build_anthropic_body(model: Option, anthropic_version: Option) — for Bedrock pass model=None (model id goes in the URL path) and anthropic_version=Some(\"bedrock-2023-05-31\") in the body. POST to https://bedrock-runtime.{region}.amazonaws.com/model/{model_id}/invoke (percent-encode ':' in the model id), signed with the aws-sigv4 crate (service \"bedrock\") + aws-credential-types + aws-smithy-runtime-api + http, reading AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN + region from env." + }, + { + "expansion_handle": "memory:01M1Y0CDSEBRPP3R78RVYDDGZA", + "id": "01M1Y0HQQ3NKKRBFHX5B9PTF3A", + "kind": "memory", + "score": 0.9203452467918396, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1063.1221, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 3455, + "mcp_result_bytes": 3618, + "wire_bytes": 3655, + "reported_used_tokens": 3618, + "working_set_bytes": 292786176, + "peak_working_set_bytes": 293703680 + }, + { + "query": "Bedrock InvokeModel fails because the region is not configured", + "ranked": [ + "aws-region-resolution", + "aws-sigv4-bedrock-blocking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CH4021WNS0JP2HVKJDXQ", + "id": "01M1Y0HRRHCAC0BZVVRY4J0N27", + "kind": "memory", + "score": 0.99688321352005, + "summary": "project:fact - [tags: aws region env config bedrock kimetsu] The AWS region for Bedrock InvokeModel must come from `AWS_REGION` (or `AWS_DEFAULT_REGION` as fallback). On EC2, the region is also available in the instance metadata at `http://169.254.169.254/latest/meta-data/placement/region`. In kimetsu, if neither env var is set, the Bedrock provider returns a clear configuration error at startup rather than failing cryptically at request time." + }, + { + "expansion_handle": "memory:01M1Y0CDSEBRPP3R78RVYDDGZA", + "id": "01M1Y0HRRH1M1CCVSAB131AVXB", + "kind": "memory", + "score": 0.6450709104537964, + "summary": "project:fact - [tags: aws bedrock sigv4 rust reqwest] aws-sigv4 v1.4.5 API for blocking Bedrock InvokeModel: (1) build Identity via `Credentials::new(ak, sk, session_token, None, \"name\").into()`, (2) `v4::SigningParams::builder().identity(&identity).region(r).name(\"bedrock\").time(t).settings(SigningSettings::default()).build().unwrap().into()` -> `SigningParams`, (3) `SignableRequest::new(\"POST\", url, [(\"content-type\",\"application/json\")].into_iter(), SignableBody::Bytes(&payload))`, (4) `sign(signable, ¶ms)?.into_parts()` -> `(instructions, _sig)`, (5) build a `http::Request` with the same headers then `instructions.apply_to_request_http1x(&mut http_req)` to materialise the signed headers, then copy to reqwest. aws-sigv4 feature flags needed: `sign-http` + `http1` (default). No `sign-eventstream` or `sigv4a` needed." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1074.4991, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1810, + "mcp_result_bytes": 1929, + "wire_bytes": 1966, + "reported_used_tokens": 1929, + "working_set_bytes": 292790272, + "peak_working_set_bytes": 293711872 + }, + { + "query": "how do I handle ThrottlingException from Bedrock with exponential backoff?", + "ranked": [ + "aws-retry-throttling" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CH5512SPEE30NNSG0SKK", + "id": "01M1Y0HSSWGMZNXARFSF14ZT2C", + "kind": "memory", + "score": 0.9997082352638244, + "summary": "project:fact - [tags: aws bedrock retry throttling rate-limit 429] Bedrock returns HTTP 429 (`ThrottlingException`) when you exceed the model's TPS quota. Unlike most HTTP 429s, Bedrock's throttling response body is JSON: `{\"__type\":\"ThrottlingException\",\"message\":\"...rate exceeded...\"}`. Retry with exponential backoff starting at 1s, max 5 attempts, max 30s backoff, with ±25% jitter." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1095.8706, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 771, + "mcp_result_bytes": 868, + "wire_bytes": 905, + "reported_used_tokens": 868, + "working_set_bytes": 292864000, + "peak_working_set_bytes": 293769216 + }, + { + "query": "generating a presigned S3 URL for brain export without exposing credentials", + "ranked": [ + "aws-presigned-urls" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CH6AWWAV734TFW14SHW0", + "id": "01M1Y0HTW6E6X8RQ9KX4V2W2NH", + "kind": "memory", + "score": 0.9990487694740297, + "summary": "project:fact - [tags: aws presigned-url s3 sigv4 expiry rust] AWS presigned URLs embed the SigV4 signature in query parameters instead of headers. To generate one for S3 GetObject: set `X-Amz-Expires` (seconds until expiry, max 604800 for IAM role credentials), include `X-Amz-SignedHeaders=host`, and sign with an empty body hash (`UNSIGNED-PAYLOAD` for public bucket or actual SHA256). The presigned URL is valid only from the signing time — clock skew > 15 minutes causes `RequestTimeTooSkewed`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1040.3455999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 875, + "mcp_result_bytes": 956, + "wire_bytes": 993, + "reported_used_tokens": 956, + "working_set_bytes": 292958208, + "peak_working_set_bytes": 293875712 + }, + { + "query": "IMDSv2 token required for instance metadata — PUT before GET", + "ranked": [ + "aws-instance-metadata" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CH7CGS47V8TBVANKJECQ", + "id": "01M1Y0HVWN30PE4C29789AMEBQ", + "kind": "memory", + "score": 0.9997182488441468, + "summary": "project:fact - [2026-09-07] [tags: aws imds instance-metadata ec2 token] The AWS Instance Metadata Service v2 (IMDSv2) requires a session token: PUT `http://169.254.169.254/latest/api/token` with `X-aws-ec2-metadata-token-ttl-seconds: 21600` to get a token, then GET metadata with `X-aws-ec2-metadata-token: `. IMDSv1 (no token) is disabled on hardened instances. The metadata endpoint is only reachable from within EC2 — a connection timeout means you're not on EC2." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1043.1979000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 851, + "mcp_result_bytes": 932, + "wire_bytes": 969, + "reported_used_tokens": 932, + "working_set_bytes": 292986880, + "peak_working_set_bytes": 293904384 + }, + { + "query": "Cargo cache key strategy for GitHub Actions to avoid toolchain version collisions", + "ranked": [ + "ci-cache-keys" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHAQ3R9BP3JD09Z9JTHP", + "id": "01M1Y0HWXH9FRTKWNN4FDMZKVN", + "kind": "memory", + "score": 0.998869240283966, + "summary": "project:fact - [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key — macOS and Windows have incompatible artifact formats." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1106.5658, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 788, + "mcp_result_bytes": 869, + "wire_bytes": 906, + "reported_used_tokens": 869, + "working_set_bytes": 292986880, + "peak_working_set_bytes": 293904384 + }, + { + "query": "CI matrix has 18 jobs and costs too much — how do I reduce it?", + "ranked": [ + "ci-matrix-explosion" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHBK2CM26GS0FWYJYK27", + "id": "01M1Y0HY0HXB66Q4JBQP9AAPM8", + "kind": "memory", + "score": 0.999057948589325, + "summary": "project:fact - [tags: ci github-actions matrix jobs resources] A CI matrix combining OS (3) x Rust toolchain (3) x features (2) = 18 jobs. Each spawns a runner; at $0.008/min for Ubuntu and $0.016/min for Windows, a 10-minute build costs $2.40 per push. Reduce: test the full matrix only on PRs to main; on feature branches, test only Linux+stable." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1091.4141, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 722, + "mcp_result_bytes": 803, + "wire_bytes": 840, + "reported_used_tokens": 803, + "working_set_bytes": 293109760, + "peak_working_set_bytes": 294031360 + }, + { + "query": "GitHub Actions secret accidentally printed in build logs", + "ranked": [ + "ci-secrets-masking", + "ci-cache-keys", + "ci-artifact-retention" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHCK07ASK7V6B3J3GTZF", + "id": "01M1Y0HZ22003GCZFW4K0C89FR", + "kind": "memory", + "score": 0.9963951706886292, + "summary": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output — but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable." + }, + { + "expansion_handle": "memory:01M1Y0CHAQ3R9BP3JD09Z9JTHP", + "id": "01M1Y0HZ2235HE1B7HFM2QEGGD", + "kind": "memory", + "score": 0.4342843890190125, + "summary": "project:fact - [tags: ci github-actions cache cargo rust cache-key] For Rust projects on GitHub Actions, use `Swatinem/rust-cache` or a custom cache keyed on `Cargo.lock` hash + OS + Rust toolchain version. A stale cache with a different toolchain version causes obscure link errors (`undefined symbol: __rust_probestack`). Always include the OS in the key — macOS and Windows have incompatible artifact formats." + }, + { + "expansion_handle": "memory:01M1Y0CHDPAW207Z23THV886HR", + "id": "01M1Y0HZ22HKS1STVK0RH3FMTA", + "kind": "memory", + "score": 0.3422144949436188, + "summary": "project:fact - [tags: ci github-actions artifacts retention benchmark] GitHub Actions artifacts are retained for 90 days (default). For benchmark results, use `actions/upload-artifact` with `retention-days: 365` for long-term tracking. The free tier has 500MB storage — per-combo JSON files from kimetsu bench (each ~60KB) add up fast if you upload them on every push." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1024.6078, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1791, + "mcp_result_bytes": 1908, + "wire_bytes": 1945, + "reported_used_tokens": 1908, + "working_set_bytes": 293412864, + "peak_working_set_bytes": 294330368 + }, + { + "query": "how long do GitHub Actions artifacts persist and what's the storage limit?", + "ranked": [ + "ci-artifact-retention" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHDPAW207Z23THV886HR", + "id": "01M1Y0J0220H0YWQNY8G84V9BT", + "kind": "memory", + "score": 0.999624252319336, + "summary": "project:fact - [tags: ci github-actions artifacts retention benchmark] GitHub Actions artifacts are retained for 90 days (default). For benchmark results, use `actions/upload-artifact` with `retention-days: 365` for long-term tracking. The free tier has 500MB storage — per-combo JSON files from kimetsu bench (each ~60KB) add up fast if you upload them on every push." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1105.3394, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 744, + "mcp_result_bytes": 825, + "wire_bytes": 862, + "reported_used_tokens": 825, + "working_set_bytes": 293425152, + "peak_working_set_bytes": 294346752 + }, + { + "query": "timing-based test flake in CI — quarantine or fix?", + "ranked": [ + "ci-flaky-quarantine", + "testing-time-dependent-flakes" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHEJR8TEQHZ0MBKNP52Z", + "id": "01M1Y0J14TTNJHCB99A75JT2Z5", + "kind": "memory", + "score": 0.9994743466377258, + "summary": "project:fact - [tags: ci flaky tests quarantine nextest rust] Flaky tests poison CI signal — a 5% flake rate means 40% of runs see at least one failure in a 10-test suite. quarantine flaky tests with `#[ignore]` + a tracking issue, OR run them in a separate job with `continue-on-error: true`. `cargo nextest` supports a `--flaky-test-retries N` flag to auto-retry and mark retried tests in output." + }, + { + "expansion_handle": "memory:01M1Y0CGPCZQS2GMYCWTQKJMA0", + "id": "01M1Y0J14TXMJQK6F1R9H3TZ3V", + "kind": "memory", + "score": 0.9849997162818908, + "summary": "project:fact - [tags: testing time flaky clock mock rust] Tests that depend on wall-clock time are inherently flaky under load (slow CI runners, GC pauses). Abstract time behind a trait (`Clock: Fn() -> SystemTime`) injected at construction, and supply a fake in tests. For tests checking that something happened \"within N seconds\", use a generous multiple of the expected duration (10x is not unreasonable for CI)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1041.5246, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1340, + "mcp_result_bytes": 1443, + "wire_bytes": 1480, + "reported_used_tokens": 1443, + "working_set_bytes": 293679104, + "peak_working_set_bytes": 294604800 + }, + { + "query": "kimetsu doctor says the MCP server is running — how do I stop it before an update?", + "ranked": [ + "kimetsu-daemon-lifecycle", + "mcp-env-propagation", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHFNNPJ5W1HT08QTSPX7", + "id": "01M1Y0J25HH8E2RMMASRF5AAQ9", + "kind": "memory", + "score": 0.9989782571792604, + "summary": "project:fact - [2026-09-07] [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1Y0CGY1YEEKFJHX4JCZYHY6", + "id": "01M1Y0J25H7C70P1W6CG68EXZ4", + "kind": "memory", + "score": 0.9049031734466552, + "summary": "project:fact - [2026-09-07] [tags: mcp env propagation subprocess kimetsu hooks] kimetsu's MCP server is launched by the host agent as a subprocess. The host's environment at launch time is the server's environment — changes to the host's env after launch (e.g. `export KIMETSU_LOG=debug` in a shell that already has the host running) don't propagate." + }, + { + "expansion_handle": "memory:01M1Y0CDGSHENNVZQKGWMDBNDR", + "id": "01M1Y0J25HTAADKFJT5FSKY3V3", + "kind": "memory", + "score": 0.4812128245830536, + "summary": "project:fact - [2026-09-07] [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1053.3948, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2046, + "mcp_result_bytes": 2211, + "wire_bytes": 2248, + "reported_used_tokens": 2211, + "working_set_bytes": 293687296, + "peak_working_set_bytes": 294604800 + }, + { + "query": "noise capsules consuming token budget without contributing retrieval signal", + "ranked": [ + "kimetsu-capsule-budgets" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHGPK9383N7Y4V5RY365", + "id": "01M1Y0J3644EMGQZMTFYY9PP4A", + "kind": "memory", + "score": 0.9997420907020568, + "summary": "project:fact - [tags: kimetsu capsule tokens budget retrieval] kimetsu retrieval enforces a token budget per capsule type: memory capsules are capped at 6000 tokens total (across all retrieved memories), file capsules at 3000 tokens. When a memory is large and would exceed the budget, it is truncated at a sentence boundary. The budget is enforced AFTER reranking — reranking may reorder results so that a truncated high-ranked memory displaces a full lower-ranked one." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 819.1426, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 847, + "mcp_result_bytes": 928, + "wire_bytes": 965, + "reported_used_tokens": 928, + "working_set_bytes": 293687296, + "peak_working_set_bytes": 294604800 + }, + { + "query": "kimetsu_brain_record writes to the wrong brain location — user vs project scope", + "ranked": [ + "kimetsu-memory-scopes", + "kimetsu-write-tools-gate", + "init-project-git-boundary" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHHMQS2VGXDD5KZTKWV1", + "id": "01M1Y0J402MT89QAK5X7RV7TT9", + "kind": "memory", + "score": 0.999030828475952, + "summary": "project:fact - [tags: kimetsu memory scope project user global] kimetsu memories have three scopes: `user` (personal, stored in `~/.kimetsu`), `project` (per-repo, stored in `.kimetsu/`), and `global` (not yet implemented). Scope determines isolation: project memories are invisible outside the repo. `kimetsu brain record` always writes to the innermost scope available — if run inside a git repo with a `.kimetsu/` brain, it writes project-scope; outside, user-scope." + }, + { + "expansion_handle": "memory:01M1Y0CHMVHR9GRRHBAB5NZNDN", + "id": "01M1Y0J4033EFDRJXJFBGSAARK", + "kind": "memory", + "score": 0.9838979840278624, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level — disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1Y0CDW1WMXYA9MGV0TK68J6", + "id": "01M1Y0J403AT897EV9WNEXC9RY", + "kind": "memory", + "score": 0.3852712512016296, + "summary": "project:fact - [tags: kimetsu testing init_project git_boundary isolation] When implementing a `setup` command that calls `init_project`, tests must call `git_init_boundary(&tmp)` before `setup_cmd` so that `ProjectPaths::discover` resolves to the temp dir instead of climbing to a real parent git repo (including the user brain at ~/.kimetsu). Without it the test passes but writes to the real user brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1017.4761000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2098, + "mcp_result_bytes": 2215, + "wire_bytes": 2252, + "reported_used_tokens": 2215, + "working_set_bytes": 293687296, + "peak_working_set_bytes": 294604800 + }, + { + "query": "how do I configure kimetsu to use Claude Haiku for harvesting but Opus for the agent?", + "ranked": [ + "kimetsu-distiller-config" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHJRD9YZNN0K6RBP37DH", + "id": "01M1Y0J4ZQZGZHSDQGAJKR4B54", + "kind": "memory", + "score": 0.9989088773727416, + "summary": "project:fact - [tags: kimetsu distiller harvest config provider] The kimetsu distiller (auto-harvester) uses a SEPARATE provider configuration from the main agent: `distiller.provider`, `distiller.model`, `distiller.api_key`. This allows running the agent on an expensive model (Claude Opus) while harvesting with a cheap model (Claude Haiku). If `distiller.provider` is not set, it inherits `provider`." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1046.5562, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 778, + "mcp_result_bytes": 859, + "wire_bytes": 896, + "reported_used_tokens": 859, + "working_set_bytes": 293703680, + "peak_working_set_bytes": 294621184 + }, + { + "query": "first agent turn is slow because kimetsu proactive hook runs embedding inference", + "ranked": [ + "kimetsu-proactive-hooks", + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHKTX9JMQPFAQV1QS487", + "id": "01M1Y0J60W733WBNTEYV12MTVM", + "kind": "memory", + "score": 0.999568521976471, + "summary": "project:fact - [2026-09-07] [tags: kimetsu proactive hooks context injection] kimetsu's proactive context injection runs before each agent turn (pre-turn hook) and injects relevant memories into the system prompt prefix. The hook invocation adds latency to the first token: embedding inference + vector search + reranking + context formatting. On a cold start, this can be 1-3 seconds." + }, + { + "expansion_handle": "memory:01M1Y0CGWYZ09QBQFPX4FEVE70", + "id": "01M1Y0J60X8ZQFEZWFD2JPX44Q", + "kind": "memory", + "score": 0.9405298233032228, + "summary": "project:fact - [2026-09-07] [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking — in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize — keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1104.3481, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1403, + "mcp_result_bytes": 1502, + "wire_bytes": 1539, + "reported_used_tokens": 1502, + "working_set_bytes": 293703680, + "peak_working_set_bytes": 294621184 + }, + { + "query": "make the kimetsu brain read-only for certain repos on a shared remote server", + "ranked": [ + "kimetsu-write-tools-gate", + "remote-ingest-split-roots", + "remote-mcp-host-wiring" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHMVHR9GRRHBAB5NZNDN", + "id": "01M1Y0J72ZD54011XJVEBC504F", + "kind": "memory", + "score": 0.997682809829712, + "summary": "project:fact - [tags: kimetsu mcp write-tools config security] kimetsu's write tools (`kimetsu_brain_record`, `kimetsu_brain_delete`) are gated by a `write_tools_enabled` config flag (default: true locally, false for untrusted remote contexts). The gate is checked at the MCP `tools/list` level — disabled tools are simply absent from the response, so the host agent never knows they exist. For shared kimetsu-remote deployments, set `write_tools_enabled = false` per repo-id in the server config to make the brain read-only for that repo's clients." + }, + { + "expansion_handle": "memory:01M1Y0CDERH0MV40CSBCQDEQYA", + "id": "01M1Y0J72ZW401EAWKGM35EPNB", + "kind": "memory", + "score": 0.9957050681114196, + "summary": "project:fact - [tags: kimetsu remote ingest git rust] Kimetsu remote server-side ingest: the brain and the files-to-ingest live at DIFFERENT roots on a server (brain under --data//.kimetsu, files in a managed git checkout), but project::ingest_repo walks paths.repo_root which equals the brain root. Solution: add project::ingest_repo_at_root(brain_root, files_root) = load_project_at_root(brain_root) then override `paths.repo_root = files_root.canonicalize()` before calling ingest::ingest_repo — lock/brain stay under brain_root, the file walk hits the checkout. Security model: operator pre-registers repo-id->git URL in a --repos-file (clients can't make the server clone arbitrary URLs); invoke git via Command argv (no shell -> no injection); shallow clone (--depth 1) + fetch/reset to refresh; serialize ingests with a per-server tokio Mutex to avoid checkout races." + }, + { + "expansion_handle": "memory:01M1Y0CDGSHENNVZQKGWMDBNDR", + "id": "01M1Y0J72ZYP6RWND2YZYTN3FA", + "kind": "memory", + "score": 0.9909282326698304, + "summary": "project:fact - [tags: kimetsu mcp remote bridge host-integration] Kimetsu remote-MCP host wiring shapes (for `kimetsu plugin install --remote`): Claude Code `.mcp.json` remote server = {\"type\":\"http\",\"url\":\"/mcp/\",\"headers\":{\"Authorization\":\"Bearer \"}} under both `mcpServers` and `servers`. OpenClaw `openclaw.json` = mcp.servers.kimetsu {\"url\":...,\"transport\":\"streamable-http\",\"headers\":{\"Authorization\":...}}. Default the token to the literal string `${KIMETSU_REMOTE_TOKEN}` (both hosts expand ${VAR} in config) so the secret isn't written to disk; --token writes a literal." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 955.7925, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2725, + "mcp_result_bytes": 2890, + "wire_bytes": 2927, + "reported_used_tokens": 2890, + "working_set_bytes": 293724160, + "peak_working_set_bytes": 294641664 + }, + { + "query": "kimetsu FTS search misses 'deadlocking' when memory says 'deadlock'", + "ranked": [ + "kimetsu-query-stemming", + "mutex-deadlock-user-brain-disabled" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHR9RRWWX5FQ2Z9H6B27", + "id": "01M1Y0J80S1SQ9MW4Z8H77X8HE", + "kind": "memory", + "score": 0.9904030561447144, + "summary": "project:fact - [2026-09-07] [tags: kimetsu retrieval stemming lexical fts5] kimetsu applies Porter stemming to the query side of FTS5 searches (not the index side) to improve lexical recall on morphologically related terms. \"deadlocking\" -> \"deadlock\", \"embeddings\" -> \"embed\". This is done via a Rust port of the Porter stemmer applied to each query token before building the FTS5 MATCH expression." + }, + { + "expansion_handle": "memory:01M1Y0CDDKV46DFCQB5VK0TWX5", + "id": "01M1Y0J80ST8HAEZF5F6K7R0DQ", + "kind": "memory", + "score": 0.91664320230484, + "summary": "project:fact - [2026-09-07] [tags: rust testing mutex deadlock kimetsu-brain] When a Rust test uses `with_user_brain_disabled(|| { ... })`, do NOT call `test_env_lock().lock()` inside the closure — `with_user_brain_disabled` already acquires that same non-reentrant `std::sync::Mutex`, causing an immediate deadlock. Simply save/restore extra env vars directly inside the closure; the outer lock already serializes all env mutation." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 937.5399, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1363, + "mcp_result_bytes": 1478, + "wire_bytes": 1515, + "reported_used_tokens": 1478, + "working_set_bytes": 293744640, + "peak_working_set_bytes": 294658048 + }, + { + "query": "how does pool size affect retrieval recall and latency in the bench?", + "ranked": [ + "kimetsu-rerank-pool" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHSBCRWVC89BQK1ZX1Z7", + "id": "01M1Y0J8Y4275MZ24TP2QA28BK", + "kind": "memory", + "score": 0.9998373985290528, + "summary": "project:fact - [tags: kimetsu reranker pool size ann retrieval] kimetsu's retrieval pipeline: ANN (approximate nearest neighbor) retrieves a pool of candidates, then the reranker reorders them, then the top-K are returned. The pool size (default 6 for production, 12 in bench) controls the recall-latency tradeoff: larger pool = higher recall = more reranker calls = more latency. For the jina-tiny reranker, pool 12 adds ~80ms vs pool 6." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1016.9318000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 813, + "mcp_result_bytes": 894, + "wire_bytes": 931, + "reported_used_tokens": 894, + "working_set_bytes": 293756928, + "peak_working_set_bytes": 294678528 + }, + { + "query": "second embedder in a remote bench run gets worse results than the first", + "ranked": [ + "kimetsu-bench-remote-embedder-singleton" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHTA3AQK16G6EE1QCJKZ", + "id": "01M1Y0J9XWJGPSV3QKW8K6080B", + "kind": "memory", + "score": 0.9939629435539246, + "summary": "project:fact - [2026-09-07] [tags: kimetsu bench remote embedder singleton process-global] The kimetsu-remote bench path has a known issue: the embedder is initialized as a process-global singleton on the first `--embedders` value. If you pass multiple comma-separated embedders in one `brain bench --remote` invocation, subsequent embedders get seeded with the FIRST embedder's vectors (because the remote server's brain is seeded in-process). This silently degrades all but the first embedder to lexical-only retrieval." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1042.4408, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 895, + "mcp_result_bytes": 976, + "wire_bytes": 1013, + "reported_used_tokens": 976, + "working_set_bytes": 293756928, + "peak_working_set_bytes": 294678528 + }, + { + "query": "what is the expected JSON schema for kimetsu brain bench dataset files?", + "ranked": [ + "kimetsu-eval-fixture-shape", + "testing-fixture-drift", + "kimetsu-mrr-metric", + "mcp-schema-validation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHVB50Y5BXTDMZRNKFGR", + "id": "01M1Y0JAYSRYC3MS1F9ZA4ND8Z", + "kind": "memory", + "score": 0.9996767044067384, + "summary": "project:fact - [tags: kimetsu eval fixture dataset bench shape] The `EvalFixture` JSON shape expected by `kimetsu brain bench` is: `{\"memories\": [{\"key\": \"\", \"text\": \"\"}], \"cases\": [{\"query\": \"\", \"relevant\": [\"\", \"\"]}]}`. Keys in `relevant` MUST exist in `memories` — a key mismatch causes a bench panic at dataset load time. `relevant: []` is valid (no-answer cases)." + }, + { + "expansion_handle": "memory:01M1Y0CGTZS0E9FZN5W5KHQQXA", + "id": "01M1Y0JAYS490H835J6MHDP267", + "kind": "memory", + "score": 0.9682880640029908, + "summary": "project:fact - [tags: testing fixtures drift schema migration rust] Test fixtures (hardcoded JSON/TOML/SQL in test files) drift from the production schema over time. After a migration, tests that use old fixture formats fail with confusing deserialization errors rather than migration failures. Best practice: generate fixtures programmatically from the same constructors used in production code." + }, + { + "expansion_handle": "memory:01M1Y0CHWG3T7N3ZZ8F2Z1EQHR", + "id": "01M1Y0JAYS6V03BPW4DHTGP8MF", + "kind": "memory", + "score": 0.8818408250808716, + "summary": "project:fact - [tags: kimetsu bench mrr recall metrics evaluation] kimetsu bench reports MRR (Mean Reciprocal Rank) and Recall@K. MRR is 1/rank_of_first_relevant_result, averaged across cases; it penalizes models that rank the correct answer 2nd or 3rd. Recall@K is the fraction of cases where at least one relevant answer appears in the top K." + }, + { + "expansion_handle": "memory:01M1Y0CGZ4ETMHT4DV0PTAD4FW", + "id": "01M1Y0JAYSB72ZBGPSP5DT8HP0", + "kind": "memory", + "score": 0.6527947187423706, + "summary": "project:fact - [tags: mcp schema jsonschema tool-definition rust] MCP tool input schemas are JSON Schema (draft 7). Host agents validate client inputs against the schema before calling the tool. For kimetsu tools, all required fields must be in the `required` array — omitting a field from `required` but including it in `properties` means the host may send a call without that field, causing a Rust deserialization error." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1067.3744000000002, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2424, + "mcp_result_bytes": 2603, + "wire_bytes": 2640, + "reported_used_tokens": 2603, + "working_set_bytes": 293752832, + "peak_working_set_bytes": 294678528 + }, + { + "query": "what does MRR mean and how do I interpret a 0.01 difference between combos?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1027.9297000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 293752832, + "peak_working_set_bytes": 294678528 + }, + { + "query": "SQLITE_BUSY keeps appearing even with WAL mode enabled", + "ranked": [ + "sqlite-busy-timeout-wal", + "sqlite-wal-network-drive" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CE63YDGRR9GCFM6A405N", + "id": "01M1Y0JD04D9Z44NWBTF4ETWT4", + "kind": "memory", + "score": 0.9982662796974182, + "summary": "project:fact - [tags: sqlite rust rusqlite wal busy_timeout] When multiple readers and a writer share a SQLite WAL-mode database, always set `PRAGMA busy_timeout = 5000;` immediately after opening the connection. Without it, any write that hits a locked WAL returns SQLITE_BUSY instantly, and rusqlite surfaces this as `Error::SqliteFailure` with extended code 5. In WAL mode, readers never block writers, but a writer still blocks other writers; 5 seconds is usually enough for the background harvester to finish its batch." + }, + { + "expansion_handle": "memory:01M1Y0CE84FR94JFPZ2CTRK5KY", + "id": "01M1Y0JD057X5VQCJATC8Y7GNA", + "kind": "memory", + "score": 0.7844027280807495, + "summary": "project:fact - [tags: sqlite wal network-drive windows] Do NOT use SQLite WAL mode on network drives (SMB/CIFS/NFS mounts on Windows). WAL mode relies on shared-memory files (`-shm`) that require byte-range locking semantics the OS provides for local filesystems but not reliably for SMB. Symptoms: SQLITE_IOERR_LOCK or SQLITE_CANTOPEN when a second process opens the same db." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1094.0042999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1423, + "mcp_result_bytes": 1522, + "wire_bytes": 1559, + "reported_used_tokens": 1522, + "working_set_bytes": 293773312, + "peak_working_set_bytes": 294690816 + }, + { + "query": "my brain file got huge again right after I compacted it", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 985.3113999999999, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 293773312, + "peak_working_set_bytes": 294690816 + }, + { + "query": "all my FTS queries stopped returning results after I changed the tokenizer config", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1052.9347, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 293773312, + "peak_working_set_bytes": 294690816 + }, + { + "query": "something is preventing the kimetsu binary from being replaced during update", + "ranked": [ + "kimetsu-daemon-lifecycle", + "windows-update-process-locking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CHFNNPJ5W1HT08QTSPX7", + "id": "01M1Y0JG2DKPRY5W4Z0MBNBAMM", + "kind": "memory", + "score": 0.9678457975387572, + "summary": "project:fact - [tags: kimetsu daemon mcp server lifecycle startup] kimetsu's MCP server is NOT a long-running daemon; it is launched by the host agent as a subprocess (stdio transport). There is no separate `kimetsu serve` command needed — the host manages the process lifetime. For HTTP transport (kimetsu-remote), a separate `kimetsu-remote --port 8080` process is required." + }, + { + "expansion_handle": "memory:01M1Y0CE3VN854570296AQJ8B8", + "id": "01M1Y0JG2EAX2663C2P9991NE0", + "kind": "memory", + "score": 0.9395453929901124, + "summary": "project:fact - [tags: rust windows update process locking] When adding a pre-flight check that reuses a live OS process-enumerator already added by an earlier task (Q1 `list_kimetsu_processes`), delegate the post-failure fallback helper (`kimetsu_processes_locking`) to the same enumerator via a path-filter wrapper (`processes_locking_target`) rather than keeping a second PowerShell query. The filter belongs in `process.rs` (pure, test-friendly) and the decision seam (`decide_preflight_action`) belongs in `update.rs` as a `pub fn` with `BufRead`/`Write` generics — mirrors `resolve_tier` for uninstall. Mark the retained-for-tests pure parser with `#[cfg_attr(not(test), allow(dead_code))]` to silence the dead-code lint without deleting tested code." + } + ], + "positive_recall_at_4": 0.6666666666666666, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 942.629, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1657, + "mcp_result_bytes": 1756, + "wire_bytes": 1793, + "reported_used_tokens": 1756, + "working_set_bytes": 293773312, + "peak_working_set_bytes": 294690816 + }, + { + "query": "tool call results not appearing in the context — is the semantic floor too high?", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1074.0403000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 293781504, + "peak_working_set_bytes": 294707200 + }, + { + "query": "CARGO_INCREMENTAL=0 in CI prevents a class of spurious compilation errors", + "ranked": [ + "cargo-incremental-cache-corruption" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CEKN0QBDZ73SACBN6Y6X", + "id": "01M1Y0JJ11K46MD3BXJC930ZAC", + "kind": "memory", + "score": 0.7995238304138184, + "summary": "project:fact - [tags: cargo incremental compilation cache corruption] Cargo incremental compilation caches `.d` dependency files and `.rmeta` artifacts. If you switch branches that change proc-macro or build-script outputs without a clean, you can get cache corruption: the compiler reads a stale `.rmeta` for an unchanged dep, emits a type mismatch, and the error message references a type the source no longer contains. Symptom: error message cites a type or trait not found anywhere in the codebase." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1013.2102999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 877, + "mcp_result_bytes": 958, + "wire_bytes": 995, + "reported_used_tokens": 958, + "working_set_bytes": 293826560, + "peak_working_set_bytes": 294748160 + }, + { + "query": "how do I check whether my Cargo workspace respects the MSRV constraint?", + "ranked": [ + "cargo-msrv", + "cargo-dev-dep-leak", + "cargo-patch-section", + "cargo-target-dir-sharing" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CEPM3E4XMZRG0JF64DYX", + "id": "01M1Y0JK0NAYC2Y2XGXPCERRXX", + "kind": "memory", + "score": 0.9921064376831056, + "summary": "project:fact - [tags: cargo rust msrv edition compatibility] Set `rust-version` in each `Cargo.toml` to declare the minimum supported Rust version (MSRV). Cargo enforces this with `--check`: `cargo check` fails if the toolchain is older than `rust-version`. Keep MSRV as old as your oldest supported deployment target." + }, + { + "expansion_handle": "memory:01M1Y0CEHKWTJ8ZMXANWDJAMCD", + "id": "01M1Y0JK0PSEF7D2VKF306HW81", + "kind": "memory", + "score": 0.887407660484314, + "summary": "project:fact - [tags: cargo dev-dependencies feature-leak workspace] `dev-dependencies` in a crate's Cargo.toml should never appear under `[dependencies]` unless required at runtime. However, in a Cargo workspace, a dev-dep that activates features of a shared dependency can still influence feature resolution for the whole build if it appears in `[dev-dependencies]` with `features = [...]` of a workspace-shared crate. The fix is to move the feature-activating dev dep into its own test-helper crate with `default = []`, or use `cfg(test)` feature gates." + }, + { + "expansion_handle": "memory:01M1Y0CENN1SARTT7M8KTJ5XE9", + "id": "01M1Y0JK0PB1YAZVJD33VRQFW0", + "kind": "memory", + "score": 0.7220955491065979, + "summary": "project:fact - [tags: cargo patch workspace dependency override] Use `[patch.crates-io]` in the workspace root `Cargo.toml` to override a transitive dependency with a local path or git revision: `my-crate = { path = \"../my-crate\" }`. This patches ALL crates in the workspace — including transitive deps — that depend on `my-crate`. Remove the patch before publishing." + }, + { + "expansion_handle": "memory:01M1Y0CEJNCGQE145VV7PMAA9T", + "id": "01M1Y0JK0PYA75CWSCY9RQM5HR", + "kind": "memory", + "score": 0.4095200598239898, + "summary": "project:fact - [tags: cargo target-dir workspace incremental cache] All crates in a Cargo workspace share a single `target/` directory by default. This is usually good (shared artifacts), but can cause problems: (1) `cargo clean -p ` only removes that crate's artifacts, not shared deps — use `cargo clean` for a full rebuild; (2) on Windows, antivirus or indexing services locking files in `target/` cause sporadic LNK errors; add `target/` to AV exclusions. If you have multiple workspaces on the same machine, set `CARGO_TARGET_DIR` or `.cargo/config.toml` `[build] target-dir` to avoid cross-contamination." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1017.9978999999998, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 2682, + "mcp_result_bytes": 2821, + "wire_bytes": 2858, + "reported_used_tokens": 2821, + "working_set_bytes": 293826560, + "peak_working_set_bytes": 294752256 + }, + { + "query": "rusqlite connection opened but ON DELETE CASCADE cascade never fires", + "ranked": [ + "sqlite-foreign-keys-default-off" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CEB89X22N5Z655769N3B", + "id": "01M1Y0JM10ZQQCM3J90RAVCQAM", + "kind": "memory", + "score": 0.9922945499420166, + "summary": "project:fact - [tags: sqlite foreign-keys rusqlite schema] SQLite foreign key enforcement is OFF by default and must be enabled per connection with `PRAGMA foreign_keys = ON;`. This is a connection-level setting, not a database-wide setting — every connection must set it. Forgetting this means ON DELETE CASCADE / ON UPDATE CASCADE rules silently do nothing." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1019.9111, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 735, + "mcp_result_bytes": 816, + "wire_bytes": 853, + "reported_used_tokens": 816, + "working_set_bytes": 293826560, + "peak_working_set_bytes": 294752256 + }, + { + "query": "I cannot connect to kimetsu-remote — something about TLS cert validation failed", + "ranked": [], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 976.5345, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 227, + "mcp_result_bytes": 290, + "wire_bytes": 327, + "reported_used_tokens": 290, + "working_set_bytes": 293826560, + "peak_working_set_bytes": 294752256 + }, + { + "query": "graceful shutdown fails because in-flight SQLite queries are still running when pool closes", + "ranked": [ + "tokio-shutdown-ordering" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGCDX7QHSQ0KP7DJW3ES", + "id": "01M1Y0JNYYNT98GS85HHZ8ZRXA", + "kind": "memory", + "score": 0.9996342658996582, + "summary": "project:fact - [2026-09-07] [tags: tokio shutdown graceful ordering async rust] Graceful tokio shutdown requires careful ordering: (1) stop accepting new requests, (2) wait for in-flight requests to complete, (3) shut down background tasks (harvester, GC), (4) flush writes to SQLite, (5) close connections. A common mistake is shutting down the database connection pool before in-flight tasks finish their queries — the task panics when the pool returns an error. Use a `tokio::sync::broadcast` channel for the shutdown signal and a `JoinSet` to track in-flight tasks." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 962.4521000000001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 947, + "mcp_result_bytes": 1028, + "wire_bytes": 1065, + "reported_used_tokens": 1028, + "working_set_bytes": 293826560, + "peak_working_set_bytes": 294752256 + }, + { + "query": "kimetsu-remote response takes 8 seconds — which stage is slow?", + "ranked": [ + "mcp-tool-timeouts" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGWYZ09QBQFPX4FEVE70", + "id": "01M1Y0JPX6BP5GH2AYQZESWGBR", + "kind": "memory", + "score": 0.9876242876052856, + "summary": "project:fact - [tags: mcp timeout tool-call kimetsu host] MCP tool calls have a timeout enforced by the host (Claude Code: 60s, configurable). kimetsu_brain_context triggers embedding inference + vector search + optional reranking — in the worst case (cold model, large corpus, slow reranker) this can take 8-12 seconds. To prevent timeout-induced retries, cache the loaded model (the fastembed EmbeddingModel is NOT cheap to initialize — keep it in a process-global `OnceLock`)." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1071.5462, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 858, + "mcp_result_bytes": 939, + "wire_bytes": 976, + "reported_used_tokens": 939, + "working_set_bytes": 293826560, + "peak_working_set_bytes": 294752256 + }, + { + "query": "git reflog to rescue accidentally deleted branch", + "ranked": [ + "git-reflog-rescue" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CG3J6F6PE488KD89FHD4", + "id": "01M1Y0JQZ0JVWA00YKB1H9GBGP", + "kind": "memory", + "score": 0.998464822769165, + "summary": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone — they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only — remote reflog is not accessible via normal git commands." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1096.6826, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 761, + "mcp_result_bytes": 842, + "wire_bytes": 879, + "reported_used_tokens": 842, + "working_set_bytes": 293826560, + "peak_working_set_bytes": 294752256 + }, + { + "query": "git submodule --remote advances the pinned SHA unexpectedly", + "ranked": [ + "git-submodule-pinning", + "git-reflog-rescue", + "ci-secrets-masking" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CG2J6XC8ARPF2CP7BP61", + "id": "01M1Y0JS1KPJK0G5YQKRPCTMKY", + "kind": "memory", + "score": 0.9998551607131958, + "summary": "project:fact - [tags: git submodule pinning workspace kbench] Git submodules record a specific commit SHA, not a branch. Running `git submodule update --remote` advances the pinned commit to the branch tip — this is intentional only during a deliberate upgrade. In a CI pipeline, always `git submodule update --init --recursive` (without `--remote`) to get the pinned version." + }, + { + "expansion_handle": "memory:01M1Y0CG3J6F6PE488KD89FHD4", + "id": "01M1Y0JS1KGCJPKHN9BVZJ7ED4", + "kind": "memory", + "score": 0.8857361078262329, + "summary": "project:fact - [tags: git reflog recovery lost-commit reset] If you accidentally `git reset --hard HEAD~N` or force-push over commits, they are NOT gone — they live in the reflog for 90 days by default. Run `git reflog show HEAD` to find the lost SHA, then `git checkout -b rescue ` to recover. The reflog is local only — remote reflog is not accessible via normal git commands." + }, + { + "expansion_handle": "memory:01M1Y0CHCK07ASK7V6B3J3GTZF", + "id": "01M1Y0JS1K0RTSZMMFTC19D17B", + "kind": "memory", + "score": 0.8434544205665588, + "summary": "project:fact - [tags: ci secrets masking github-actions environment] GitHub Actions automatically masks values added via `${{ secrets.MY_SECRET }}` in log output — but only EXACT matches are masked. If the secret contains a substring that appears in a build output (e.g. a UUID that's also a git SHA prefix), the masking is unreliable." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 995.8891, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1771, + "mcp_result_bytes": 1888, + "wire_bytes": 1925, + "reported_used_tokens": 1888, + "working_set_bytes": 293826560, + "peak_working_set_bytes": 294752256 + }, + { + "query": "axum SSE streaming drops the last event when client disconnects", + "ranked": [ + "http-streaming-bodies" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CGHXSFNGV11ZX9ABYM9G", + "id": "01M1Y0JT06VA79FNGD2B30AQE9", + "kind": "memory", + "score": 0.9926375150680542, + "summary": "project:fact - [2026-09-07] [tags: http streaming reqwest axum body rust] For streaming LLM responses (server-sent events), use reqwest's `Response::bytes_stream()` + `tokio_util::io::StreamReader` or iterate with `.chunk().await?`. Axum streaming response: return `axum::response::Sse>>` for SSE or `axum::body::Body::from_stream(...)` for raw chunked transfer. Buffer entire SSE frames before forwarding — a chunk may split across frame boundaries." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1047.3868, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 859, + "mcp_result_bytes": 940, + "wire_bytes": 977, + "reported_used_tokens": 940, + "working_set_bytes": 293826560, + "peak_working_set_bytes": 294752256 + }, + { + "query": "how do I detect that I am running inside a git worktree vs the main checkout?", + "ranked": [ + "git-worktree-brain-isolation" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFYDVF1XT2NJ4R1CE9KK", + "id": "01M1Y0JV20CS3ZXDBNP9KMQ8AF", + "kind": "memory", + "score": 0.9857924580574036, + "summary": "project:fact - [tags: git worktrees kimetsu brain isolation] When using `git worktree add` to work on multiple branches simultaneously, each worktree shares the `.git` directory of the main checkout. kimetsu's `ProjectPaths::discover` walks up to the git root — if you run kimetsu from a worktree, it finds the SAME `.git` and therefore the SAME kimetsu brain as the main checkout. This is usually correct behavior (one brain per repo), but tests spawned from a worktree can contaminate the shared brain." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1080.4377, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 881, + "mcp_result_bytes": 962, + "wire_bytes": 999, + "reported_used_tokens": 962, + "working_set_bytes": 293826560, + "peak_working_set_bytes": 294752256 + }, + { + "query": "ONNX Runtime intra-op threads causing CPU contention during parallel bench", + "ranked": [ + "onnx-ort-threading", + "tokio-blocking-in-async" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CFXBEVH5NSD9GP4MD291", + "id": "01M1Y0JW2T99XCP5XBFGPH7H44", + "kind": "memory", + "score": 0.9999210834503174, + "summary": "project:fact - [tags: onnx ort thread-pool parallelism cpu] ORT (ONNX Runtime) creates its own inter-op and intra-op thread pools. In a multi-process bench setup, each child inherits these pools and they compete for CPU cores. Set `SessionOptionsBuilder::with_intra_threads(1).with_inter_threads(1)` if you're running many parallel bench processes — this sacrifices per-inference throughput for lower contention." + }, + { + "expansion_handle": "memory:01M1Y0CG4GXEDWCNR553B345ZY", + "id": "01M1Y0JW2VP1TCSPS0G30X7782", + "kind": "memory", + "score": 0.5390238761901855, + "summary": "project:fact - [tags: tokio async blocking rust performance] Running blocking I/O or CPU-bound work directly inside a tokio async task starves the runtime's worker threads. Use `tokio::task::spawn_blocking(|| { heavy_work() })` to offload to a dedicated thread pool. SQLite operations via rusqlite are ALWAYS blocking — never call rusqlite directly from an async fn without spawn_blocking." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 947.4401, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 1328, + "mcp_result_bytes": 1427, + "wire_bytes": 1464, + "reported_used_tokens": 1427, + "working_set_bytes": 293826560, + "peak_working_set_bytes": 294752256 + }, + { + "query": "what is the right way to supply AWS session token alongside access key and secret?", + "ranked": [ + "aws-credentials-chain" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0CH2MJMDSTCRQ0SR4KPC5", + "id": "01M1Y0JX0DQ8K13P0MV9FK08DW", + "kind": "memory", + "score": 0.9493365287780762, + "summary": "project:fact - [tags: aws credentials chain env iam instance-profile] AWS SDK and kimetsu's hand-rolled SigV4 both resolve credentials in a chain: (1) `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`+`AWS_SESSION_TOKEN` env vars, (2) `~/.aws/credentials` named profile (`AWS_PROFILE` or `[default]`), (3) EC2 instance metadata (169.254.169.254), (4) ECS task credentials (via `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`). kimetsu reads ONLY env vars (step 1). If you need profile or instance role support, load the file manually." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1018.4159, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 895, + "mcp_result_bytes": 976, + "wire_bytes": 1013, + "reported_used_tokens": 976, + "working_set_bytes": 293953536, + "peak_working_set_bytes": 294866944 + } + ], + "id": "existing-development-100", + "dimension": "retrieval", + "tier": "hard", + "score": 0.8182539682539681, + "skipped": false, + "detail": "positive-recall@4=0.84 mrr=0.85 stale-hit=n/a resolution=n/a false-injection=0.538 (n=13) positive-n=197 negative-n=13 (210 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 0.8182539682539681, + 1 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 0.8182539682539681, + "n": 1, + "ci95": null + } + }, + "overall_index": 0.8182539682539681, + "scenario_weighted_index": 0.8182539682539681 +} diff --git a/docs/audits/2026-09-07-structured-facts/results/development/comparison.json b/docs/audits/2026-09-07-structured-facts/results/development/comparison.json new file mode 100644 index 0000000..a49e634 --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/results/development/comparison.json @@ -0,0 +1,155 @@ +{ + "schema_version": 1, + "status": "complete", + "harness": { + "path": "E:\\Kimetsu\\bench\\target\\release\\kbench.exe", + "sha256": "5ba5065f9aaa28ced75a091bb43e01c8b995751e0e5cd3f9842ac6c822e844ff", + "bytes": 9405952 + }, + "runner": { + "path": "E:\\tmp\\kimetsu-brain-hardening\\bench\\scripts\\compare_brainbench.py", + "sha256": "738bad9404a4ec2b911fff661967ca56f48b584dfeb22f83823c972a1498df37", + "bytes": 24527 + }, + "binaries": { + "baseline": { + "path": "E:\\tmp\\kimetsu-brain-hardening\\tmp-tests\\kimetsu-answerability-candidate.exe", + "sha256": "405d3483fe320e76b0ec776bf9ada3b7771852b04a73f70f3b5da377a43d31c3", + "bytes": 47151104 + }, + "candidate": { + "path": "E:\\tmp\\kimetsu-brain-hardening\\tmp-tests\\kimetsu-structured-facts-candidate.exe", + "sha256": "b5672cfed1da5bbd03fdbc20b954582ad5216f4fb579c8e463914c0839126ee7", + "bytes": 47382016 + } + }, + "datasets": [ + { + "path": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-retrieval\\development-100.json", + "sha256": "ff3c78f8b5dab7e9b2f10894af93f07965705502a4f9d3e8c45c529b9b6ca33f", + "bytes": 122806 + } + ], + "settings": { + "budget_tokens": 6000, + "dimensions": [ + "poisoning", + "render-contract", + "retrieval", + "workflow" + ], + "jobs": 1, + "warm_start": false, + "include_ambient": false, + "overrides": { + "KIMETSU_BRAIN_EMBEDDER": "bge-small-en-v1.5", + "KIMETSU_DETECT_CONFLICTS": "0", + "KIMETSU_RESOLVE_CONFLICTS": "0", + "FASTEMBED_CACHE_DIR": "E:\\Kimetsu\\.fastembed_cache", + "HF_HOME": "E:\\tmp\\kimetsu-brain-hardening\\tmp-tests\\hf-home" + }, + "baseline_threads": 0, + "candidate_threads": 0, + "baseline_reranker": "ms-marco-tinybert-l-2-v2", + "candidate_reranker": "ms-marco-tinybert-l-2-v2", + "baseline_rerank_floor": 0.3, + "candidate_rerank_floor": 0.3 + }, + "runs": [ + { + "label": "baseline", + "repeat": 1, + "intra_threads_override": null, + "rerank_floor_override": "0.3", + "explicit_fact_guard_override": "true", + "reranker_override": "ms-marco-tinybert-l-2-v2", + "wall_seconds": 212.0146130999783, + "report_file": "1-baseline.json" + }, + { + "label": "candidate", + "repeat": 1, + "intra_threads_override": null, + "rerank_floor_override": "0.3", + "explicit_fact_guard_override": "true", + "reranker_override": "ms-marco-tinybert-l-2-v2", + "wall_seconds": 215.18466660002014, + "report_file": "1-candidate.json" + } + ], + "comparison": { + "measurement_summary": { + "baseline": { + "unique_queries": 210, + "query_observations": 210, + "positive_queries": 197, + "negative_queries": 13, + "stale_queries": 0, + "positive_recall_at_4": 0.8417935702199661, + "positive_hit_at_4": 0.8578680203045685, + "positive_mrr": 0.850253807106599, + "negative_injection_rate": 0.5384615384615384, + "stale_injection_rate": null, + "first_query_mean_ms": 1075.8079, + "subsequent_query_p50_ms": 996.8795, + "subsequent_query_p95_ms": 1111.6677, + "subsequent_observations": 209, + "mean_model_text_bytes": 1167.5666666666666, + "mean_mcp_result_bytes": 1262.395238095238, + "memory_observations": 210, + "mean_mcp_working_set_bytes": 288490895.84761906, + "max_mcp_peak_working_set_bytes": 294875136, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + }, + "candidate": { + "unique_queries": 210, + "query_observations": 210, + "positive_queries": 197, + "negative_queries": 13, + "stale_queries": 0, + "positive_recall_at_4": 0.8417935702199661, + "positive_hit_at_4": 0.8578680203045685, + "positive_mrr": 0.850253807106599, + "negative_injection_rate": 0.5384615384615384, + "stale_injection_rate": null, + "first_query_mean_ms": 1064.4432, + "subsequent_query_p50_ms": 1011.3393, + "subsequent_query_p95_ms": 1094.0042999999998, + "subsequent_observations": 209, + "mean_model_text_bytes": 1167.5666666666666, + "mean_mcp_result_bytes": 1262.395238095238, + "memory_observations": 210, + "mean_mcp_working_set_bytes": 287489287.3142857, + "max_mcp_peak_working_set_bytes": 294866944, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + } + }, + "by_dimension": { + "retrieval": { + "n_scenarios": 1, + "baseline": 0.8182539682539681, + "candidate": 0.8182539682539681, + "mean_delta": 0.0, + "ci95": null, + "wins": 0, + "ties": 1, + "losses": 0 + } + }, + "scenarios": [ + { + "identity": "retrieval/existing-development-100", + "dimension": "retrieval", + "baseline": 0.8182539682539681, + "candidate": 0.8182539682539681, + "delta": 0.0 + } + ], + "unpaired_scenarios": [], + "unpaired_details": [], + "baseline_errors": 0, + "candidate_errors": 0, + "repeats": 1, + "uncertainty_note": "Exploratory paired bootstrap over scenario IDs after averaging repeats; correlated task families require a separate grouped holdout." + } +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-structured-facts/results/development/comparison.md b/docs/audits/2026-09-07-structured-facts/results/development/comparison.md new file mode 100644 index 0000000..fc83b0b --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/results/development/comparison.md @@ -0,0 +1,26 @@ +# Paired BrainBench comparison + +Same harness and fixture; run order alternates. Positive delta favors the candidate. + +| Dimension | Scenarios | Baseline | Candidate | Delta | Exploratory 95% interval | +|---|---:|---:|---:|---:|---| +| retrieval | 1 | 0.818 | 0.818 | +0.000 | n/a | + +Errors: baseline 0, candidate 0. +Unpaired/skipped scenarios: 0. + +Exploratory paired bootstrap over scenario IDs after averaging repeats; correlated task families require a separate grouped holdout. + +Wall times include process/model startup, corpus seeding and queries; they are not warm inference latency. + +baseline: mean complete-run time 212.01 s (1 repeats). +candidate: mean complete-run time 215.18 s (1 repeats). + +Query measurements through persistent MCP (subsequent queries reuse the process): + +| Build | Positive hit@4 | Positive recall@4 | False injection | Subsequent p50 / p95 ms | Mean MCP result bytes | Peak MCP working set MiB | +|---|---:|---:|---:|---:|---:|---:| +| baseline | 0.858 | 0.842 | 0.538 | 996.880 / 1111.668 | 1262.395 | 281.215 | +| candidate | 0.858 | 0.842 | 0.538 | 1011.339 / 1094.004 | 1262.395 | 281.207 | + +Measured bytes include JSON escaping; reported token estimates are retained per query but may use different accounting rules across builds. Query timing excludes the separately recorded MCP initialization and corpus seeding. diff --git a/docs/audits/2026-09-07-structured-facts/results/validation/1-baseline.json b/docs/audits/2026-09-07-structured-facts/results/validation/1-baseline.json new file mode 100644 index 0000000..357c685 --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/results/validation/1-baseline.json @@ -0,0 +1,1495 @@ +{ + "generated_at": "2026-09-07T13:24:25.5097752Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-structured-facts\\validation-frozen.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "What is the Lumen staging gateway port?", + "ranked": [ + "stage-port", + "prod-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MEGJK4S1X6SB7DJ2QVA8", + "id": "01M1Y0MGPVR4X1JVGA6FB009SN", + "kind": "memory", + "score": 0.999786913394928, + "summary": "project:fact - Lumen staging gateway port is 7101." + }, + { + "expansion_handle": "memory:01M1Y0MEGYV2GFB7884HENJBSW", + "id": "01M1Y0MGPVCSZRG5YVWRB204WM", + "kind": "memory", + "score": 0.9992988109588624, + "summary": "project:fact - Lumen production gateway port is 8101." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2161.8268, + "first_query": true, + "server_startup_ms": 71.7619, + "model_text_bytes": 623, + "mcp_result_bytes": 722, + "wire_bytes": 757, + "reported_used_tokens": 722, + "working_set_bytes": 634105856, + "peak_working_set_bytes": 685051904 + }, + { + "query": "What is the Lumen production gateway port?", + "ranked": [ + "prod-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MEGYV2GFB7884HENJBSW", + "id": "01M1Y0MH1T69GBTR3AG98950NW", + "kind": "memory", + "score": 0.9999414682388306, + "summary": "project:fact - Lumen production gateway port is 8101." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 337.3809, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 543, + "reported_used_tokens": 508, + "working_set_bytes": 634716160, + "peak_working_set_bytes": 685051904 + }, + { + "query": "What are the Lumen staging gateway retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MEHXNXNPD1S5946679C4", + "id": "01M1Y0MHC966FVCR51XAG676YR", + "kind": "memory", + "score": 0.9987403750419616, + "summary": "project:fact - Lumen staging gateway retries are 3." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 337.5271, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 425, + "mcp_result_bytes": 506, + "wire_bytes": 541, + "reported_used_tokens": 506, + "working_set_bytes": 635052032, + "peak_working_set_bytes": 685051904 + }, + { + "query": "What is `cache.max_entries` for Lumen?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MEKQPR684WMSD9WVSQY5", + "id": "01M1Y0MHQ0B6KT8CZFBRFSVSQM", + "kind": "memory", + "score": 0.9999709129333496, + "summary": "project:fact - Lumen cache.max_entries = 200." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 354.614, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 419, + "mcp_result_bytes": 500, + "wire_bytes": 535, + "reported_used_tokens": 500, + "working_set_bytes": 637194240, + "peak_working_set_bytes": 685051904 + }, + { + "query": "Which database stores local state for Lumen?", + "ranked": [ + "runtime", + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MEKD5WDR6ZE7AMP5XVTC", + "id": "01M1Y0MJ26THJ5ANP1EDZ7443Y", + "kind": "memory", + "score": 0.9999797344207764, + "summary": "project:fact - Lumen stores its local state in SQLite using WAL mode." + }, + { + "expansion_handle": "memory:01M1Y0MEJ6M1BYAK8ZKDPT7CKY", + "id": "01M1Y0MJ26THRF8AC3H387NEKW", + "kind": "memory", + "score": 0.9386039972305298, + "summary": "project:fact - Lumen production database port is 5432." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 342.1549, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 644, + "mcp_result_bytes": 743, + "wire_bytes": 778, + "reported_used_tokens": 743, + "working_set_bytes": 637284352, + "peak_working_set_bytes": 685051904 + }, + { + "query": "What are the Lumen staging gateway port and password?", + "ranked": [ + "no-password", + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MEJGWQH0AB6WS5RZJQWW", + "id": "01M1Y0MJCXWD9N16ADRGQ3Y513", + "kind": "memory", + "score": 0.9715816974639891, + "summary": "project:fact - No password is required for the Lumen production gateway." + }, + { + "expansion_handle": "memory:01M1Y0MEJ6M1BYAK8ZKDPT7CKY", + "id": "01M1Y0MJCYDP8NY0QCYJ2RAND4", + "kind": "memory", + "score": 0.5804274678230286, + "summary": "project:fact - Lumen production database port is 5432." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 348.96139999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 647, + "mcp_result_bytes": 746, + "wire_bytes": 781, + "reported_used_tokens": 746, + "working_set_bytes": 637353984, + "peak_working_set_bytes": 685051904 + }, + { + "query": "What are the Lumen staging gateway port and retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MEHXNXNPD1S5946679C4", + "id": "01M1Y0MJR6HQVBVEGR679SJ3TJ", + "kind": "memory", + "score": 0.9976552724838256, + "summary": "project:fact - Lumen staging gateway retries are 3." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 364.68899999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 425, + "mcp_result_bytes": 506, + "wire_bytes": 541, + "reported_used_tokens": 506, + "working_set_bytes": 637411328, + "peak_working_set_bytes": 685051904 + }, + { + "query": "What is the Lumen staging gateway timeout?", + "ranked": [ + "timeout-b", + "timeout-a" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MEHJAX6CJJQ9VZK51JTG", + "id": "01M1Y0MK3EPE6M3YAA7MJJ8BNC", + "kind": "memory", + "score": 0.9999746084213256, + "summary": "project:fact - Lumen staging gateway timeout is 45 seconds. The deployment checklist records a different current value." + }, + { + "expansion_handle": "memory:01M1Y0MEH7N0AX26SH1A524DK0", + "id": "01M1Y0MK3ERD3S8200QEH4K9PP", + "kind": "memory", + "score": 0.9999712705612184, + "summary": "project:fact - Lumen staging gateway timeout is 30 seconds. Operators record this in the request settings." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 365.25010000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 746, + "mcp_result_bytes": 845, + "wire_bytes": 880, + "reported_used_tokens": 845, + "working_set_bytes": 637419520, + "peak_working_set_bytes": 685051904 + }, + { + "query": "What password does the Lumen production gateway require?", + "ranked": [ + "no-password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MEJGWQH0AB6WS5RZJQWW", + "id": "01M1Y0MKF9FEXG3ZQ7Y3CQ0C0V", + "kind": "memory", + "score": 0.9999558925628662, + "summary": "project:fact - No password is required for the Lumen production gateway." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 372.4498, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 446, + "mcp_result_bytes": 527, + "wire_bytes": 563, + "reported_used_tokens": 527, + "working_set_bytes": 642215936, + "peak_working_set_bytes": 685051904 + }, + { + "query": "What is the Lumen staging database port?", + "ranked": [ + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MEJ6M1BYAK8ZKDPT7CKY", + "id": "01M1Y0MKT7FMJFTQEJQPXFEY9S", + "kind": "memory", + "score": 0.9998140931129456, + "summary": "project:fact - Lumen production database port is 5432." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 343.56579999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 428, + "mcp_result_bytes": 509, + "wire_bytes": 545, + "reported_used_tokens": 509, + "working_set_bytes": 642269184, + "peak_working_set_bytes": 685051904 + }, + { + "query": "What is the Lumen production gateway timeout?", + "ranked": [ + "timeout-a", + "timeout-b" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MEH7N0AX26SH1A524DK0", + "id": "01M1Y0MM516QC1EWRPR39JH8W8", + "kind": "memory", + "score": 0.999944806098938, + "summary": "project:fact - Lumen staging gateway timeout is 30 seconds. Operators record this in the request settings." + }, + { + "expansion_handle": "memory:01M1Y0MEHJAX6CJJQ9VZK51JTG", + "id": "01M1Y0MM51PX9KAX76GMF1AJEN", + "kind": "memory", + "score": 0.9999274015426636, + "summary": "project:fact - Lumen staging gateway timeout is 45 seconds. The deployment checklist records a different current value." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 357.7755, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 745, + "mcp_result_bytes": 844, + "wire_bytes": 880, + "reported_used_tokens": 844, + "working_set_bytes": 642318336, + "peak_working_set_bytes": 685051904 + }, + { + "query": "What is the Unknown staging gateway port?", + "ranked": [ + "foreign-port", + "stage-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MEK45XJ7TCN3T7R9Q9KY", + "id": "01M1Y0MMG6A0PFM0PACHC980FD", + "kind": "memory", + "score": 0.7973032593727112, + "summary": "project:fact - Foreign staging gateway port is 9944." + }, + { + "expansion_handle": "memory:01M1Y0MEGJK4S1X6SB7DJ2QVA8", + "id": "01M1Y0MMG6V2F0T27GF8K30786", + "kind": "memory", + "score": 0.7143108248710632, + "summary": "project:fact - Lumen staging gateway port is 7101." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 349.24129999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 622, + "mcp_result_bytes": 721, + "wire_bytes": 757, + "reported_used_tokens": 721, + "working_set_bytes": 642322432, + "peak_working_set_bytes": 685051904 + }, + { + "query": "What is the Lumen test gateway port?", + "ranked": [ + "stage-port", + "prod-port", + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MEGJK4S1X6SB7DJ2QVA8", + "id": "01M1Y0MMV3S7K3RTACF3Q6W93P", + "kind": "memory", + "score": 0.9995205402374268, + "summary": "project:fact - Lumen staging gateway port is 7101." + }, + { + "expansion_handle": "memory:01M1Y0MEGYV2GFB7884HENJBSW", + "id": "01M1Y0MMV36P79FN0C5PB5YTR8", + "kind": "memory", + "score": 0.9990516304969788, + "summary": "project:fact - Lumen production gateway port is 8101." + }, + { + "expansion_handle": "memory:01M1Y0MEJ6M1BYAK8ZKDPT7CKY", + "id": "01M1Y0MMV390V4A7RYGPVVD00G", + "kind": "memory", + "score": 0.9248796105384828, + "summary": "project:fact - Lumen production database port is 5432." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 353.62510000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 824, + "mcp_result_bytes": 941, + "wire_bytes": 977, + "reported_used_tokens": 941, + "working_set_bytes": 642404352, + "peak_working_set_bytes": 685051904 + }, + { + "query": "What are the Lumen production gateway retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MEHXNXNPD1S5946679C4", + "id": "01M1Y0MN64G8N83N3HG1RTGG5M", + "kind": "memory", + "score": 0.9977250695228576, + "summary": "project:fact - Lumen staging gateway retries are 3." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 348.50989999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 425, + "mcp_result_bytes": 506, + "wire_bytes": 542, + "reported_used_tokens": 506, + "working_set_bytes": 642494464, + "peak_working_set_bytes": 685051904 + }, + { + "query": "What is the Lumen staging worker timeout?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 386.7882, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 642502656, + "peak_working_set_bytes": 685051904 + } + ], + "id": "lumen-structured-scope", + "dimension": "retrieval", + "tier": "hard", + "score": 0.5666666666666667, + "skipped": false, + "detail": "positive-recall@4=0.83 mrr=0.89 stale-hit=n/a resolution=n/a false-injection=0.833 (n=6) positive-n=9 negative-n=6 (15 queries)" + }, + { + "observations": [ + { + "query": "What is the Harbor staging gateway port?", + "ranked": [ + "stage-port", + "prod-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MQ2ZA5KDDPFZ23RHB8XH", + "id": "01M1Y0MSFZEV4KKR75J9TC7M9B", + "kind": "memory", + "score": 0.9975167512893676, + "summary": "project:fact - Harbor staging gateway port is 7102." + }, + { + "expansion_handle": "memory:01M1Y0MQ3BH95XDSR9MGVK404P", + "id": "01M1Y0MSFZPF9TXAND4ESWZX5M", + "kind": "memory", + "score": 0.9971925616264344, + "summary": "project:fact - Harbor production gateway port is 8102." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2386.2785, + "first_query": true, + "server_startup_ms": 74.53899999999999, + "model_text_bytes": 626, + "mcp_result_bytes": 725, + "wire_bytes": 760, + "reported_used_tokens": 725, + "working_set_bytes": 635764736, + "peak_working_set_bytes": 685244416 + }, + { + "query": "What is the Harbor production gateway port?", + "ranked": [ + "prod-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MQ3BH95XDSR9MGVK404P", + "id": "01M1Y0MSVTPDQAD2S4G5VCEW01", + "kind": "memory", + "score": 0.9997510313987732, + "summary": "project:fact - Harbor production gateway port is 8102." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 349.6612, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 428, + "mcp_result_bytes": 509, + "wire_bytes": 544, + "reported_used_tokens": 509, + "working_set_bytes": 636194816, + "peak_working_set_bytes": 685244416 + }, + { + "query": "What are the Harbor staging gateway retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MQ4B8Q9ZWPZDYB23MC98", + "id": "01M1Y0MT6CT82P2X1DPNQYP60Q", + "kind": "memory", + "score": 0.9980675578117372, + "summary": "project:fact - Harbor staging gateway retries are 3." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 344.2382, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 426, + "mcp_result_bytes": 507, + "wire_bytes": 542, + "reported_used_tokens": 507, + "working_set_bytes": 636461056, + "peak_working_set_bytes": 685244416 + }, + { + "query": "What is `cache.max_entries` for Harbor?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MQ620SFCWY0FESRF8WB8", + "id": "01M1Y0MTHHKEJYTGCBRGP6Z61G", + "kind": "memory", + "score": 0.9999717473983764, + "summary": "project:fact - Harbor cache.max_entries = 200." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 357.80109999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 420, + "mcp_result_bytes": 501, + "wire_bytes": 536, + "reported_used_tokens": 501, + "working_set_bytes": 638603264, + "peak_working_set_bytes": 685244416 + }, + { + "query": "Which database stores local state for Harbor?", + "ranked": [ + "runtime", + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MQ5RHQW2THGRF9S0K19M", + "id": "01M1Y0MTWCVRC1TRRVF6BWJ8EP", + "kind": "memory", + "score": 0.9999715089797974, + "summary": "project:fact - Harbor stores its local state in SQLite using WAL mode." + }, + { + "expansion_handle": "memory:01M1Y0MQ4MQC2ZF45HFNG58TGK", + "id": "01M1Y0MTWCQS6Q1J7TQCJ9WT3H", + "kind": "memory", + "score": 0.8901878595352173, + "summary": "project:fact - Harbor production database port is 5432." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 338.3875, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 646, + "mcp_result_bytes": 745, + "wire_bytes": 780, + "reported_used_tokens": 745, + "working_set_bytes": 638771200, + "peak_working_set_bytes": 685244416 + }, + { + "query": "What are the Harbor staging gateway port and password?", + "ranked": [ + "no-password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MQ4XRVE2RFW6DY859NF6", + "id": "01M1Y0MV73DQETVFJ9Z6N5E34M", + "kind": "memory", + "score": 0.9481525421142578, + "summary": "project:fact - No password is required for the Harbor production gateway." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 357.32779999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 447, + "mcp_result_bytes": 528, + "wire_bytes": 563, + "reported_used_tokens": 528, + "working_set_bytes": 638849024, + "peak_working_set_bytes": 685244416 + }, + { + "query": "What are the Harbor staging gateway port and retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MQ4B8Q9ZWPZDYB23MC98", + "id": "01M1Y0MVJ5XZWF4NXXEBPSR00N", + "kind": "memory", + "score": 0.9916656613349916, + "summary": "project:fact - Harbor staging gateway retries are 3." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.4323, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 426, + "mcp_result_bytes": 507, + "wire_bytes": 542, + "reported_used_tokens": 507, + "working_set_bytes": 639021056, + "peak_working_set_bytes": 685244416 + }, + { + "query": "What is the Harbor staging gateway timeout?", + "ranked": [ + "timeout-b", + "timeout-a" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MQ3Z9TDZVZ7WQ6YXA2BX", + "id": "01M1Y0MVXN94HN1NWNSH1DCRBE", + "kind": "memory", + "score": 0.9999679327011108, + "summary": "project:fact - Harbor staging gateway timeout is 45 seconds. The deployment checklist records a different current value." + }, + { + "expansion_handle": "memory:01M1Y0MQ3MV3S4P9XRQD4EMT0V", + "id": "01M1Y0MVXN14YVFSR810JW2E5P", + "kind": "memory", + "score": 0.9999622106552124, + "summary": "project:fact - Harbor staging gateway timeout is 30 seconds. Operators record this in the request settings." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 384.5116, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 748, + "mcp_result_bytes": 847, + "wire_bytes": 882, + "reported_used_tokens": 847, + "working_set_bytes": 639053824, + "peak_working_set_bytes": 685244416 + }, + { + "query": "What password does the Harbor production gateway require?", + "ranked": [ + "no-password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MQ4XRVE2RFW6DY859NF6", + "id": "01M1Y0MW9CMC5WQCBBAZ4MDGZ3", + "kind": "memory", + "score": 0.999948024749756, + "summary": "project:fact - No password is required for the Harbor production gateway." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 348.7006, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 447, + "mcp_result_bytes": 528, + "wire_bytes": 564, + "reported_used_tokens": 528, + "working_set_bytes": 643731456, + "peak_working_set_bytes": 685244416 + }, + { + "query": "What is the Harbor staging database port?", + "ranked": [ + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MQ4MQC2ZF45HFNG58TGK", + "id": "01M1Y0MWM6G4Z1YG6GWGZRETGK", + "kind": "memory", + "score": 0.9995362758636476, + "summary": "project:fact - Harbor production database port is 5432." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 343.74490000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 429, + "mcp_result_bytes": 510, + "wire_bytes": 546, + "reported_used_tokens": 510, + "working_set_bytes": 643792896, + "peak_working_set_bytes": 685244416 + }, + { + "query": "What is the Harbor production gateway timeout?", + "ranked": [ + "timeout-a", + "timeout-b" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MQ3MV3S4P9XRQD4EMT0V", + "id": "01M1Y0MWYZJZ5061WCB7WVHQAZ", + "kind": "memory", + "score": 0.9995842576026917, + "summary": "project:fact - Harbor staging gateway timeout is 30 seconds. Operators record this in the request settings." + }, + { + "expansion_handle": "memory:01M1Y0MQ3Z9TDZVZ7WQ6YXA2BX", + "id": "01M1Y0MWYZZB7C8SN5VWY7E6M8", + "kind": "memory", + "score": 0.9994773268699646, + "summary": "project:fact - Harbor staging gateway timeout is 45 seconds. The deployment checklist records a different current value." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 344.4561, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 748, + "mcp_result_bytes": 847, + "wire_bytes": 883, + "reported_used_tokens": 847, + "working_set_bytes": 643792896, + "peak_working_set_bytes": 685244416 + }, + { + "query": "What is the Unknown staging gateway port?", + "ranked": [ + "foreign-port", + "stage-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MQ5GBXMTM8YQZN2RGJSC", + "id": "01M1Y0MX9QAN518VAJ4SAC5PEX", + "kind": "memory", + "score": 0.873483419418335, + "summary": "project:fact - Foreign staging gateway port is 9944." + }, + { + "expansion_handle": "memory:01M1Y0MQ2ZA5KDDPFZ23RHB8XH", + "id": "01M1Y0MX9QT3QEFDP1XPVF5106", + "kind": "memory", + "score": 0.7721561789512634, + "summary": "project:fact - Harbor staging gateway port is 7102." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 348.8424, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 622, + "mcp_result_bytes": 721, + "wire_bytes": 757, + "reported_used_tokens": 721, + "working_set_bytes": 643825664, + "peak_working_set_bytes": 685244416 + }, + { + "query": "What is the Harbor test gateway port?", + "ranked": [ + "prod-port", + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MQ3BH95XDSR9MGVK404P", + "id": "01M1Y0MXMVCZDQP71FZNZ2FKTS", + "kind": "memory", + "score": 0.9944294691085817, + "summary": "project:fact - Harbor production gateway port is 8102." + }, + { + "expansion_handle": "memory:01M1Y0MQ4MQC2ZF45HFNG58TGK", + "id": "01M1Y0MXMVGG6M5BKKRSW4TP0X", + "kind": "memory", + "score": 0.5744403600692749, + "summary": "project:fact - Harbor production database port is 5432." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 350.05920000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 629, + "mcp_result_bytes": 728, + "wire_bytes": 764, + "reported_used_tokens": 728, + "working_set_bytes": 643825664, + "peak_working_set_bytes": 685244416 + }, + { + "query": "What are the Harbor production gateway retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MQ4B8Q9ZWPZDYB23MC98", + "id": "01M1Y0MXZMDDBH4WBVZANPBRGN", + "kind": "memory", + "score": 0.9974480867385864, + "summary": "project:fact - Harbor staging gateway retries are 3." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 347.078, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 426, + "mcp_result_bytes": 507, + "wire_bytes": 543, + "reported_used_tokens": 507, + "working_set_bytes": 643907584, + "peak_working_set_bytes": 685244416 + }, + { + "query": "What is the Harbor staging worker timeout?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 357.9303, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643928064, + "peak_working_set_bytes": 685244416 + } + ], + "id": "harbor-structured-scope", + "dimension": "retrieval", + "tier": "hard", + "score": 0.5666666666666667, + "skipped": false, + "detail": "positive-recall@4=0.83 mrr=0.89 stale-hit=n/a resolution=n/a false-injection=0.833 (n=6) positive-n=9 negative-n=6 (15 queries)" + }, + { + "observations": [ + { + "query": "What is the Sable staging gateway port?", + "ranked": [ + "stage-port", + "prod-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MZYEKSR2C8636N31FRH1", + "id": "01M1Y0N290YR56CSYRD0PVM0AT", + "kind": "memory", + "score": 0.9981032609939576, + "summary": "project:fact - Sable staging gateway port is 7103." + }, + { + "expansion_handle": "memory:01M1Y0MZYTT5EG3BN8Z2VDZYF1", + "id": "01M1Y0N290C2VVP864WDV7X43X", + "kind": "memory", + "score": 0.9930086135864258, + "summary": "project:fact - Sable production gateway port is 8103." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2303.7169999999996, + "first_query": true, + "server_startup_ms": 71.23870000000001, + "model_text_bytes": 624, + "mcp_result_bytes": 723, + "wire_bytes": 758, + "reported_used_tokens": 723, + "working_set_bytes": 637116416, + "peak_working_set_bytes": 685043712 + }, + { + "query": "What is the Sable production gateway port?", + "ranked": [ + "prod-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MZYTT5EG3BN8Z2VDZYF1", + "id": "01M1Y0N2M0Q2BD5KCZXBN61XH4", + "kind": "memory", + "score": 0.9996471405029296, + "summary": "project:fact - Sable production gateway port is 8103." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 338.7989, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 543, + "reported_used_tokens": 508, + "working_set_bytes": 637607936, + "peak_working_set_bytes": 685043712 + }, + { + "query": "What are the Sable staging gateway retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MZZSGHP9RQJ7SJBET0V1", + "id": "01M1Y0N2YK6ZYT1AX62CHKKH1A", + "kind": "memory", + "score": 0.9971465468406676, + "summary": "project:fact - Sable staging gateway retries are 3." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 341.02500000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 425, + "mcp_result_bytes": 506, + "wire_bytes": 541, + "reported_used_tokens": 506, + "working_set_bytes": 637861888, + "peak_working_set_bytes": 685043712 + }, + { + "query": "What is `cache.max_entries` for Sable?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0N01ET7DABQCDCY7VFGZJ", + "id": "01M1Y0N39N8APG91JC4GFGRV5F", + "kind": "memory", + "score": 0.9999657869338988, + "summary": "project:fact - Sable cache.max_entries = 200." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 357.88779999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 419, + "mcp_result_bytes": 500, + "wire_bytes": 535, + "reported_used_tokens": 500, + "working_set_bytes": 639881216, + "peak_working_set_bytes": 685043712 + }, + { + "query": "Which database stores local state for Sable?", + "ranked": [ + "runtime", + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0N014EG1WJAVZ27PEM84S", + "id": "01M1Y0N3MKH0N10XVFF43T1FH6", + "kind": "memory", + "score": 0.9999470710754396, + "summary": "project:fact - Sable stores its local state in SQLite using WAL mode." + }, + { + "expansion_handle": "memory:01M1Y0N0013E35ENMXVAVQ9QRT", + "id": "01M1Y0N3MK1SGAKP0JP0XTQ0RZ", + "kind": "memory", + "score": 0.7779185175895691, + "summary": "project:fact - Sable production database port is 5432." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 340.3432, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 644, + "mcp_result_bytes": 743, + "wire_bytes": 778, + "reported_used_tokens": 743, + "working_set_bytes": 639963136, + "peak_working_set_bytes": 685043712 + }, + { + "query": "What are the Sable staging gateway port and password?", + "ranked": [ + "no-password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0N00AA6WFV64M4HZS0AHQ", + "id": "01M1Y0N3ZFZPF2P8MBXW2GQ1BN", + "kind": "memory", + "score": 0.9167110919952391, + "summary": "project:fact - No password is required for the Sable production gateway." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.231, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 446, + "mcp_result_bytes": 527, + "wire_bytes": 562, + "reported_used_tokens": 527, + "working_set_bytes": 640012288, + "peak_working_set_bytes": 685043712 + }, + { + "query": "What are the Sable staging gateway port and retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MZZSGHP9RQJ7SJBET0V1", + "id": "01M1Y0N4AC456FTWYRTQQCB4M5", + "kind": "memory", + "score": 0.9917003512382508, + "summary": "project:fact - Sable staging gateway retries are 3." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 355.7128, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 425, + "mcp_result_bytes": 506, + "wire_bytes": 541, + "reported_used_tokens": 506, + "working_set_bytes": 640225280, + "peak_working_set_bytes": 685043712 + }, + { + "query": "What is the Sable staging gateway timeout?", + "ranked": [ + "timeout-b", + "timeout-a" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MZZE36BDMZG2RZKCCQYG", + "id": "01M1Y0N4NXSRE844ZBZNQ7032N", + "kind": "memory", + "score": 0.9999639987945556, + "summary": "project:fact - Sable staging gateway timeout is 45 seconds. The deployment checklist records a different current value." + }, + { + "expansion_handle": "memory:01M1Y0MZZ3G0FF4JCM4923V26S", + "id": "01M1Y0N4NXQG4R3AQX9JQNYJSR", + "kind": "memory", + "score": 0.9999442100524902, + "summary": "project:fact - Sable staging gateway timeout is 30 seconds. Operators record this in the request settings." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 378.49899999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 746, + "mcp_result_bytes": 845, + "wire_bytes": 880, + "reported_used_tokens": 845, + "working_set_bytes": 640249856, + "peak_working_set_bytes": 685043712 + }, + { + "query": "What password does the Sable production gateway require?", + "ranked": [ + "no-password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0N00AA6WFV64M4HZS0AHQ", + "id": "01M1Y0N51ARDG72QKQ6ES9ANXQ", + "kind": "memory", + "score": 0.9999281167984008, + "summary": "project:fact - No password is required for the Sable production gateway." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 349.7147, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 446, + "mcp_result_bytes": 527, + "wire_bytes": 563, + "reported_used_tokens": 527, + "working_set_bytes": 645111808, + "peak_working_set_bytes": 685043712 + }, + { + "query": "What is the Sable staging database port?", + "ranked": [ + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0N0013E35ENMXVAVQ9QRT", + "id": "01M1Y0N5C76Z71N14QBAT4D4E6", + "kind": "memory", + "score": 0.9992688298225404, + "summary": "project:fact - Sable production database port is 5432." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 347.5526, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 428, + "mcp_result_bytes": 509, + "wire_bytes": 545, + "reported_used_tokens": 509, + "working_set_bytes": 645169152, + "peak_working_set_bytes": 685043712 + }, + { + "query": "What is the Sable production gateway timeout?", + "ranked": [ + "timeout-b", + "timeout-a" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MZZE36BDMZG2RZKCCQYG", + "id": "01M1Y0N5Q55H95XWKGSPYHPJJA", + "kind": "memory", + "score": 0.9997990727424622, + "summary": "project:fact - Sable staging gateway timeout is 45 seconds. The deployment checklist records a different current value." + }, + { + "expansion_handle": "memory:01M1Y0MZZ3G0FF4JCM4923V26S", + "id": "01M1Y0N5Q5EZVQRYJC05ZPZM7J", + "kind": "memory", + "score": 0.9994401335716248, + "summary": "project:fact - Sable staging gateway timeout is 30 seconds. Operators record this in the request settings." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 349.3759, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 746, + "mcp_result_bytes": 845, + "wire_bytes": 881, + "reported_used_tokens": 845, + "working_set_bytes": 645210112, + "peak_working_set_bytes": 685043712 + }, + { + "query": "What is the Unknown staging gateway port?", + "ranked": [ + "stage-port", + "foreign-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MZYEKSR2C8636N31FRH1", + "id": "01M1Y0N61ZA4Z3FD765ZJY62ZN", + "kind": "memory", + "score": 0.8858267068862915, + "summary": "project:fact - Sable staging gateway port is 7103." + }, + { + "expansion_handle": "memory:01M1Y0N00W5ZMG5YZY06YGZ0Q9", + "id": "01M1Y0N61Z1NWX36X5CB29JJ2Z", + "kind": "memory", + "score": 0.8669298887252808, + "summary": "project:fact - Foreign staging gateway port is 9944." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 345.1217, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 622, + "mcp_result_bytes": 721, + "wire_bytes": 757, + "reported_used_tokens": 721, + "working_set_bytes": 645222400, + "peak_working_set_bytes": 685043712 + }, + { + "query": "What is the Sable test gateway port?", + "ranked": [ + "prod-port", + "stage-port", + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MZYTT5EG3BN8Z2VDZYF1", + "id": "01M1Y0N6D1XYQQWMARK6D2T9VT", + "kind": "memory", + "score": 0.9929980039596558, + "summary": "project:fact - Sable production gateway port is 8103." + }, + { + "expansion_handle": "memory:01M1Y0MZYEKSR2C8636N31FRH1", + "id": "01M1Y0N6D1TVDJAZT1C35A34ZN", + "kind": "memory", + "score": 0.9868773221969604, + "summary": "project:fact - Sable staging gateway port is 7103." + }, + { + "expansion_handle": "memory:01M1Y0N0013E35ENMXVAVQ9QRT", + "id": "01M1Y0N6D1YE9XRE71AQ3KDEY5", + "kind": "memory", + "score": 0.6837578415870667, + "summary": "project:fact - Sable production database port is 5432." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 364.24440000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 824, + "mcp_result_bytes": 941, + "wire_bytes": 977, + "reported_used_tokens": 941, + "working_set_bytes": 645234688, + "peak_working_set_bytes": 685043712 + }, + { + "query": "What are the Sable production gateway retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MZZSGHP9RQJ7SJBET0V1", + "id": "01M1Y0N6R5YFHDZZF3HJRCPMSW", + "kind": "memory", + "score": 0.992603600025177, + "summary": "project:fact - Sable staging gateway retries are 3." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 347.4506, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 424, + "mcp_result_bytes": 505, + "wire_bytes": 541, + "reported_used_tokens": 505, + "working_set_bytes": 645234688, + "peak_working_set_bytes": 685043712 + }, + { + "query": "What is the Sable staging worker timeout?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 355.12919999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645271552, + "peak_working_set_bytes": 685043712 + } + ], + "id": "sable-structured-scope", + "dimension": "retrieval", + "tier": "hard", + "score": 0.5666666666666667, + "skipped": false, + "detail": "positive-recall@4=0.83 mrr=0.89 stale-hit=n/a resolution=n/a false-injection=0.833 (n=6) positive-n=9 negative-n=6 (15 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 1.7, + 3 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 0.5666666666666667, + "n": 3, + "ci95": 0.0 + } + }, + "overall_index": 0.5666666666666667, + "scenario_weighted_index": 0.5666666666666667 +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-structured-facts/results/validation/1-baseline.stderr.log b/docs/audits/2026-09-07-structured-facts/results/validation/1-baseline.stderr.log new file mode 100644 index 0000000..2fc800d --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/results/validation/1-baseline.stderr.log @@ -0,0 +1,8 @@ +brainbench: 3 scenario(s) to run + [1/3] lumen-structured-scope | dim=retrieval tier=hard ... + -> score=0.57 | positive-recall@4=0.83 mrr=0.89 stale-hit=n/a resolution=n/a false-injection=0.833 (n=6) positive-n=9 negative-n=6 (15 queries) + [2/3] harbor-structured-scope | dim=retrieval tier=hard ... + -> score=0.57 | positive-recall@4=0.83 mrr=0.89 stale-hit=n/a resolution=n/a false-injection=0.833 (n=6) positive-n=9 negative-n=6 (15 queries) + [3/3] sable-structured-scope | dim=retrieval tier=hard ... + -> score=0.57 | positive-recall@4=0.83 mrr=0.89 stale-hit=n/a resolution=n/a false-injection=0.833 (n=6) positive-n=9 negative-n=6 (15 queries) +kbench brainbench: report saved -> E:\tmp\kimetsu-brain-hardening\bench\local\runs\brainbench\2026-09-07T13-24-25.5101223Z.json diff --git a/docs/audits/2026-09-07-structured-facts/results/validation/1-baseline.stdout.log b/docs/audits/2026-09-07-structured-facts/results/validation/1-baseline.stdout.log new file mode 100644 index 0000000..1ae4684 --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/results/validation/1-baseline.stdout.log @@ -0,0 +1,1495 @@ +{ + "generated_at": "2026-09-07T13:24:25.5097752Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-structured-facts\\validation-frozen.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "What is the Lumen staging gateway port?", + "ranked": [ + "stage-port", + "prod-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MEGJK4S1X6SB7DJ2QVA8", + "id": "01M1Y0MGPVR4X1JVGA6FB009SN", + "kind": "memory", + "score": 0.999786913394928, + "summary": "project:fact - Lumen staging gateway port is 7101." + }, + { + "expansion_handle": "memory:01M1Y0MEGYV2GFB7884HENJBSW", + "id": "01M1Y0MGPVCSZRG5YVWRB204WM", + "kind": "memory", + "score": 0.9992988109588624, + "summary": "project:fact - Lumen production gateway port is 8101." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2161.8268, + "first_query": true, + "server_startup_ms": 71.7619, + "model_text_bytes": 623, + "mcp_result_bytes": 722, + "wire_bytes": 757, + "reported_used_tokens": 722, + "working_set_bytes": 634105856, + "peak_working_set_bytes": 685051904 + }, + { + "query": "What is the Lumen production gateway port?", + "ranked": [ + "prod-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MEGYV2GFB7884HENJBSW", + "id": "01M1Y0MH1T69GBTR3AG98950NW", + "kind": "memory", + "score": 0.9999414682388306, + "summary": "project:fact - Lumen production gateway port is 8101." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 337.3809, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 543, + "reported_used_tokens": 508, + "working_set_bytes": 634716160, + "peak_working_set_bytes": 685051904 + }, + { + "query": "What are the Lumen staging gateway retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MEHXNXNPD1S5946679C4", + "id": "01M1Y0MHC966FVCR51XAG676YR", + "kind": "memory", + "score": 0.9987403750419616, + "summary": "project:fact - Lumen staging gateway retries are 3." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 337.5271, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 425, + "mcp_result_bytes": 506, + "wire_bytes": 541, + "reported_used_tokens": 506, + "working_set_bytes": 635052032, + "peak_working_set_bytes": 685051904 + }, + { + "query": "What is `cache.max_entries` for Lumen?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MEKQPR684WMSD9WVSQY5", + "id": "01M1Y0MHQ0B6KT8CZFBRFSVSQM", + "kind": "memory", + "score": 0.9999709129333496, + "summary": "project:fact - Lumen cache.max_entries = 200." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 354.614, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 419, + "mcp_result_bytes": 500, + "wire_bytes": 535, + "reported_used_tokens": 500, + "working_set_bytes": 637194240, + "peak_working_set_bytes": 685051904 + }, + { + "query": "Which database stores local state for Lumen?", + "ranked": [ + "runtime", + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MEKD5WDR6ZE7AMP5XVTC", + "id": "01M1Y0MJ26THJ5ANP1EDZ7443Y", + "kind": "memory", + "score": 0.9999797344207764, + "summary": "project:fact - Lumen stores its local state in SQLite using WAL mode." + }, + { + "expansion_handle": "memory:01M1Y0MEJ6M1BYAK8ZKDPT7CKY", + "id": "01M1Y0MJ26THRF8AC3H387NEKW", + "kind": "memory", + "score": 0.9386039972305298, + "summary": "project:fact - Lumen production database port is 5432." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 342.1549, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 644, + "mcp_result_bytes": 743, + "wire_bytes": 778, + "reported_used_tokens": 743, + "working_set_bytes": 637284352, + "peak_working_set_bytes": 685051904 + }, + { + "query": "What are the Lumen staging gateway port and password?", + "ranked": [ + "no-password", + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MEJGWQH0AB6WS5RZJQWW", + "id": "01M1Y0MJCXWD9N16ADRGQ3Y513", + "kind": "memory", + "score": 0.9715816974639891, + "summary": "project:fact - No password is required for the Lumen production gateway." + }, + { + "expansion_handle": "memory:01M1Y0MEJ6M1BYAK8ZKDPT7CKY", + "id": "01M1Y0MJCYDP8NY0QCYJ2RAND4", + "kind": "memory", + "score": 0.5804274678230286, + "summary": "project:fact - Lumen production database port is 5432." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 348.96139999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 647, + "mcp_result_bytes": 746, + "wire_bytes": 781, + "reported_used_tokens": 746, + "working_set_bytes": 637353984, + "peak_working_set_bytes": 685051904 + }, + { + "query": "What are the Lumen staging gateway port and retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MEHXNXNPD1S5946679C4", + "id": "01M1Y0MJR6HQVBVEGR679SJ3TJ", + "kind": "memory", + "score": 0.9976552724838256, + "summary": "project:fact - Lumen staging gateway retries are 3." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 364.68899999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 425, + "mcp_result_bytes": 506, + "wire_bytes": 541, + "reported_used_tokens": 506, + "working_set_bytes": 637411328, + "peak_working_set_bytes": 685051904 + }, + { + "query": "What is the Lumen staging gateway timeout?", + "ranked": [ + "timeout-b", + "timeout-a" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MEHJAX6CJJQ9VZK51JTG", + "id": "01M1Y0MK3EPE6M3YAA7MJJ8BNC", + "kind": "memory", + "score": 0.9999746084213256, + "summary": "project:fact - Lumen staging gateway timeout is 45 seconds. The deployment checklist records a different current value." + }, + { + "expansion_handle": "memory:01M1Y0MEH7N0AX26SH1A524DK0", + "id": "01M1Y0MK3ERD3S8200QEH4K9PP", + "kind": "memory", + "score": 0.9999712705612184, + "summary": "project:fact - Lumen staging gateway timeout is 30 seconds. Operators record this in the request settings." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 365.25010000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 746, + "mcp_result_bytes": 845, + "wire_bytes": 880, + "reported_used_tokens": 845, + "working_set_bytes": 637419520, + "peak_working_set_bytes": 685051904 + }, + { + "query": "What password does the Lumen production gateway require?", + "ranked": [ + "no-password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MEJGWQH0AB6WS5RZJQWW", + "id": "01M1Y0MKF9FEXG3ZQ7Y3CQ0C0V", + "kind": "memory", + "score": 0.9999558925628662, + "summary": "project:fact - No password is required for the Lumen production gateway." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 372.4498, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 446, + "mcp_result_bytes": 527, + "wire_bytes": 563, + "reported_used_tokens": 527, + "working_set_bytes": 642215936, + "peak_working_set_bytes": 685051904 + }, + { + "query": "What is the Lumen staging database port?", + "ranked": [ + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MEJ6M1BYAK8ZKDPT7CKY", + "id": "01M1Y0MKT7FMJFTQEJQPXFEY9S", + "kind": "memory", + "score": 0.9998140931129456, + "summary": "project:fact - Lumen production database port is 5432." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 343.56579999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 428, + "mcp_result_bytes": 509, + "wire_bytes": 545, + "reported_used_tokens": 509, + "working_set_bytes": 642269184, + "peak_working_set_bytes": 685051904 + }, + { + "query": "What is the Lumen production gateway timeout?", + "ranked": [ + "timeout-a", + "timeout-b" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MEH7N0AX26SH1A524DK0", + "id": "01M1Y0MM516QC1EWRPR39JH8W8", + "kind": "memory", + "score": 0.999944806098938, + "summary": "project:fact - Lumen staging gateway timeout is 30 seconds. Operators record this in the request settings." + }, + { + "expansion_handle": "memory:01M1Y0MEHJAX6CJJQ9VZK51JTG", + "id": "01M1Y0MM51PX9KAX76GMF1AJEN", + "kind": "memory", + "score": 0.9999274015426636, + "summary": "project:fact - Lumen staging gateway timeout is 45 seconds. The deployment checklist records a different current value." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 357.7755, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 745, + "mcp_result_bytes": 844, + "wire_bytes": 880, + "reported_used_tokens": 844, + "working_set_bytes": 642318336, + "peak_working_set_bytes": 685051904 + }, + { + "query": "What is the Unknown staging gateway port?", + "ranked": [ + "foreign-port", + "stage-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MEK45XJ7TCN3T7R9Q9KY", + "id": "01M1Y0MMG6A0PFM0PACHC980FD", + "kind": "memory", + "score": 0.7973032593727112, + "summary": "project:fact - Foreign staging gateway port is 9944." + }, + { + "expansion_handle": "memory:01M1Y0MEGJK4S1X6SB7DJ2QVA8", + "id": "01M1Y0MMG6V2F0T27GF8K30786", + "kind": "memory", + "score": 0.7143108248710632, + "summary": "project:fact - Lumen staging gateway port is 7101." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 349.24129999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 622, + "mcp_result_bytes": 721, + "wire_bytes": 757, + "reported_used_tokens": 721, + "working_set_bytes": 642322432, + "peak_working_set_bytes": 685051904 + }, + { + "query": "What is the Lumen test gateway port?", + "ranked": [ + "stage-port", + "prod-port", + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MEGJK4S1X6SB7DJ2QVA8", + "id": "01M1Y0MMV3S7K3RTACF3Q6W93P", + "kind": "memory", + "score": 0.9995205402374268, + "summary": "project:fact - Lumen staging gateway port is 7101." + }, + { + "expansion_handle": "memory:01M1Y0MEGYV2GFB7884HENJBSW", + "id": "01M1Y0MMV36P79FN0C5PB5YTR8", + "kind": "memory", + "score": 0.9990516304969788, + "summary": "project:fact - Lumen production gateway port is 8101." + }, + { + "expansion_handle": "memory:01M1Y0MEJ6M1BYAK8ZKDPT7CKY", + "id": "01M1Y0MMV390V4A7RYGPVVD00G", + "kind": "memory", + "score": 0.9248796105384828, + "summary": "project:fact - Lumen production database port is 5432." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 353.62510000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 824, + "mcp_result_bytes": 941, + "wire_bytes": 977, + "reported_used_tokens": 941, + "working_set_bytes": 642404352, + "peak_working_set_bytes": 685051904 + }, + { + "query": "What are the Lumen production gateway retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MEHXNXNPD1S5946679C4", + "id": "01M1Y0MN64G8N83N3HG1RTGG5M", + "kind": "memory", + "score": 0.9977250695228576, + "summary": "project:fact - Lumen staging gateway retries are 3." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 348.50989999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 425, + "mcp_result_bytes": 506, + "wire_bytes": 542, + "reported_used_tokens": 506, + "working_set_bytes": 642494464, + "peak_working_set_bytes": 685051904 + }, + { + "query": "What is the Lumen staging worker timeout?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 386.7882, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 642502656, + "peak_working_set_bytes": 685051904 + } + ], + "id": "lumen-structured-scope", + "dimension": "retrieval", + "tier": "hard", + "score": 0.5666666666666667, + "skipped": false, + "detail": "positive-recall@4=0.83 mrr=0.89 stale-hit=n/a resolution=n/a false-injection=0.833 (n=6) positive-n=9 negative-n=6 (15 queries)" + }, + { + "observations": [ + { + "query": "What is the Harbor staging gateway port?", + "ranked": [ + "stage-port", + "prod-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MQ2ZA5KDDPFZ23RHB8XH", + "id": "01M1Y0MSFZEV4KKR75J9TC7M9B", + "kind": "memory", + "score": 0.9975167512893676, + "summary": "project:fact - Harbor staging gateway port is 7102." + }, + { + "expansion_handle": "memory:01M1Y0MQ3BH95XDSR9MGVK404P", + "id": "01M1Y0MSFZPF9TXAND4ESWZX5M", + "kind": "memory", + "score": 0.9971925616264344, + "summary": "project:fact - Harbor production gateway port is 8102." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2386.2785, + "first_query": true, + "server_startup_ms": 74.53899999999999, + "model_text_bytes": 626, + "mcp_result_bytes": 725, + "wire_bytes": 760, + "reported_used_tokens": 725, + "working_set_bytes": 635764736, + "peak_working_set_bytes": 685244416 + }, + { + "query": "What is the Harbor production gateway port?", + "ranked": [ + "prod-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MQ3BH95XDSR9MGVK404P", + "id": "01M1Y0MSVTPDQAD2S4G5VCEW01", + "kind": "memory", + "score": 0.9997510313987732, + "summary": "project:fact - Harbor production gateway port is 8102." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 349.6612, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 428, + "mcp_result_bytes": 509, + "wire_bytes": 544, + "reported_used_tokens": 509, + "working_set_bytes": 636194816, + "peak_working_set_bytes": 685244416 + }, + { + "query": "What are the Harbor staging gateway retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MQ4B8Q9ZWPZDYB23MC98", + "id": "01M1Y0MT6CT82P2X1DPNQYP60Q", + "kind": "memory", + "score": 0.9980675578117372, + "summary": "project:fact - Harbor staging gateway retries are 3." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 344.2382, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 426, + "mcp_result_bytes": 507, + "wire_bytes": 542, + "reported_used_tokens": 507, + "working_set_bytes": 636461056, + "peak_working_set_bytes": 685244416 + }, + { + "query": "What is `cache.max_entries` for Harbor?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MQ620SFCWY0FESRF8WB8", + "id": "01M1Y0MTHHKEJYTGCBRGP6Z61G", + "kind": "memory", + "score": 0.9999717473983764, + "summary": "project:fact - Harbor cache.max_entries = 200." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 357.80109999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 420, + "mcp_result_bytes": 501, + "wire_bytes": 536, + "reported_used_tokens": 501, + "working_set_bytes": 638603264, + "peak_working_set_bytes": 685244416 + }, + { + "query": "Which database stores local state for Harbor?", + "ranked": [ + "runtime", + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MQ5RHQW2THGRF9S0K19M", + "id": "01M1Y0MTWCVRC1TRRVF6BWJ8EP", + "kind": "memory", + "score": 0.9999715089797974, + "summary": "project:fact - Harbor stores its local state in SQLite using WAL mode." + }, + { + "expansion_handle": "memory:01M1Y0MQ4MQC2ZF45HFNG58TGK", + "id": "01M1Y0MTWCQS6Q1J7TQCJ9WT3H", + "kind": "memory", + "score": 0.8901878595352173, + "summary": "project:fact - Harbor production database port is 5432." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 338.3875, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 646, + "mcp_result_bytes": 745, + "wire_bytes": 780, + "reported_used_tokens": 745, + "working_set_bytes": 638771200, + "peak_working_set_bytes": 685244416 + }, + { + "query": "What are the Harbor staging gateway port and password?", + "ranked": [ + "no-password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MQ4XRVE2RFW6DY859NF6", + "id": "01M1Y0MV73DQETVFJ9Z6N5E34M", + "kind": "memory", + "score": 0.9481525421142578, + "summary": "project:fact - No password is required for the Harbor production gateway." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 357.32779999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 447, + "mcp_result_bytes": 528, + "wire_bytes": 563, + "reported_used_tokens": 528, + "working_set_bytes": 638849024, + "peak_working_set_bytes": 685244416 + }, + { + "query": "What are the Harbor staging gateway port and retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MQ4B8Q9ZWPZDYB23MC98", + "id": "01M1Y0MVJ5XZWF4NXXEBPSR00N", + "kind": "memory", + "score": 0.9916656613349916, + "summary": "project:fact - Harbor staging gateway retries are 3." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.4323, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 426, + "mcp_result_bytes": 507, + "wire_bytes": 542, + "reported_used_tokens": 507, + "working_set_bytes": 639021056, + "peak_working_set_bytes": 685244416 + }, + { + "query": "What is the Harbor staging gateway timeout?", + "ranked": [ + "timeout-b", + "timeout-a" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MQ3Z9TDZVZ7WQ6YXA2BX", + "id": "01M1Y0MVXN94HN1NWNSH1DCRBE", + "kind": "memory", + "score": 0.9999679327011108, + "summary": "project:fact - Harbor staging gateway timeout is 45 seconds. The deployment checklist records a different current value." + }, + { + "expansion_handle": "memory:01M1Y0MQ3MV3S4P9XRQD4EMT0V", + "id": "01M1Y0MVXN14YVFSR810JW2E5P", + "kind": "memory", + "score": 0.9999622106552124, + "summary": "project:fact - Harbor staging gateway timeout is 30 seconds. Operators record this in the request settings." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 384.5116, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 748, + "mcp_result_bytes": 847, + "wire_bytes": 882, + "reported_used_tokens": 847, + "working_set_bytes": 639053824, + "peak_working_set_bytes": 685244416 + }, + { + "query": "What password does the Harbor production gateway require?", + "ranked": [ + "no-password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MQ4XRVE2RFW6DY859NF6", + "id": "01M1Y0MW9CMC5WQCBBAZ4MDGZ3", + "kind": "memory", + "score": 0.999948024749756, + "summary": "project:fact - No password is required for the Harbor production gateway." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 348.7006, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 447, + "mcp_result_bytes": 528, + "wire_bytes": 564, + "reported_used_tokens": 528, + "working_set_bytes": 643731456, + "peak_working_set_bytes": 685244416 + }, + { + "query": "What is the Harbor staging database port?", + "ranked": [ + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MQ4MQC2ZF45HFNG58TGK", + "id": "01M1Y0MWM6G4Z1YG6GWGZRETGK", + "kind": "memory", + "score": 0.9995362758636476, + "summary": "project:fact - Harbor production database port is 5432." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 343.74490000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 429, + "mcp_result_bytes": 510, + "wire_bytes": 546, + "reported_used_tokens": 510, + "working_set_bytes": 643792896, + "peak_working_set_bytes": 685244416 + }, + { + "query": "What is the Harbor production gateway timeout?", + "ranked": [ + "timeout-a", + "timeout-b" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MQ3MV3S4P9XRQD4EMT0V", + "id": "01M1Y0MWYZJZ5061WCB7WVHQAZ", + "kind": "memory", + "score": 0.9995842576026917, + "summary": "project:fact - Harbor staging gateway timeout is 30 seconds. Operators record this in the request settings." + }, + { + "expansion_handle": "memory:01M1Y0MQ3Z9TDZVZ7WQ6YXA2BX", + "id": "01M1Y0MWYZZB7C8SN5VWY7E6M8", + "kind": "memory", + "score": 0.9994773268699646, + "summary": "project:fact - Harbor staging gateway timeout is 45 seconds. The deployment checklist records a different current value." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 344.4561, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 748, + "mcp_result_bytes": 847, + "wire_bytes": 883, + "reported_used_tokens": 847, + "working_set_bytes": 643792896, + "peak_working_set_bytes": 685244416 + }, + { + "query": "What is the Unknown staging gateway port?", + "ranked": [ + "foreign-port", + "stage-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MQ5GBXMTM8YQZN2RGJSC", + "id": "01M1Y0MX9QAN518VAJ4SAC5PEX", + "kind": "memory", + "score": 0.873483419418335, + "summary": "project:fact - Foreign staging gateway port is 9944." + }, + { + "expansion_handle": "memory:01M1Y0MQ2ZA5KDDPFZ23RHB8XH", + "id": "01M1Y0MX9QT3QEFDP1XPVF5106", + "kind": "memory", + "score": 0.7721561789512634, + "summary": "project:fact - Harbor staging gateway port is 7102." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 348.8424, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 622, + "mcp_result_bytes": 721, + "wire_bytes": 757, + "reported_used_tokens": 721, + "working_set_bytes": 643825664, + "peak_working_set_bytes": 685244416 + }, + { + "query": "What is the Harbor test gateway port?", + "ranked": [ + "prod-port", + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MQ3BH95XDSR9MGVK404P", + "id": "01M1Y0MXMVCZDQP71FZNZ2FKTS", + "kind": "memory", + "score": 0.9944294691085817, + "summary": "project:fact - Harbor production gateway port is 8102." + }, + { + "expansion_handle": "memory:01M1Y0MQ4MQC2ZF45HFNG58TGK", + "id": "01M1Y0MXMVGG6M5BKKRSW4TP0X", + "kind": "memory", + "score": 0.5744403600692749, + "summary": "project:fact - Harbor production database port is 5432." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 350.05920000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 629, + "mcp_result_bytes": 728, + "wire_bytes": 764, + "reported_used_tokens": 728, + "working_set_bytes": 643825664, + "peak_working_set_bytes": 685244416 + }, + { + "query": "What are the Harbor production gateway retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MQ4B8Q9ZWPZDYB23MC98", + "id": "01M1Y0MXZMDDBH4WBVZANPBRGN", + "kind": "memory", + "score": 0.9974480867385864, + "summary": "project:fact - Harbor staging gateway retries are 3." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 347.078, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 426, + "mcp_result_bytes": 507, + "wire_bytes": 543, + "reported_used_tokens": 507, + "working_set_bytes": 643907584, + "peak_working_set_bytes": 685244416 + }, + { + "query": "What is the Harbor staging worker timeout?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 357.9303, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 643928064, + "peak_working_set_bytes": 685244416 + } + ], + "id": "harbor-structured-scope", + "dimension": "retrieval", + "tier": "hard", + "score": 0.5666666666666667, + "skipped": false, + "detail": "positive-recall@4=0.83 mrr=0.89 stale-hit=n/a resolution=n/a false-injection=0.833 (n=6) positive-n=9 negative-n=6 (15 queries)" + }, + { + "observations": [ + { + "query": "What is the Sable staging gateway port?", + "ranked": [ + "stage-port", + "prod-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MZYEKSR2C8636N31FRH1", + "id": "01M1Y0N290YR56CSYRD0PVM0AT", + "kind": "memory", + "score": 0.9981032609939576, + "summary": "project:fact - Sable staging gateway port is 7103." + }, + { + "expansion_handle": "memory:01M1Y0MZYTT5EG3BN8Z2VDZYF1", + "id": "01M1Y0N290C2VVP864WDV7X43X", + "kind": "memory", + "score": 0.9930086135864258, + "summary": "project:fact - Sable production gateway port is 8103." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2303.7169999999996, + "first_query": true, + "server_startup_ms": 71.23870000000001, + "model_text_bytes": 624, + "mcp_result_bytes": 723, + "wire_bytes": 758, + "reported_used_tokens": 723, + "working_set_bytes": 637116416, + "peak_working_set_bytes": 685043712 + }, + { + "query": "What is the Sable production gateway port?", + "ranked": [ + "prod-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MZYTT5EG3BN8Z2VDZYF1", + "id": "01M1Y0N2M0Q2BD5KCZXBN61XH4", + "kind": "memory", + "score": 0.9996471405029296, + "summary": "project:fact - Sable production gateway port is 8103." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 338.7989, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 543, + "reported_used_tokens": 508, + "working_set_bytes": 637607936, + "peak_working_set_bytes": 685043712 + }, + { + "query": "What are the Sable staging gateway retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MZZSGHP9RQJ7SJBET0V1", + "id": "01M1Y0N2YK6ZYT1AX62CHKKH1A", + "kind": "memory", + "score": 0.9971465468406676, + "summary": "project:fact - Sable staging gateway retries are 3." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 341.02500000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 425, + "mcp_result_bytes": 506, + "wire_bytes": 541, + "reported_used_tokens": 506, + "working_set_bytes": 637861888, + "peak_working_set_bytes": 685043712 + }, + { + "query": "What is `cache.max_entries` for Sable?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0N01ET7DABQCDCY7VFGZJ", + "id": "01M1Y0N39N8APG91JC4GFGRV5F", + "kind": "memory", + "score": 0.9999657869338988, + "summary": "project:fact - Sable cache.max_entries = 200." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 357.88779999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 419, + "mcp_result_bytes": 500, + "wire_bytes": 535, + "reported_used_tokens": 500, + "working_set_bytes": 639881216, + "peak_working_set_bytes": 685043712 + }, + { + "query": "Which database stores local state for Sable?", + "ranked": [ + "runtime", + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0N014EG1WJAVZ27PEM84S", + "id": "01M1Y0N3MKH0N10XVFF43T1FH6", + "kind": "memory", + "score": 0.9999470710754396, + "summary": "project:fact - Sable stores its local state in SQLite using WAL mode." + }, + { + "expansion_handle": "memory:01M1Y0N0013E35ENMXVAVQ9QRT", + "id": "01M1Y0N3MK1SGAKP0JP0XTQ0RZ", + "kind": "memory", + "score": 0.7779185175895691, + "summary": "project:fact - Sable production database port is 5432." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 340.3432, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 644, + "mcp_result_bytes": 743, + "wire_bytes": 778, + "reported_used_tokens": 743, + "working_set_bytes": 639963136, + "peak_working_set_bytes": 685043712 + }, + { + "query": "What are the Sable staging gateway port and password?", + "ranked": [ + "no-password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0N00AA6WFV64M4HZS0AHQ", + "id": "01M1Y0N3ZFZPF2P8MBXW2GQ1BN", + "kind": "memory", + "score": 0.9167110919952391, + "summary": "project:fact - No password is required for the Sable production gateway." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.231, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 446, + "mcp_result_bytes": 527, + "wire_bytes": 562, + "reported_used_tokens": 527, + "working_set_bytes": 640012288, + "peak_working_set_bytes": 685043712 + }, + { + "query": "What are the Sable staging gateway port and retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MZZSGHP9RQJ7SJBET0V1", + "id": "01M1Y0N4AC456FTWYRTQQCB4M5", + "kind": "memory", + "score": 0.9917003512382508, + "summary": "project:fact - Sable staging gateway retries are 3." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 355.7128, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 425, + "mcp_result_bytes": 506, + "wire_bytes": 541, + "reported_used_tokens": 506, + "working_set_bytes": 640225280, + "peak_working_set_bytes": 685043712 + }, + { + "query": "What is the Sable staging gateway timeout?", + "ranked": [ + "timeout-b", + "timeout-a" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MZZE36BDMZG2RZKCCQYG", + "id": "01M1Y0N4NXSRE844ZBZNQ7032N", + "kind": "memory", + "score": 0.9999639987945556, + "summary": "project:fact - Sable staging gateway timeout is 45 seconds. The deployment checklist records a different current value." + }, + { + "expansion_handle": "memory:01M1Y0MZZ3G0FF4JCM4923V26S", + "id": "01M1Y0N4NXQG4R3AQX9JQNYJSR", + "kind": "memory", + "score": 0.9999442100524902, + "summary": "project:fact - Sable staging gateway timeout is 30 seconds. Operators record this in the request settings." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 378.49899999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 746, + "mcp_result_bytes": 845, + "wire_bytes": 880, + "reported_used_tokens": 845, + "working_set_bytes": 640249856, + "peak_working_set_bytes": 685043712 + }, + { + "query": "What password does the Sable production gateway require?", + "ranked": [ + "no-password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0N00AA6WFV64M4HZS0AHQ", + "id": "01M1Y0N51ARDG72QKQ6ES9ANXQ", + "kind": "memory", + "score": 0.9999281167984008, + "summary": "project:fact - No password is required for the Sable production gateway." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 349.7147, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 446, + "mcp_result_bytes": 527, + "wire_bytes": 563, + "reported_used_tokens": 527, + "working_set_bytes": 645111808, + "peak_working_set_bytes": 685043712 + }, + { + "query": "What is the Sable staging database port?", + "ranked": [ + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0N0013E35ENMXVAVQ9QRT", + "id": "01M1Y0N5C76Z71N14QBAT4D4E6", + "kind": "memory", + "score": 0.9992688298225404, + "summary": "project:fact - Sable production database port is 5432." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 347.5526, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 428, + "mcp_result_bytes": 509, + "wire_bytes": 545, + "reported_used_tokens": 509, + "working_set_bytes": 645169152, + "peak_working_set_bytes": 685043712 + }, + { + "query": "What is the Sable production gateway timeout?", + "ranked": [ + "timeout-b", + "timeout-a" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MZZE36BDMZG2RZKCCQYG", + "id": "01M1Y0N5Q55H95XWKGSPYHPJJA", + "kind": "memory", + "score": 0.9997990727424622, + "summary": "project:fact - Sable staging gateway timeout is 45 seconds. The deployment checklist records a different current value." + }, + { + "expansion_handle": "memory:01M1Y0MZZ3G0FF4JCM4923V26S", + "id": "01M1Y0N5Q5EZVQRYJC05ZPZM7J", + "kind": "memory", + "score": 0.9994401335716248, + "summary": "project:fact - Sable staging gateway timeout is 30 seconds. Operators record this in the request settings." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 349.3759, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 746, + "mcp_result_bytes": 845, + "wire_bytes": 881, + "reported_used_tokens": 845, + "working_set_bytes": 645210112, + "peak_working_set_bytes": 685043712 + }, + { + "query": "What is the Unknown staging gateway port?", + "ranked": [ + "stage-port", + "foreign-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MZYEKSR2C8636N31FRH1", + "id": "01M1Y0N61ZA4Z3FD765ZJY62ZN", + "kind": "memory", + "score": 0.8858267068862915, + "summary": "project:fact - Sable staging gateway port is 7103." + }, + { + "expansion_handle": "memory:01M1Y0N00W5ZMG5YZY06YGZ0Q9", + "id": "01M1Y0N61Z1NWX36X5CB29JJ2Z", + "kind": "memory", + "score": 0.8669298887252808, + "summary": "project:fact - Foreign staging gateway port is 9944." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 345.1217, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 622, + "mcp_result_bytes": 721, + "wire_bytes": 757, + "reported_used_tokens": 721, + "working_set_bytes": 645222400, + "peak_working_set_bytes": 685043712 + }, + { + "query": "What is the Sable test gateway port?", + "ranked": [ + "prod-port", + "stage-port", + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MZYTT5EG3BN8Z2VDZYF1", + "id": "01M1Y0N6D1XYQQWMARK6D2T9VT", + "kind": "memory", + "score": 0.9929980039596558, + "summary": "project:fact - Sable production gateway port is 8103." + }, + { + "expansion_handle": "memory:01M1Y0MZYEKSR2C8636N31FRH1", + "id": "01M1Y0N6D1TVDJAZT1C35A34ZN", + "kind": "memory", + "score": 0.9868773221969604, + "summary": "project:fact - Sable staging gateway port is 7103." + }, + { + "expansion_handle": "memory:01M1Y0N0013E35ENMXVAVQ9QRT", + "id": "01M1Y0N6D1YE9XRE71AQ3KDEY5", + "kind": "memory", + "score": 0.6837578415870667, + "summary": "project:fact - Sable production database port is 5432." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 364.24440000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 824, + "mcp_result_bytes": 941, + "wire_bytes": 977, + "reported_used_tokens": 941, + "working_set_bytes": 645234688, + "peak_working_set_bytes": 685043712 + }, + { + "query": "What are the Sable production gateway retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0MZZSGHP9RQJ7SJBET0V1", + "id": "01M1Y0N6R5YFHDZZF3HJRCPMSW", + "kind": "memory", + "score": 0.992603600025177, + "summary": "project:fact - Sable staging gateway retries are 3." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 347.4506, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 424, + "mcp_result_bytes": 505, + "wire_bytes": 541, + "reported_used_tokens": 505, + "working_set_bytes": 645234688, + "peak_working_set_bytes": 685043712 + }, + { + "query": "What is the Sable staging worker timeout?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 355.12919999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 645271552, + "peak_working_set_bytes": 685043712 + } + ], + "id": "sable-structured-scope", + "dimension": "retrieval", + "tier": "hard", + "score": 0.5666666666666667, + "skipped": false, + "detail": "positive-recall@4=0.83 mrr=0.89 stale-hit=n/a resolution=n/a false-injection=0.833 (n=6) positive-n=9 negative-n=6 (15 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 1.7, + 3 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 0.5666666666666667, + "n": 3, + "ci95": 0.0 + } + }, + "overall_index": 0.5666666666666667, + "scenario_weighted_index": 0.5666666666666667 +} diff --git a/docs/audits/2026-09-07-structured-facts/results/validation/1-candidate.json b/docs/audits/2026-09-07-structured-facts/results/validation/1-candidate.json new file mode 100644 index 0000000..5b8342b --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/results/validation/1-candidate.json @@ -0,0 +1,1741 @@ +{ + "generated_at": "2026-09-07T13:24:51.980886Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-structured-facts\\validation-frozen.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "What is the Lumen staging gateway port?", + "ranked": [ + "stage-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0N8JV8AWK1EX4B6MFZVBG", + "id": "01M1Y0NATFNYFE4MBBEWZZTNDV", + "kind": "memory", + "score": 0.999786913394928, + "summary": "project:fact - Lumen staging gateway port is 7101." + } + ], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [], + "status": "supported", + "subject": "lumen gateway", + "supported": [ + { + "attribute": "port", + "sources": [ + "memory:01M1Y0N8JV8AWK1EX4B6MFZVBG" + ], + "value": "7101" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2132.7079, + "first_query": true, + "server_startup_ms": 72.2507, + "model_text_bytes": 640, + "mcp_result_bytes": 753, + "wire_bytes": 788, + "reported_used_tokens": 753, + "working_set_bytes": 632086528, + "peak_working_set_bytes": 684879872 + }, + { + "query": "What is the Lumen production gateway port?", + "ranked": [ + "prod-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0N8KBEDCE6XARQTHEDFGZ", + "id": "01M1Y0NB5HRYQZ6YMWKY97N62V", + "kind": "memory", + "score": 0.9999414682388306, + "summary": "project:fact - Lumen production gateway port is 8101." + } + ], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [], + "status": "supported", + "subject": "lumen gateway", + "supported": [ + { + "attribute": "port", + "sources": [ + "memory:01M1Y0N8KBEDCE6XARQTHEDFGZ" + ], + "value": "8101" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 342.8739, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 647, + "mcp_result_bytes": 760, + "wire_bytes": 795, + "reported_used_tokens": 760, + "working_set_bytes": 632594432, + "peak_working_set_bytes": 684879872 + }, + { + "query": "What are the Lumen staging gateway retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0N8MBH4F0B8YABX63NTNY", + "id": "01M1Y0NBG2JH38S5DW9P549B5H", + "kind": "memory", + "score": 0.9987403750419616, + "summary": "project:fact - Lumen staging gateway retries are 3." + } + ], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [], + "status": "supported", + "subject": "lumen gateway", + "supported": [ + { + "attribute": "retries", + "sources": [ + "memory:01M1Y0N8MBH4F0B8YABX63NTNY" + ], + "value": "3" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 338.7062, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 642, + "mcp_result_bytes": 755, + "wire_bytes": 790, + "reported_used_tokens": 755, + "working_set_bytes": 632823808, + "peak_working_set_bytes": 684879872 + }, + { + "query": "What is `cache.max_entries` for Lumen?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0N8P5AKGAGMY45RS6J2T0", + "id": "01M1Y0NBV21TXKSHXSHK0NW2RB", + "kind": "memory", + "score": 0.9999709129333496, + "summary": "project:fact - Lumen cache.max_entries = 200." + } + ], + "answerability": { + "conflicting": [], + "environment": null, + "missing": [], + "status": "supported", + "subject": "lumen", + "supported": [ + { + "attribute": "cache.max_entries", + "sources": [ + "memory:01M1Y0N8P5AKGAGMY45RS6J2T0" + ], + "value": "200" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.8805, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 635, + "mcp_result_bytes": 746, + "wire_bytes": 781, + "reported_used_tokens": 746, + "working_set_bytes": 635035648, + "peak_working_set_bytes": 684879872 + }, + { + "query": "Which database stores local state for Lumen?", + "ranked": [ + "runtime", + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0N8NVP17TEQF22YPHA513", + "id": "01M1Y0NC5ZNFXGVCBR3WCX8R7V", + "kind": "memory", + "score": 0.9999797344207764, + "summary": "project:fact - Lumen stores its local state in SQLite using WAL mode." + }, + { + "expansion_handle": "memory:01M1Y0N8MM9Q7YXBHCMKQG0BDZ", + "id": "01M1Y0NC5ZDP944DRYQGP4T9P3", + "kind": "memory", + "score": 0.9386039972305298, + "summary": "project:fact - Lumen production database port is 5432." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 342.3475, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 644, + "mcp_result_bytes": 743, + "wire_bytes": 778, + "reported_used_tokens": 743, + "working_set_bytes": 635142144, + "peak_working_set_bytes": 684879872 + }, + { + "query": "What are the Lumen staging gateway port and password?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port", + "password" + ], + "status": "missing", + "subject": "lumen gateway", + "supported": [] + }, + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 359.0096, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 375, + "mcp_result_bytes": 462, + "wire_bytes": 497, + "reported_used_tokens": 462, + "working_set_bytes": 635224064, + "peak_working_set_bytes": 684879872 + }, + { + "query": "What are the Lumen staging gateway port and retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0N8MBH4F0B8YABX63NTNY", + "id": "01M1Y0NCWAS1DEK68HCZ80E69V", + "kind": "memory", + "score": 0.9976552724838256, + "summary": "project:fact - Lumen staging gateway retries are 3." + } + ], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port" + ], + "status": "partial", + "subject": "lumen gateway", + "supported": [ + { + "attribute": "retries", + "sources": [ + "memory:01M1Y0N8MBH4F0B8YABX63NTNY" + ], + "value": "3" + } + ] + }, + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 363.3687, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 645, + "mcp_result_bytes": 760, + "wire_bytes": 795, + "reported_used_tokens": 760, + "working_set_bytes": 635297792, + "peak_working_set_bytes": 684879872 + }, + { + "query": "What is the Lumen staging gateway timeout?", + "ranked": [ + "timeout-b", + "timeout-a" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0N8KZ7C1Y24GCS56KFSKV", + "id": "01M1Y0ND8794XXCACEKPGX65JE", + "kind": "memory", + "score": 0.9999746084213256, + "summary": "project:fact - Lumen staging gateway timeout is 45 seconds. The deployment checklist records a different current value." + }, + { + "expansion_handle": "memory:01M1Y0N8KMX268WYEWMZF4KMPJ", + "id": "01M1Y0ND87Y0F364T1B8Z5V7MZ", + "kind": "memory", + "score": 0.9999712705612184, + "summary": "project:fact - Lumen staging gateway timeout is 30 seconds. Operators record this in the request settings." + } + ], + "answerability": { + "conflicting": [ + "timeout" + ], + "environment": "staging", + "missing": [], + "status": "conflicting", + "subject": "lumen gateway", + "supported": [] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 376.29449999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 891, + "mcp_result_bytes": 1012, + "wire_bytes": 1047, + "reported_used_tokens": 1012, + "working_set_bytes": 635420672, + "peak_working_set_bytes": 684879872 + }, + { + "query": "What password does the Lumen production gateway require?", + "ranked": [ + "no-password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0N8MYBYNCSDQD3BXBD6DX", + "id": "01M1Y0NDK15S999CA89JHNY5SN", + "kind": "memory", + "score": 0.9999558925628662, + "summary": "project:fact - No password is required for the Lumen production gateway." + } + ], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [], + "status": "supported", + "subject": "lumen gateway", + "supported": [ + { + "attribute": "password", + "sources": [ + "memory:01M1Y0N8MYBYNCSDQD3BXBD6DX" + ], + "value": "not required" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 348.5839, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 678, + "mcp_result_bytes": 791, + "wire_bytes": 827, + "reported_used_tokens": 791, + "working_set_bytes": 640208896, + "peak_working_set_bytes": 684879872 + }, + { + "query": "What is the Lumen staging database port?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port" + ], + "status": "missing", + "subject": "lumen database", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 345.6919, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 365, + "mcp_result_bytes": 450, + "wire_bytes": 486, + "reported_used_tokens": 450, + "working_set_bytes": 640229376, + "peak_working_set_bytes": 684879872 + }, + { + "query": "What is the Lumen production gateway timeout?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [ + "timeout" + ], + "status": "missing", + "subject": "lumen gateway", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 343.9848, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 370, + "mcp_result_bytes": 455, + "wire_bytes": 491, + "reported_used_tokens": 455, + "working_set_bytes": 640294912, + "peak_working_set_bytes": 684879872 + }, + { + "query": "What is the Unknown staging gateway port?", + "ranked": [ + "foreign-port", + "stage-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0N8NJZMFD0BFE06B71ZPE", + "id": "01M1Y0NEKMZS57FJ1NT5Q5K6PN", + "kind": "memory", + "score": 0.7973032593727112, + "summary": "project:fact - Foreign staging gateway port is 9944." + }, + { + "expansion_handle": "memory:01M1Y0N8JV8AWK1EX4B6MFZVBG", + "id": "01M1Y0NEKM3KC3SF56Y0XDKVEG", + "kind": "memory", + "score": 0.7143108248710632, + "summary": "project:fact - Lumen staging gateway port is 7101." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 352.2388, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 622, + "mcp_result_bytes": 721, + "wire_bytes": 757, + "reported_used_tokens": 721, + "working_set_bytes": 640311296, + "peak_working_set_bytes": 684879872 + }, + { + "query": "What is the Lumen test gateway port?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "test", + "missing": [ + "port" + ], + "status": "missing", + "subject": "lumen gateway", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 356.2607, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 361, + "mcp_result_bytes": 446, + "wire_bytes": 482, + "reported_used_tokens": 446, + "working_set_bytes": 640425984, + "peak_working_set_bytes": 684879872 + }, + { + "query": "What are the Lumen production gateway retries?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [ + "retries" + ], + "status": "missing", + "subject": "lumen gateway", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 342.2727, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 370, + "mcp_result_bytes": 455, + "wire_bytes": 491, + "reported_used_tokens": 455, + "working_set_bytes": 640512000, + "peak_working_set_bytes": 684879872 + }, + { + "query": "What is the Lumen staging worker timeout?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "timeout" + ], + "status": "missing", + "subject": "lumen worker", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 359.5198, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 366, + "mcp_result_bytes": 451, + "wire_bytes": 487, + "reported_used_tokens": 451, + "working_set_bytes": 640516096, + "peak_working_set_bytes": 684879872 + } + ], + "id": "lumen-structured-scope", + "dimension": "retrieval", + "tier": "hard", + "score": 0.8333333333333334, + "skipped": false, + "detail": "positive-recall@4=0.83 mrr=0.89 stale-hit=n/a resolution=n/a false-injection=0.167 (n=6) positive-n=9 negative-n=6 (15 queries)" + }, + { + "observations": [ + { + "query": "What is the Harbor staging gateway port?", + "ranked": [ + "stage-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0NH25CJQCWS0HJJPBGGMG", + "id": "01M1Y0NKE704MQ168VRH184V5B", + "kind": "memory", + "score": 0.9975167512893676, + "summary": "project:fact - Harbor staging gateway port is 7102." + } + ], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [], + "status": "supported", + "subject": "harbor gateway", + "supported": [ + { + "attribute": "port", + "sources": [ + "memory:01M1Y0NH25CJQCWS0HJJPBGGMG" + ], + "value": "7102" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2350.6526, + "first_query": true, + "server_startup_ms": 73.1555, + "model_text_bytes": 643, + "mcp_result_bytes": 756, + "wire_bytes": 791, + "reported_used_tokens": 756, + "working_set_bytes": 635785216, + "peak_working_set_bytes": 685150208 + }, + { + "query": "What is the Harbor production gateway port?", + "ranked": [ + "prod-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0NH2NX525BD1DPYDTYRGJ", + "id": "01M1Y0NKT2VXFPFNR712KJ4SSF", + "kind": "memory", + "score": 0.9997510313987732, + "summary": "project:fact - Harbor production gateway port is 8102." + } + ], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [], + "status": "supported", + "subject": "harbor gateway", + "supported": [ + { + "attribute": "port", + "sources": [ + "memory:01M1Y0NH2NX525BD1DPYDTYRGJ" + ], + "value": "8102" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 354.3971, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 649, + "mcp_result_bytes": 762, + "wire_bytes": 797, + "reported_used_tokens": 762, + "working_set_bytes": 636338176, + "peak_working_set_bytes": 685150208 + }, + { + "query": "What are the Harbor staging gateway retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0NH3MDPTMMT87NXX9R1NY", + "id": "01M1Y0NM4Y8QEGW0ZK571X9BHJ", + "kind": "memory", + "score": 0.9980675578117372, + "summary": "project:fact - Harbor staging gateway retries are 3." + } + ], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [], + "status": "supported", + "subject": "harbor gateway", + "supported": [ + { + "attribute": "retries", + "sources": [ + "memory:01M1Y0NH3MDPTMMT87NXX9R1NY" + ], + "value": "3" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 349.5159, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 644, + "mcp_result_bytes": 757, + "wire_bytes": 792, + "reported_used_tokens": 757, + "working_set_bytes": 636563456, + "peak_working_set_bytes": 685150208 + }, + { + "query": "What is `cache.max_entries` for Harbor?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0NH5B0YS7T5MDH0PQAQMK", + "id": "01M1Y0NMG40KWN66W3E0SGCQTX", + "kind": "memory", + "score": 0.9999717473983764, + "summary": "project:fact - Harbor cache.max_entries = 200." + } + ], + "answerability": { + "conflicting": [], + "environment": null, + "missing": [], + "status": "supported", + "subject": "harbor", + "supported": [ + { + "attribute": "cache.max_entries", + "sources": [ + "memory:01M1Y0NH5B0YS7T5MDH0PQAQMK" + ], + "value": "200" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 361.2587, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 637, + "mcp_result_bytes": 748, + "wire_bytes": 783, + "reported_used_tokens": 748, + "working_set_bytes": 638644224, + "peak_working_set_bytes": 685150208 + }, + { + "query": "Which database stores local state for Harbor?", + "ranked": [ + "runtime", + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0NH51VR1CW1TJ5DWJ9X5G", + "id": "01M1Y0NMV35R1D5VQCZZXXDFFQ", + "kind": "memory", + "score": 0.9999715089797974, + "summary": "project:fact - Harbor stores its local state in SQLite using WAL mode." + }, + { + "expansion_handle": "memory:01M1Y0NH3XB2CYNEETPBDJADY7", + "id": "01M1Y0NMV3Q3GK7RYRVGK6RJTF", + "kind": "memory", + "score": 0.8901878595352173, + "summary": "project:fact - Harbor production database port is 5432." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 344.9986, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 646, + "mcp_result_bytes": 745, + "wire_bytes": 780, + "reported_used_tokens": 745, + "working_set_bytes": 638672896, + "peak_working_set_bytes": 685150208 + }, + { + "query": "What are the Harbor staging gateway port and password?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port", + "password" + ], + "status": "missing", + "subject": "harbor gateway", + "supported": [] + }, + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 355.9871, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 376, + "mcp_result_bytes": 463, + "wire_bytes": 498, + "reported_used_tokens": 463, + "working_set_bytes": 638701568, + "peak_working_set_bytes": 685150208 + }, + { + "query": "What are the Harbor staging gateway port and retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0NH3MDPTMMT87NXX9R1NY", + "id": "01M1Y0NNH845PWYZWJ3E5ZMRTP", + "kind": "memory", + "score": 0.9916656613349916, + "summary": "project:fact - Harbor staging gateway retries are 3." + } + ], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port" + ], + "status": "partial", + "subject": "harbor gateway", + "supported": [ + { + "attribute": "retries", + "sources": [ + "memory:01M1Y0NH3MDPTMMT87NXX9R1NY" + ], + "value": "3" + } + ] + }, + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 365.1855, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 647, + "mcp_result_bytes": 762, + "wire_bytes": 797, + "reported_used_tokens": 762, + "working_set_bytes": 638849024, + "peak_working_set_bytes": 685150208 + }, + { + "query": "What is the Harbor staging gateway timeout?", + "ranked": [ + "timeout-b", + "timeout-a" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0NH39W6N2WC9PVDBG3GF7", + "id": "01M1Y0NNXAF2G3M2MS7B6NYJ38", + "kind": "memory", + "score": 0.9999679327011108, + "summary": "project:fact - Harbor staging gateway timeout is 45 seconds. The deployment checklist records a different current value." + }, + { + "expansion_handle": "memory:01M1Y0NH2Y9WDAVB2X7407278P", + "id": "01M1Y0NNX9Z7RS5P525TKE4VWM", + "kind": "memory", + "score": 0.9999622106552124, + "summary": "project:fact - Harbor staging gateway timeout is 30 seconds. Operators record this in the request settings." + } + ], + "answerability": { + "conflicting": [ + "timeout" + ], + "environment": "staging", + "missing": [], + "status": "conflicting", + "subject": "harbor gateway", + "supported": [] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 385.6261, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 894, + "mcp_result_bytes": 1015, + "wire_bytes": 1050, + "reported_used_tokens": 1015, + "working_set_bytes": 638889984, + "peak_working_set_bytes": 685150208 + }, + { + "query": "What password does the Harbor production gateway require?", + "ranked": [ + "no-password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0NH45JZRYHJBC9SYS9Q07", + "id": "01M1Y0NP8F49YZ2BPFMDDYGW08", + "kind": "memory", + "score": 0.999948024749756, + "summary": "project:fact - No password is required for the Harbor production gateway." + } + ], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [], + "status": "supported", + "subject": "harbor gateway", + "supported": [ + { + "attribute": "password", + "sources": [ + "memory:01M1Y0NH45JZRYHJBC9SYS9Q07" + ], + "value": "not required" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 350.6501, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 680, + "mcp_result_bytes": 793, + "wire_bytes": 829, + "reported_used_tokens": 793, + "working_set_bytes": 643739648, + "peak_working_set_bytes": 685150208 + }, + { + "query": "What is the Harbor staging database port?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port" + ], + "status": "missing", + "subject": "harbor database", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 347.705, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 366, + "mcp_result_bytes": 451, + "wire_bytes": 487, + "reported_used_tokens": 451, + "working_set_bytes": 643776512, + "peak_working_set_bytes": 685150208 + }, + { + "query": "What is the Harbor production gateway timeout?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [ + "timeout" + ], + "status": "missing", + "subject": "harbor gateway", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 346.6725, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 372, + "mcp_result_bytes": 457, + "wire_bytes": 493, + "reported_used_tokens": 457, + "working_set_bytes": 643784704, + "peak_working_set_bytes": 685150208 + }, + { + "query": "What is the Unknown staging gateway port?", + "ranked": [ + "foreign-port", + "stage-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0NH4SAMXQB0N8HPF9RHZE", + "id": "01M1Y0NQ98XE88KYPNNJYEJMWK", + "kind": "memory", + "score": 0.873483419418335, + "summary": "project:fact - Foreign staging gateway port is 9944." + }, + { + "expansion_handle": "memory:01M1Y0NH25CJQCWS0HJJPBGGMG", + "id": "01M1Y0NQ98SM0SF3MRXK1XDEF1", + "kind": "memory", + "score": 0.7721561789512634, + "summary": "project:fact - Harbor staging gateway port is 7102." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 361.8254, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 622, + "mcp_result_bytes": 721, + "wire_bytes": 757, + "reported_used_tokens": 721, + "working_set_bytes": 643805184, + "peak_working_set_bytes": 685150208 + }, + { + "query": "What is the Harbor test gateway port?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "test", + "missing": [ + "port" + ], + "status": "missing", + "subject": "harbor gateway", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 350.3417, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 362, + "mcp_result_bytes": 447, + "wire_bytes": 483, + "reported_used_tokens": 447, + "working_set_bytes": 643805184, + "peak_working_set_bytes": 685150208 + }, + { + "query": "What are the Harbor production gateway retries?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [ + "retries" + ], + "status": "missing", + "subject": "harbor gateway", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 347.2758, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 371, + "mcp_result_bytes": 456, + "wire_bytes": 492, + "reported_used_tokens": 456, + "working_set_bytes": 643833856, + "peak_working_set_bytes": 685150208 + }, + { + "query": "What is the Harbor staging worker timeout?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "timeout" + ], + "status": "missing", + "subject": "harbor worker", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 355.7233, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 367, + "mcp_result_bytes": 452, + "wire_bytes": 488, + "reported_used_tokens": 452, + "working_set_bytes": 643837952, + "peak_working_set_bytes": 685150208 + } + ], + "id": "harbor-structured-scope", + "dimension": "retrieval", + "tier": "hard", + "score": 0.8333333333333334, + "skipped": false, + "detail": "positive-recall@4=0.83 mrr=0.89 stale-hit=n/a resolution=n/a false-injection=0.167 (n=6) positive-n=9 negative-n=6 (15 queries)" + }, + { + "observations": [ + { + "query": "What is the Sable staging gateway port?", + "ranked": [ + "stage-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0NSSNPPJVRQD1M8114FEA", + "id": "01M1Y0NW03J93B321M8FRNTX48", + "kind": "memory", + "score": 0.9981032609939576, + "summary": "project:fact - Sable staging gateway port is 7103." + } + ], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [], + "status": "supported", + "subject": "sable gateway", + "supported": [ + { + "attribute": "port", + "sources": [ + "memory:01M1Y0NSSNPPJVRQD1M8114FEA" + ], + "value": "7103" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2162.0893, + "first_query": true, + "server_startup_ms": 70.8939, + "model_text_bytes": 641, + "mcp_result_bytes": 754, + "wire_bytes": 789, + "reported_used_tokens": 754, + "working_set_bytes": 634753024, + "peak_working_set_bytes": 685252608 + }, + { + "query": "What is the Sable production gateway port?", + "ranked": [ + "prod-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0NST5MPHXNREV8FJ4MCQ0", + "id": "01M1Y0NWB1Y1W04DXSH3DJXHQ1", + "kind": "memory", + "score": 0.9996471405029296, + "summary": "project:fact - Sable production gateway port is 8103." + } + ], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [], + "status": "supported", + "subject": "sable gateway", + "supported": [ + { + "attribute": "port", + "sources": [ + "memory:01M1Y0NST5MPHXNREV8FJ4MCQ0" + ], + "value": "8103" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 334.3506, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 647, + "mcp_result_bytes": 760, + "wire_bytes": 795, + "reported_used_tokens": 760, + "working_set_bytes": 635207680, + "peak_working_set_bytes": 685252608 + }, + { + "query": "What are the Sable staging gateway retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0NSV5NZC1VQCG1R0WZYKP", + "id": "01M1Y0NWPGCTT6QWN2Y2Z4STGE", + "kind": "memory", + "score": 0.9971465468406676, + "summary": "project:fact - Sable staging gateway retries are 3." + } + ], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [], + "status": "supported", + "subject": "sable gateway", + "supported": [ + { + "attribute": "retries", + "sources": [ + "memory:01M1Y0NSV5NZC1VQCG1R0WZYKP" + ], + "value": "3" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 384.5283, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 642, + "mcp_result_bytes": 755, + "wire_bytes": 790, + "reported_used_tokens": 755, + "working_set_bytes": 635482112, + "peak_working_set_bytes": 685252608 + }, + { + "query": "What is `cache.max_entries` for Sable?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0NSWWCEH8RWHMR9S6N8VY", + "id": "01M1Y0NX2KKMZZ3FWH69WC6ZWH", + "kind": "memory", + "score": 0.9999657869338988, + "summary": "project:fact - Sable cache.max_entries = 200." + } + ], + "answerability": { + "conflicting": [], + "environment": null, + "missing": [], + "status": "supported", + "subject": "sable", + "supported": [ + { + "attribute": "cache.max_entries", + "sources": [ + "memory:01M1Y0NSWWCEH8RWHMR9S6N8VY" + ], + "value": "200" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 390.72639999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 635, + "mcp_result_bytes": 746, + "wire_bytes": 781, + "reported_used_tokens": 746, + "working_set_bytes": 637632512, + "peak_working_set_bytes": 685252608 + }, + { + "query": "Which database stores local state for Sable?", + "ranked": [ + "runtime", + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0NSWJ7GTCVHTN4H24DAA5", + "id": "01M1Y0NXEPDKVVBT2C2JTZP70F", + "kind": "memory", + "score": 0.9999470710754396, + "summary": "project:fact - Sable stores its local state in SQLite using WAL mode." + }, + { + "expansion_handle": "memory:01M1Y0NSVDBB9GMHYG8RF8DT0K", + "id": "01M1Y0NXEPZNXTCNJ1G7JBP1WY", + "kind": "memory", + "score": 0.7779185175895691, + "summary": "project:fact - Sable production database port is 5432." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 375.0238, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 644, + "mcp_result_bytes": 743, + "wire_bytes": 778, + "reported_used_tokens": 743, + "working_set_bytes": 637657088, + "peak_working_set_bytes": 685252608 + }, + { + "query": "What are the Sable staging gateway port and password?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port", + "password" + ], + "status": "missing", + "subject": "sable gateway", + "supported": [] + }, + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 368.93359999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 375, + "mcp_result_bytes": 462, + "wire_bytes": 497, + "reported_used_tokens": 462, + "working_set_bytes": 637788160, + "peak_working_set_bytes": 685252608 + }, + { + "query": "What are the Sable staging gateway port and retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0NSV5NZC1VQCG1R0WZYKP", + "id": "01M1Y0NY5S0R8QWK1VH2Z517Y9", + "kind": "memory", + "score": 0.9917003512382508, + "summary": "project:fact - Sable staging gateway retries are 3." + } + ], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port" + ], + "status": "partial", + "subject": "sable gateway", + "supported": [ + { + "attribute": "retries", + "sources": [ + "memory:01M1Y0NSV5NZC1VQCG1R0WZYKP" + ], + "value": "3" + } + ] + }, + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 376.2567, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 645, + "mcp_result_bytes": 760, + "wire_bytes": 795, + "reported_used_tokens": 760, + "working_set_bytes": 637898752, + "peak_working_set_bytes": 685252608 + }, + { + "query": "What is the Sable staging gateway timeout?", + "ranked": [ + "timeout-b", + "timeout-a" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0NSTSW733RSPMCE1QNNJF", + "id": "01M1Y0NYHS6KKBREJFZYRF7FAR", + "kind": "memory", + "score": 0.9999639987945556, + "summary": "project:fact - Sable staging gateway timeout is 45 seconds. The deployment checklist records a different current value." + }, + { + "expansion_handle": "memory:01M1Y0NSTDH5MZJ41KZ60REVDB", + "id": "01M1Y0NYHS1B3G5QTK53RPW1E7", + "kind": "memory", + "score": 0.9999442100524902, + "summary": "project:fact - Sable staging gateway timeout is 30 seconds. Operators record this in the request settings." + } + ], + "answerability": { + "conflicting": [ + "timeout" + ], + "environment": "staging", + "missing": [], + "status": "conflicting", + "subject": "sable gateway", + "supported": [] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 379.5149, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 891, + "mcp_result_bytes": 1012, + "wire_bytes": 1047, + "reported_used_tokens": 1012, + "working_set_bytes": 638009344, + "peak_working_set_bytes": 685252608 + }, + { + "query": "What password does the Sable production gateway require?", + "ranked": [ + "no-password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0NSVPFCJ014R3TGPTR6P8", + "id": "01M1Y0NYWR6GC4D5K4AV74QP5W", + "kind": "memory", + "score": 0.9999281167984008, + "summary": "project:fact - No password is required for the Sable production gateway." + } + ], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [], + "status": "supported", + "subject": "sable gateway", + "supported": [ + { + "attribute": "password", + "sources": [ + "memory:01M1Y0NSVPFCJ014R3TGPTR6P8" + ], + "value": "not required" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 346.3059, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 678, + "mcp_result_bytes": 791, + "wire_bytes": 827, + "reported_used_tokens": 791, + "working_set_bytes": 642740224, + "peak_working_set_bytes": 685252608 + }, + { + "query": "What is the Sable staging database port?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port" + ], + "status": "missing", + "subject": "sable database", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 344.7927, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 365, + "mcp_result_bytes": 450, + "wire_bytes": 486, + "reported_used_tokens": 450, + "working_set_bytes": 642768896, + "peak_working_set_bytes": 685252608 + }, + { + "query": "What is the Sable production gateway timeout?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [ + "timeout" + ], + "status": "missing", + "subject": "sable gateway", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 348.0659, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 370, + "mcp_result_bytes": 455, + "wire_bytes": 491, + "reported_used_tokens": 455, + "working_set_bytes": 642801664, + "peak_working_set_bytes": 685252608 + }, + { + "query": "What is the Unknown staging gateway port?", + "ranked": [ + "stage-port", + "foreign-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0NSSNPPJVRQD1M8114FEA", + "id": "01M1Y0NZX8JTX7WAQKJCJFX56K", + "kind": "memory", + "score": 0.8858267068862915, + "summary": "project:fact - Sable staging gateway port is 7103." + }, + { + "expansion_handle": "memory:01M1Y0NSW94MBZ8R4HTQ6W63FS", + "id": "01M1Y0NZX81PH93BKQYY9H3G1V", + "kind": "memory", + "score": 0.8669298887252808, + "summary": "project:fact - Foreign staging gateway port is 9944." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 354.448, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 622, + "mcp_result_bytes": 721, + "wire_bytes": 757, + "reported_used_tokens": 721, + "working_set_bytes": 642846720, + "peak_working_set_bytes": 685252608 + }, + { + "query": "What is the Sable test gateway port?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "test", + "missing": [ + "port" + ], + "status": "missing", + "subject": "sable gateway", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 345.237, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 361, + "mcp_result_bytes": 446, + "wire_bytes": 482, + "reported_used_tokens": 446, + "working_set_bytes": 642875392, + "peak_working_set_bytes": 685252608 + }, + { + "query": "What are the Sable production gateway retries?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [ + "retries" + ], + "status": "missing", + "subject": "sable gateway", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 350.0077, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 370, + "mcp_result_bytes": 455, + "wire_bytes": 491, + "reported_used_tokens": 455, + "working_set_bytes": 642908160, + "peak_working_set_bytes": 685252608 + }, + { + "query": "What is the Sable staging worker timeout?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "timeout" + ], + "status": "missing", + "subject": "sable worker", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 368.7043, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 366, + "mcp_result_bytes": 451, + "wire_bytes": 487, + "reported_used_tokens": 451, + "working_set_bytes": 642928640, + "peak_working_set_bytes": 685252608 + } + ], + "id": "sable-structured-scope", + "dimension": "retrieval", + "tier": "hard", + "score": 0.8333333333333334, + "skipped": false, + "detail": "positive-recall@4=0.83 mrr=0.89 stale-hit=n/a resolution=n/a false-injection=0.167 (n=6) positive-n=9 negative-n=6 (15 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 2.5, + 3 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 0.8333333333333334, + "n": 3, + "ci95": 0.0 + } + }, + "overall_index": 0.8333333333333334, + "scenario_weighted_index": 0.8333333333333334 +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-structured-facts/results/validation/1-candidate.stderr.log b/docs/audits/2026-09-07-structured-facts/results/validation/1-candidate.stderr.log new file mode 100644 index 0000000..e143ac6 --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/results/validation/1-candidate.stderr.log @@ -0,0 +1,8 @@ +brainbench: 3 scenario(s) to run + [1/3] lumen-structured-scope | dim=retrieval tier=hard ... + -> score=0.83 | positive-recall@4=0.83 mrr=0.89 stale-hit=n/a resolution=n/a false-injection=0.167 (n=6) positive-n=9 negative-n=6 (15 queries) + [2/3] harbor-structured-scope | dim=retrieval tier=hard ... + -> score=0.83 | positive-recall@4=0.83 mrr=0.89 stale-hit=n/a resolution=n/a false-injection=0.167 (n=6) positive-n=9 negative-n=6 (15 queries) + [3/3] sable-structured-scope | dim=retrieval tier=hard ... + -> score=0.83 | positive-recall@4=0.83 mrr=0.89 stale-hit=n/a resolution=n/a false-injection=0.167 (n=6) positive-n=9 negative-n=6 (15 queries) +kbench brainbench: report saved -> E:\tmp\kimetsu-brain-hardening\bench\local\runs\brainbench\2026-09-07T13-24-51.9811874Z.json diff --git a/docs/audits/2026-09-07-structured-facts/results/validation/1-candidate.stdout.log b/docs/audits/2026-09-07-structured-facts/results/validation/1-candidate.stdout.log new file mode 100644 index 0000000..3dd5e09 --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/results/validation/1-candidate.stdout.log @@ -0,0 +1,1741 @@ +{ + "generated_at": "2026-09-07T13:24:51.980886Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-structured-facts\\validation-frozen.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "What is the Lumen staging gateway port?", + "ranked": [ + "stage-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0N8JV8AWK1EX4B6MFZVBG", + "id": "01M1Y0NATFNYFE4MBBEWZZTNDV", + "kind": "memory", + "score": 0.999786913394928, + "summary": "project:fact - Lumen staging gateway port is 7101." + } + ], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [], + "status": "supported", + "subject": "lumen gateway", + "supported": [ + { + "attribute": "port", + "sources": [ + "memory:01M1Y0N8JV8AWK1EX4B6MFZVBG" + ], + "value": "7101" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2132.7079, + "first_query": true, + "server_startup_ms": 72.2507, + "model_text_bytes": 640, + "mcp_result_bytes": 753, + "wire_bytes": 788, + "reported_used_tokens": 753, + "working_set_bytes": 632086528, + "peak_working_set_bytes": 684879872 + }, + { + "query": "What is the Lumen production gateway port?", + "ranked": [ + "prod-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0N8KBEDCE6XARQTHEDFGZ", + "id": "01M1Y0NB5HRYQZ6YMWKY97N62V", + "kind": "memory", + "score": 0.9999414682388306, + "summary": "project:fact - Lumen production gateway port is 8101." + } + ], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [], + "status": "supported", + "subject": "lumen gateway", + "supported": [ + { + "attribute": "port", + "sources": [ + "memory:01M1Y0N8KBEDCE6XARQTHEDFGZ" + ], + "value": "8101" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 342.8739, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 647, + "mcp_result_bytes": 760, + "wire_bytes": 795, + "reported_used_tokens": 760, + "working_set_bytes": 632594432, + "peak_working_set_bytes": 684879872 + }, + { + "query": "What are the Lumen staging gateway retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0N8MBH4F0B8YABX63NTNY", + "id": "01M1Y0NBG2JH38S5DW9P549B5H", + "kind": "memory", + "score": 0.9987403750419616, + "summary": "project:fact - Lumen staging gateway retries are 3." + } + ], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [], + "status": "supported", + "subject": "lumen gateway", + "supported": [ + { + "attribute": "retries", + "sources": [ + "memory:01M1Y0N8MBH4F0B8YABX63NTNY" + ], + "value": "3" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 338.7062, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 642, + "mcp_result_bytes": 755, + "wire_bytes": 790, + "reported_used_tokens": 755, + "working_set_bytes": 632823808, + "peak_working_set_bytes": 684879872 + }, + { + "query": "What is `cache.max_entries` for Lumen?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0N8P5AKGAGMY45RS6J2T0", + "id": "01M1Y0NBV21TXKSHXSHK0NW2RB", + "kind": "memory", + "score": 0.9999709129333496, + "summary": "project:fact - Lumen cache.max_entries = 200." + } + ], + "answerability": { + "conflicting": [], + "environment": null, + "missing": [], + "status": "supported", + "subject": "lumen", + "supported": [ + { + "attribute": "cache.max_entries", + "sources": [ + "memory:01M1Y0N8P5AKGAGMY45RS6J2T0" + ], + "value": "200" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.8805, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 635, + "mcp_result_bytes": 746, + "wire_bytes": 781, + "reported_used_tokens": 746, + "working_set_bytes": 635035648, + "peak_working_set_bytes": 684879872 + }, + { + "query": "Which database stores local state for Lumen?", + "ranked": [ + "runtime", + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0N8NVP17TEQF22YPHA513", + "id": "01M1Y0NC5ZNFXGVCBR3WCX8R7V", + "kind": "memory", + "score": 0.9999797344207764, + "summary": "project:fact - Lumen stores its local state in SQLite using WAL mode." + }, + { + "expansion_handle": "memory:01M1Y0N8MM9Q7YXBHCMKQG0BDZ", + "id": "01M1Y0NC5ZDP944DRYQGP4T9P3", + "kind": "memory", + "score": 0.9386039972305298, + "summary": "project:fact - Lumen production database port is 5432." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 342.3475, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 644, + "mcp_result_bytes": 743, + "wire_bytes": 778, + "reported_used_tokens": 743, + "working_set_bytes": 635142144, + "peak_working_set_bytes": 684879872 + }, + { + "query": "What are the Lumen staging gateway port and password?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port", + "password" + ], + "status": "missing", + "subject": "lumen gateway", + "supported": [] + }, + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 359.0096, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 375, + "mcp_result_bytes": 462, + "wire_bytes": 497, + "reported_used_tokens": 462, + "working_set_bytes": 635224064, + "peak_working_set_bytes": 684879872 + }, + { + "query": "What are the Lumen staging gateway port and retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0N8MBH4F0B8YABX63NTNY", + "id": "01M1Y0NCWAS1DEK68HCZ80E69V", + "kind": "memory", + "score": 0.9976552724838256, + "summary": "project:fact - Lumen staging gateway retries are 3." + } + ], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port" + ], + "status": "partial", + "subject": "lumen gateway", + "supported": [ + { + "attribute": "retries", + "sources": [ + "memory:01M1Y0N8MBH4F0B8YABX63NTNY" + ], + "value": "3" + } + ] + }, + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 363.3687, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 645, + "mcp_result_bytes": 760, + "wire_bytes": 795, + "reported_used_tokens": 760, + "working_set_bytes": 635297792, + "peak_working_set_bytes": 684879872 + }, + { + "query": "What is the Lumen staging gateway timeout?", + "ranked": [ + "timeout-b", + "timeout-a" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0N8KZ7C1Y24GCS56KFSKV", + "id": "01M1Y0ND8794XXCACEKPGX65JE", + "kind": "memory", + "score": 0.9999746084213256, + "summary": "project:fact - Lumen staging gateway timeout is 45 seconds. The deployment checklist records a different current value." + }, + { + "expansion_handle": "memory:01M1Y0N8KMX268WYEWMZF4KMPJ", + "id": "01M1Y0ND87Y0F364T1B8Z5V7MZ", + "kind": "memory", + "score": 0.9999712705612184, + "summary": "project:fact - Lumen staging gateway timeout is 30 seconds. Operators record this in the request settings." + } + ], + "answerability": { + "conflicting": [ + "timeout" + ], + "environment": "staging", + "missing": [], + "status": "conflicting", + "subject": "lumen gateway", + "supported": [] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 376.29449999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 891, + "mcp_result_bytes": 1012, + "wire_bytes": 1047, + "reported_used_tokens": 1012, + "working_set_bytes": 635420672, + "peak_working_set_bytes": 684879872 + }, + { + "query": "What password does the Lumen production gateway require?", + "ranked": [ + "no-password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0N8MYBYNCSDQD3BXBD6DX", + "id": "01M1Y0NDK15S999CA89JHNY5SN", + "kind": "memory", + "score": 0.9999558925628662, + "summary": "project:fact - No password is required for the Lumen production gateway." + } + ], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [], + "status": "supported", + "subject": "lumen gateway", + "supported": [ + { + "attribute": "password", + "sources": [ + "memory:01M1Y0N8MYBYNCSDQD3BXBD6DX" + ], + "value": "not required" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 348.5839, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 678, + "mcp_result_bytes": 791, + "wire_bytes": 827, + "reported_used_tokens": 791, + "working_set_bytes": 640208896, + "peak_working_set_bytes": 684879872 + }, + { + "query": "What is the Lumen staging database port?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port" + ], + "status": "missing", + "subject": "lumen database", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 345.6919, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 365, + "mcp_result_bytes": 450, + "wire_bytes": 486, + "reported_used_tokens": 450, + "working_set_bytes": 640229376, + "peak_working_set_bytes": 684879872 + }, + { + "query": "What is the Lumen production gateway timeout?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [ + "timeout" + ], + "status": "missing", + "subject": "lumen gateway", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 343.9848, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 370, + "mcp_result_bytes": 455, + "wire_bytes": 491, + "reported_used_tokens": 455, + "working_set_bytes": 640294912, + "peak_working_set_bytes": 684879872 + }, + { + "query": "What is the Unknown staging gateway port?", + "ranked": [ + "foreign-port", + "stage-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0N8NJZMFD0BFE06B71ZPE", + "id": "01M1Y0NEKMZS57FJ1NT5Q5K6PN", + "kind": "memory", + "score": 0.7973032593727112, + "summary": "project:fact - Foreign staging gateway port is 9944." + }, + { + "expansion_handle": "memory:01M1Y0N8JV8AWK1EX4B6MFZVBG", + "id": "01M1Y0NEKM3KC3SF56Y0XDKVEG", + "kind": "memory", + "score": 0.7143108248710632, + "summary": "project:fact - Lumen staging gateway port is 7101." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 352.2388, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 622, + "mcp_result_bytes": 721, + "wire_bytes": 757, + "reported_used_tokens": 721, + "working_set_bytes": 640311296, + "peak_working_set_bytes": 684879872 + }, + { + "query": "What is the Lumen test gateway port?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "test", + "missing": [ + "port" + ], + "status": "missing", + "subject": "lumen gateway", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 356.2607, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 361, + "mcp_result_bytes": 446, + "wire_bytes": 482, + "reported_used_tokens": 446, + "working_set_bytes": 640425984, + "peak_working_set_bytes": 684879872 + }, + { + "query": "What are the Lumen production gateway retries?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [ + "retries" + ], + "status": "missing", + "subject": "lumen gateway", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 342.2727, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 370, + "mcp_result_bytes": 455, + "wire_bytes": 491, + "reported_used_tokens": 455, + "working_set_bytes": 640512000, + "peak_working_set_bytes": 684879872 + }, + { + "query": "What is the Lumen staging worker timeout?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "timeout" + ], + "status": "missing", + "subject": "lumen worker", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 359.5198, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 366, + "mcp_result_bytes": 451, + "wire_bytes": 487, + "reported_used_tokens": 451, + "working_set_bytes": 640516096, + "peak_working_set_bytes": 684879872 + } + ], + "id": "lumen-structured-scope", + "dimension": "retrieval", + "tier": "hard", + "score": 0.8333333333333334, + "skipped": false, + "detail": "positive-recall@4=0.83 mrr=0.89 stale-hit=n/a resolution=n/a false-injection=0.167 (n=6) positive-n=9 negative-n=6 (15 queries)" + }, + { + "observations": [ + { + "query": "What is the Harbor staging gateway port?", + "ranked": [ + "stage-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0NH25CJQCWS0HJJPBGGMG", + "id": "01M1Y0NKE704MQ168VRH184V5B", + "kind": "memory", + "score": 0.9975167512893676, + "summary": "project:fact - Harbor staging gateway port is 7102." + } + ], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [], + "status": "supported", + "subject": "harbor gateway", + "supported": [ + { + "attribute": "port", + "sources": [ + "memory:01M1Y0NH25CJQCWS0HJJPBGGMG" + ], + "value": "7102" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2350.6526, + "first_query": true, + "server_startup_ms": 73.1555, + "model_text_bytes": 643, + "mcp_result_bytes": 756, + "wire_bytes": 791, + "reported_used_tokens": 756, + "working_set_bytes": 635785216, + "peak_working_set_bytes": 685150208 + }, + { + "query": "What is the Harbor production gateway port?", + "ranked": [ + "prod-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0NH2NX525BD1DPYDTYRGJ", + "id": "01M1Y0NKT2VXFPFNR712KJ4SSF", + "kind": "memory", + "score": 0.9997510313987732, + "summary": "project:fact - Harbor production gateway port is 8102." + } + ], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [], + "status": "supported", + "subject": "harbor gateway", + "supported": [ + { + "attribute": "port", + "sources": [ + "memory:01M1Y0NH2NX525BD1DPYDTYRGJ" + ], + "value": "8102" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 354.3971, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 649, + "mcp_result_bytes": 762, + "wire_bytes": 797, + "reported_used_tokens": 762, + "working_set_bytes": 636338176, + "peak_working_set_bytes": 685150208 + }, + { + "query": "What are the Harbor staging gateway retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0NH3MDPTMMT87NXX9R1NY", + "id": "01M1Y0NM4Y8QEGW0ZK571X9BHJ", + "kind": "memory", + "score": 0.9980675578117372, + "summary": "project:fact - Harbor staging gateway retries are 3." + } + ], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [], + "status": "supported", + "subject": "harbor gateway", + "supported": [ + { + "attribute": "retries", + "sources": [ + "memory:01M1Y0NH3MDPTMMT87NXX9R1NY" + ], + "value": "3" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 349.5159, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 644, + "mcp_result_bytes": 757, + "wire_bytes": 792, + "reported_used_tokens": 757, + "working_set_bytes": 636563456, + "peak_working_set_bytes": 685150208 + }, + { + "query": "What is `cache.max_entries` for Harbor?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0NH5B0YS7T5MDH0PQAQMK", + "id": "01M1Y0NMG40KWN66W3E0SGCQTX", + "kind": "memory", + "score": 0.9999717473983764, + "summary": "project:fact - Harbor cache.max_entries = 200." + } + ], + "answerability": { + "conflicting": [], + "environment": null, + "missing": [], + "status": "supported", + "subject": "harbor", + "supported": [ + { + "attribute": "cache.max_entries", + "sources": [ + "memory:01M1Y0NH5B0YS7T5MDH0PQAQMK" + ], + "value": "200" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 361.2587, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 637, + "mcp_result_bytes": 748, + "wire_bytes": 783, + "reported_used_tokens": 748, + "working_set_bytes": 638644224, + "peak_working_set_bytes": 685150208 + }, + { + "query": "Which database stores local state for Harbor?", + "ranked": [ + "runtime", + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0NH51VR1CW1TJ5DWJ9X5G", + "id": "01M1Y0NMV35R1D5VQCZZXXDFFQ", + "kind": "memory", + "score": 0.9999715089797974, + "summary": "project:fact - Harbor stores its local state in SQLite using WAL mode." + }, + { + "expansion_handle": "memory:01M1Y0NH3XB2CYNEETPBDJADY7", + "id": "01M1Y0NMV3Q3GK7RYRVGK6RJTF", + "kind": "memory", + "score": 0.8901878595352173, + "summary": "project:fact - Harbor production database port is 5432." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 344.9986, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 646, + "mcp_result_bytes": 745, + "wire_bytes": 780, + "reported_used_tokens": 745, + "working_set_bytes": 638672896, + "peak_working_set_bytes": 685150208 + }, + { + "query": "What are the Harbor staging gateway port and password?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port", + "password" + ], + "status": "missing", + "subject": "harbor gateway", + "supported": [] + }, + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 355.9871, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 376, + "mcp_result_bytes": 463, + "wire_bytes": 498, + "reported_used_tokens": 463, + "working_set_bytes": 638701568, + "peak_working_set_bytes": 685150208 + }, + { + "query": "What are the Harbor staging gateway port and retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0NH3MDPTMMT87NXX9R1NY", + "id": "01M1Y0NNH845PWYZWJ3E5ZMRTP", + "kind": "memory", + "score": 0.9916656613349916, + "summary": "project:fact - Harbor staging gateway retries are 3." + } + ], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port" + ], + "status": "partial", + "subject": "harbor gateway", + "supported": [ + { + "attribute": "retries", + "sources": [ + "memory:01M1Y0NH3MDPTMMT87NXX9R1NY" + ], + "value": "3" + } + ] + }, + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 365.1855, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 647, + "mcp_result_bytes": 762, + "wire_bytes": 797, + "reported_used_tokens": 762, + "working_set_bytes": 638849024, + "peak_working_set_bytes": 685150208 + }, + { + "query": "What is the Harbor staging gateway timeout?", + "ranked": [ + "timeout-b", + "timeout-a" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0NH39W6N2WC9PVDBG3GF7", + "id": "01M1Y0NNXAF2G3M2MS7B6NYJ38", + "kind": "memory", + "score": 0.9999679327011108, + "summary": "project:fact - Harbor staging gateway timeout is 45 seconds. The deployment checklist records a different current value." + }, + { + "expansion_handle": "memory:01M1Y0NH2Y9WDAVB2X7407278P", + "id": "01M1Y0NNX9Z7RS5P525TKE4VWM", + "kind": "memory", + "score": 0.9999622106552124, + "summary": "project:fact - Harbor staging gateway timeout is 30 seconds. Operators record this in the request settings." + } + ], + "answerability": { + "conflicting": [ + "timeout" + ], + "environment": "staging", + "missing": [], + "status": "conflicting", + "subject": "harbor gateway", + "supported": [] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 385.6261, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 894, + "mcp_result_bytes": 1015, + "wire_bytes": 1050, + "reported_used_tokens": 1015, + "working_set_bytes": 638889984, + "peak_working_set_bytes": 685150208 + }, + { + "query": "What password does the Harbor production gateway require?", + "ranked": [ + "no-password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0NH45JZRYHJBC9SYS9Q07", + "id": "01M1Y0NP8F49YZ2BPFMDDYGW08", + "kind": "memory", + "score": 0.999948024749756, + "summary": "project:fact - No password is required for the Harbor production gateway." + } + ], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [], + "status": "supported", + "subject": "harbor gateway", + "supported": [ + { + "attribute": "password", + "sources": [ + "memory:01M1Y0NH45JZRYHJBC9SYS9Q07" + ], + "value": "not required" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 350.6501, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 680, + "mcp_result_bytes": 793, + "wire_bytes": 829, + "reported_used_tokens": 793, + "working_set_bytes": 643739648, + "peak_working_set_bytes": 685150208 + }, + { + "query": "What is the Harbor staging database port?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port" + ], + "status": "missing", + "subject": "harbor database", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 347.705, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 366, + "mcp_result_bytes": 451, + "wire_bytes": 487, + "reported_used_tokens": 451, + "working_set_bytes": 643776512, + "peak_working_set_bytes": 685150208 + }, + { + "query": "What is the Harbor production gateway timeout?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [ + "timeout" + ], + "status": "missing", + "subject": "harbor gateway", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 346.6725, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 372, + "mcp_result_bytes": 457, + "wire_bytes": 493, + "reported_used_tokens": 457, + "working_set_bytes": 643784704, + "peak_working_set_bytes": 685150208 + }, + { + "query": "What is the Unknown staging gateway port?", + "ranked": [ + "foreign-port", + "stage-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0NH4SAMXQB0N8HPF9RHZE", + "id": "01M1Y0NQ98XE88KYPNNJYEJMWK", + "kind": "memory", + "score": 0.873483419418335, + "summary": "project:fact - Foreign staging gateway port is 9944." + }, + { + "expansion_handle": "memory:01M1Y0NH25CJQCWS0HJJPBGGMG", + "id": "01M1Y0NQ98SM0SF3MRXK1XDEF1", + "kind": "memory", + "score": 0.7721561789512634, + "summary": "project:fact - Harbor staging gateway port is 7102." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 361.8254, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 622, + "mcp_result_bytes": 721, + "wire_bytes": 757, + "reported_used_tokens": 721, + "working_set_bytes": 643805184, + "peak_working_set_bytes": 685150208 + }, + { + "query": "What is the Harbor test gateway port?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "test", + "missing": [ + "port" + ], + "status": "missing", + "subject": "harbor gateway", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 350.3417, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 362, + "mcp_result_bytes": 447, + "wire_bytes": 483, + "reported_used_tokens": 447, + "working_set_bytes": 643805184, + "peak_working_set_bytes": 685150208 + }, + { + "query": "What are the Harbor production gateway retries?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [ + "retries" + ], + "status": "missing", + "subject": "harbor gateway", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 347.2758, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 371, + "mcp_result_bytes": 456, + "wire_bytes": 492, + "reported_used_tokens": 456, + "working_set_bytes": 643833856, + "peak_working_set_bytes": 685150208 + }, + { + "query": "What is the Harbor staging worker timeout?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "timeout" + ], + "status": "missing", + "subject": "harbor worker", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 355.7233, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 367, + "mcp_result_bytes": 452, + "wire_bytes": 488, + "reported_used_tokens": 452, + "working_set_bytes": 643837952, + "peak_working_set_bytes": 685150208 + } + ], + "id": "harbor-structured-scope", + "dimension": "retrieval", + "tier": "hard", + "score": 0.8333333333333334, + "skipped": false, + "detail": "positive-recall@4=0.83 mrr=0.89 stale-hit=n/a resolution=n/a false-injection=0.167 (n=6) positive-n=9 negative-n=6 (15 queries)" + }, + { + "observations": [ + { + "query": "What is the Sable staging gateway port?", + "ranked": [ + "stage-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0NSSNPPJVRQD1M8114FEA", + "id": "01M1Y0NW03J93B321M8FRNTX48", + "kind": "memory", + "score": 0.9981032609939576, + "summary": "project:fact - Sable staging gateway port is 7103." + } + ], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [], + "status": "supported", + "subject": "sable gateway", + "supported": [ + { + "attribute": "port", + "sources": [ + "memory:01M1Y0NSSNPPJVRQD1M8114FEA" + ], + "value": "7103" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2162.0893, + "first_query": true, + "server_startup_ms": 70.8939, + "model_text_bytes": 641, + "mcp_result_bytes": 754, + "wire_bytes": 789, + "reported_used_tokens": 754, + "working_set_bytes": 634753024, + "peak_working_set_bytes": 685252608 + }, + { + "query": "What is the Sable production gateway port?", + "ranked": [ + "prod-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0NST5MPHXNREV8FJ4MCQ0", + "id": "01M1Y0NWB1Y1W04DXSH3DJXHQ1", + "kind": "memory", + "score": 0.9996471405029296, + "summary": "project:fact - Sable production gateway port is 8103." + } + ], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [], + "status": "supported", + "subject": "sable gateway", + "supported": [ + { + "attribute": "port", + "sources": [ + "memory:01M1Y0NST5MPHXNREV8FJ4MCQ0" + ], + "value": "8103" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 334.3506, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 647, + "mcp_result_bytes": 760, + "wire_bytes": 795, + "reported_used_tokens": 760, + "working_set_bytes": 635207680, + "peak_working_set_bytes": 685252608 + }, + { + "query": "What are the Sable staging gateway retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0NSV5NZC1VQCG1R0WZYKP", + "id": "01M1Y0NWPGCTT6QWN2Y2Z4STGE", + "kind": "memory", + "score": 0.9971465468406676, + "summary": "project:fact - Sable staging gateway retries are 3." + } + ], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [], + "status": "supported", + "subject": "sable gateway", + "supported": [ + { + "attribute": "retries", + "sources": [ + "memory:01M1Y0NSV5NZC1VQCG1R0WZYKP" + ], + "value": "3" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 384.5283, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 642, + "mcp_result_bytes": 755, + "wire_bytes": 790, + "reported_used_tokens": 755, + "working_set_bytes": 635482112, + "peak_working_set_bytes": 685252608 + }, + { + "query": "What is `cache.max_entries` for Sable?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0NSWWCEH8RWHMR9S6N8VY", + "id": "01M1Y0NX2KKMZZ3FWH69WC6ZWH", + "kind": "memory", + "score": 0.9999657869338988, + "summary": "project:fact - Sable cache.max_entries = 200." + } + ], + "answerability": { + "conflicting": [], + "environment": null, + "missing": [], + "status": "supported", + "subject": "sable", + "supported": [ + { + "attribute": "cache.max_entries", + "sources": [ + "memory:01M1Y0NSWWCEH8RWHMR9S6N8VY" + ], + "value": "200" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 390.72639999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 635, + "mcp_result_bytes": 746, + "wire_bytes": 781, + "reported_used_tokens": 746, + "working_set_bytes": 637632512, + "peak_working_set_bytes": 685252608 + }, + { + "query": "Which database stores local state for Sable?", + "ranked": [ + "runtime", + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0NSWJ7GTCVHTN4H24DAA5", + "id": "01M1Y0NXEPDKVVBT2C2JTZP70F", + "kind": "memory", + "score": 0.9999470710754396, + "summary": "project:fact - Sable stores its local state in SQLite using WAL mode." + }, + { + "expansion_handle": "memory:01M1Y0NSVDBB9GMHYG8RF8DT0K", + "id": "01M1Y0NXEPZNXTCNJ1G7JBP1WY", + "kind": "memory", + "score": 0.7779185175895691, + "summary": "project:fact - Sable production database port is 5432." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 375.0238, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 644, + "mcp_result_bytes": 743, + "wire_bytes": 778, + "reported_used_tokens": 743, + "working_set_bytes": 637657088, + "peak_working_set_bytes": 685252608 + }, + { + "query": "What are the Sable staging gateway port and password?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port", + "password" + ], + "status": "missing", + "subject": "sable gateway", + "supported": [] + }, + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 368.93359999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 375, + "mcp_result_bytes": 462, + "wire_bytes": 497, + "reported_used_tokens": 462, + "working_set_bytes": 637788160, + "peak_working_set_bytes": 685252608 + }, + { + "query": "What are the Sable staging gateway port and retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0NSV5NZC1VQCG1R0WZYKP", + "id": "01M1Y0NY5S0R8QWK1VH2Z517Y9", + "kind": "memory", + "score": 0.9917003512382508, + "summary": "project:fact - Sable staging gateway retries are 3." + } + ], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port" + ], + "status": "partial", + "subject": "sable gateway", + "supported": [ + { + "attribute": "retries", + "sources": [ + "memory:01M1Y0NSV5NZC1VQCG1R0WZYKP" + ], + "value": "3" + } + ] + }, + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 376.2567, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 645, + "mcp_result_bytes": 760, + "wire_bytes": 795, + "reported_used_tokens": 760, + "working_set_bytes": 637898752, + "peak_working_set_bytes": 685252608 + }, + { + "query": "What is the Sable staging gateway timeout?", + "ranked": [ + "timeout-b", + "timeout-a" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0NSTSW733RSPMCE1QNNJF", + "id": "01M1Y0NYHS6KKBREJFZYRF7FAR", + "kind": "memory", + "score": 0.9999639987945556, + "summary": "project:fact - Sable staging gateway timeout is 45 seconds. The deployment checklist records a different current value." + }, + { + "expansion_handle": "memory:01M1Y0NSTDH5MZJ41KZ60REVDB", + "id": "01M1Y0NYHS1B3G5QTK53RPW1E7", + "kind": "memory", + "score": 0.9999442100524902, + "summary": "project:fact - Sable staging gateway timeout is 30 seconds. Operators record this in the request settings." + } + ], + "answerability": { + "conflicting": [ + "timeout" + ], + "environment": "staging", + "missing": [], + "status": "conflicting", + "subject": "sable gateway", + "supported": [] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 379.5149, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 891, + "mcp_result_bytes": 1012, + "wire_bytes": 1047, + "reported_used_tokens": 1012, + "working_set_bytes": 638009344, + "peak_working_set_bytes": 685252608 + }, + { + "query": "What password does the Sable production gateway require?", + "ranked": [ + "no-password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0NSVPFCJ014R3TGPTR6P8", + "id": "01M1Y0NYWR6GC4D5K4AV74QP5W", + "kind": "memory", + "score": 0.9999281167984008, + "summary": "project:fact - No password is required for the Sable production gateway." + } + ], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [], + "status": "supported", + "subject": "sable gateway", + "supported": [ + { + "attribute": "password", + "sources": [ + "memory:01M1Y0NSVPFCJ014R3TGPTR6P8" + ], + "value": "not required" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 346.3059, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 678, + "mcp_result_bytes": 791, + "wire_bytes": 827, + "reported_used_tokens": 791, + "working_set_bytes": 642740224, + "peak_working_set_bytes": 685252608 + }, + { + "query": "What is the Sable staging database port?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port" + ], + "status": "missing", + "subject": "sable database", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 344.7927, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 365, + "mcp_result_bytes": 450, + "wire_bytes": 486, + "reported_used_tokens": 450, + "working_set_bytes": 642768896, + "peak_working_set_bytes": 685252608 + }, + { + "query": "What is the Sable production gateway timeout?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [ + "timeout" + ], + "status": "missing", + "subject": "sable gateway", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 348.0659, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 370, + "mcp_result_bytes": 455, + "wire_bytes": 491, + "reported_used_tokens": 455, + "working_set_bytes": 642801664, + "peak_working_set_bytes": 685252608 + }, + { + "query": "What is the Unknown staging gateway port?", + "ranked": [ + "stage-port", + "foreign-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0NSSNPPJVRQD1M8114FEA", + "id": "01M1Y0NZX8JTX7WAQKJCJFX56K", + "kind": "memory", + "score": 0.8858267068862915, + "summary": "project:fact - Sable staging gateway port is 7103." + }, + { + "expansion_handle": "memory:01M1Y0NSW94MBZ8R4HTQ6W63FS", + "id": "01M1Y0NZX81PH93BKQYY9H3G1V", + "kind": "memory", + "score": 0.8669298887252808, + "summary": "project:fact - Foreign staging gateway port is 9944." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 354.448, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 622, + "mcp_result_bytes": 721, + "wire_bytes": 757, + "reported_used_tokens": 721, + "working_set_bytes": 642846720, + "peak_working_set_bytes": 685252608 + }, + { + "query": "What is the Sable test gateway port?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "test", + "missing": [ + "port" + ], + "status": "missing", + "subject": "sable gateway", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 345.237, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 361, + "mcp_result_bytes": 446, + "wire_bytes": 482, + "reported_used_tokens": 446, + "working_set_bytes": 642875392, + "peak_working_set_bytes": 685252608 + }, + { + "query": "What are the Sable production gateway retries?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [ + "retries" + ], + "status": "missing", + "subject": "sable gateway", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 350.0077, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 370, + "mcp_result_bytes": 455, + "wire_bytes": 491, + "reported_used_tokens": 455, + "working_set_bytes": 642908160, + "peak_working_set_bytes": 685252608 + }, + { + "query": "What is the Sable staging worker timeout?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "timeout" + ], + "status": "missing", + "subject": "sable worker", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 368.7043, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 366, + "mcp_result_bytes": 451, + "wire_bytes": 487, + "reported_used_tokens": 451, + "working_set_bytes": 642928640, + "peak_working_set_bytes": 685252608 + } + ], + "id": "sable-structured-scope", + "dimension": "retrieval", + "tier": "hard", + "score": 0.8333333333333334, + "skipped": false, + "detail": "positive-recall@4=0.83 mrr=0.89 stale-hit=n/a resolution=n/a false-injection=0.167 (n=6) positive-n=9 negative-n=6 (15 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 2.5, + 3 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 0.8333333333333334, + "n": 3, + "ci95": 0.0 + } + }, + "overall_index": 0.8333333333333334, + "scenario_weighted_index": 0.8333333333333334 +} diff --git a/docs/audits/2026-09-07-structured-facts/results/validation/2-baseline.json b/docs/audits/2026-09-07-structured-facts/results/validation/2-baseline.json new file mode 100644 index 0000000..f26cca6 --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/results/validation/2-baseline.json @@ -0,0 +1,1495 @@ +{ + "generated_at": "2026-09-07T13:25:45.6106275Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-structured-facts\\validation-frozen.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "What is the Lumen staging gateway port?", + "ranked": [ + "stage-port", + "prod-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PW71SCN519D3P1E7JV6N", + "id": "01M1Y0PYGGKMCK725S6MT96DVZ", + "kind": "memory", + "score": 0.999786913394928, + "summary": "project:fact - Lumen staging gateway port is 7101." + }, + { + "expansion_handle": "memory:01M1Y0PW7ES0H7SPTF25PMPPC0", + "id": "01M1Y0PYGGDD68F17GSY0F1EMB", + "kind": "memory", + "score": 0.9992988109588624, + "summary": "project:fact - Lumen production gateway port is 8101." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2250.6062, + "first_query": true, + "server_startup_ms": 71.6788, + "model_text_bytes": 623, + "mcp_result_bytes": 722, + "wire_bytes": 757, + "reported_used_tokens": 722, + "working_set_bytes": 632786944, + "peak_working_set_bytes": 684969984 + }, + { + "query": "What is the Lumen production gateway port?", + "ranked": [ + "prod-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PW7ES0H7SPTF25PMPPC0", + "id": "01M1Y0PYVHV4ANG0QYVYYGJBAM", + "kind": "memory", + "score": 0.9999414682388306, + "summary": "project:fact - Lumen production gateway port is 8101." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 339.5843, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 543, + "reported_used_tokens": 508, + "working_set_bytes": 633176064, + "peak_working_set_bytes": 684969984 + }, + { + "query": "What are the Lumen staging gateway retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PW8HYXB43WXWS9DR5GZD", + "id": "01M1Y0PZ6DT4CBTD2FQF8988Q3", + "kind": "memory", + "score": 0.9987403750419616, + "summary": "project:fact - Lumen staging gateway retries are 3." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 347.3059, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 425, + "mcp_result_bytes": 506, + "wire_bytes": 541, + "reported_used_tokens": 506, + "working_set_bytes": 633380864, + "peak_working_set_bytes": 684969984 + }, + { + "query": "What is `cache.max_entries` for Lumen?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PWADQZ2KTSPM3QZE6XYA", + "id": "01M1Y0PZH99F4BVR7CFQZQ9C04", + "kind": "memory", + "score": 0.9999709129333496, + "summary": "project:fact - Lumen cache.max_entries = 200." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 355.9296, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 419, + "mcp_result_bytes": 500, + "wire_bytes": 535, + "reported_used_tokens": 500, + "working_set_bytes": 635588608, + "peak_working_set_bytes": 684969984 + }, + { + "query": "Which database stores local state for Lumen?", + "ranked": [ + "runtime", + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PWA2S52EKYB611SYK5Z4", + "id": "01M1Y0PZWJRTE2FDBEXDEMFMPW", + "kind": "memory", + "score": 0.9999797344207764, + "summary": "project:fact - Lumen stores its local state in SQLite using WAL mode." + }, + { + "expansion_handle": "memory:01M1Y0PW8T9XDWH10BRQFTY15S", + "id": "01M1Y0PZWJHN7SA0YDAP3CE398", + "kind": "memory", + "score": 0.9386039972305298, + "summary": "project:fact - Lumen production database port is 5432." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 352.5213, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 644, + "mcp_result_bytes": 743, + "wire_bytes": 778, + "reported_used_tokens": 743, + "working_set_bytes": 635682816, + "peak_working_set_bytes": 684969984 + }, + { + "query": "What are the Lumen staging gateway port and password?", + "ranked": [ + "no-password", + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PW93BK5M00R96WPXMF7Y", + "id": "01M1Y0Q07KR344ZEQCGWZG3AMG", + "kind": "memory", + "score": 0.9715816974639891, + "summary": "project:fact - No password is required for the Lumen production gateway." + }, + { + "expansion_handle": "memory:01M1Y0PW8T9XDWH10BRQFTY15S", + "id": "01M1Y0Q07K2M8EQ92MNH35B6WX", + "kind": "memory", + "score": 0.5804274678230286, + "summary": "project:fact - Lumen production database port is 5432." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 365.0141, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 647, + "mcp_result_bytes": 746, + "wire_bytes": 781, + "reported_used_tokens": 746, + "working_set_bytes": 635822080, + "peak_working_set_bytes": 684969984 + }, + { + "query": "What are the Lumen staging gateway port and retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PW8HYXB43WXWS9DR5GZD", + "id": "01M1Y0Q0KJJZ9FAVDXYM8Q6PHA", + "kind": "memory", + "score": 0.9976552724838256, + "summary": "project:fact - Lumen staging gateway retries are 3." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 376.5827, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 425, + "mcp_result_bytes": 506, + "wire_bytes": 541, + "reported_used_tokens": 506, + "working_set_bytes": 635928576, + "peak_working_set_bytes": 684969984 + }, + { + "query": "What is the Lumen staging gateway timeout?", + "ranked": [ + "timeout-b", + "timeout-a" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PW84ZR83ZYE5E8WA1MBC", + "id": "01M1Y0Q0YG3RBWNQAY08R1PT1A", + "kind": "memory", + "score": 0.9999746084213256, + "summary": "project:fact - Lumen staging gateway timeout is 45 seconds. The deployment checklist records a different current value." + }, + { + "expansion_handle": "memory:01M1Y0PW7R430BPXFAK184NY7Q", + "id": "01M1Y0Q0YGGAG6JXA1PFBY4P4Q", + "kind": "memory", + "score": 0.9999712705612184, + "summary": "project:fact - Lumen staging gateway timeout is 30 seconds. Operators record this in the request settings." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 348.817, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 746, + "mcp_result_bytes": 845, + "wire_bytes": 880, + "reported_used_tokens": 845, + "working_set_bytes": 636026880, + "peak_working_set_bytes": 684969984 + }, + { + "query": "What password does the Lumen production gateway require?", + "ranked": [ + "no-password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PW93BK5M00R96WPXMF7Y", + "id": "01M1Y0Q19CVG05SPY6CH7TJTNX", + "kind": "memory", + "score": 0.9999558925628662, + "summary": "project:fact - No password is required for the Lumen production gateway." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 348.4909, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 446, + "mcp_result_bytes": 527, + "wire_bytes": 563, + "reported_used_tokens": 527, + "working_set_bytes": 640757760, + "peak_working_set_bytes": 684969984 + }, + { + "query": "What is the Lumen staging database port?", + "ranked": [ + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PW8T9XDWH10BRQFTY15S", + "id": "01M1Y0Q1MCV5XZV9VNHABP8YJP", + "kind": "memory", + "score": 0.9998140931129456, + "summary": "project:fact - Lumen production database port is 5432." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 349.334, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 428, + "mcp_result_bytes": 509, + "wire_bytes": 545, + "reported_used_tokens": 509, + "working_set_bytes": 640839680, + "peak_working_set_bytes": 684969984 + }, + { + "query": "What is the Lumen production gateway timeout?", + "ranked": [ + "timeout-a", + "timeout-b" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PW7R430BPXFAK184NY7Q", + "id": "01M1Y0Q1Z7P3ZE4SPDGFR4E9HG", + "kind": "memory", + "score": 0.999944806098938, + "summary": "project:fact - Lumen staging gateway timeout is 30 seconds. Operators record this in the request settings." + }, + { + "expansion_handle": "memory:01M1Y0PW84ZR83ZYE5E8WA1MBC", + "id": "01M1Y0Q1Z7SK17QX0ZQ3RT0NJN", + "kind": "memory", + "score": 0.9999274015426636, + "summary": "project:fact - Lumen staging gateway timeout is 45 seconds. The deployment checklist records a different current value." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 357.54720000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 745, + "mcp_result_bytes": 844, + "wire_bytes": 880, + "reported_used_tokens": 844, + "working_set_bytes": 640909312, + "peak_working_set_bytes": 684969984 + }, + { + "query": "What is the Unknown staging gateway port?", + "ranked": [ + "foreign-port", + "stage-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PW9RF3CZSTP0HS2NK0ST", + "id": "01M1Y0Q2ACNV5JYM5PAQB5CV7N", + "kind": "memory", + "score": 0.7973032593727112, + "summary": "project:fact - Foreign staging gateway port is 9944." + }, + { + "expansion_handle": "memory:01M1Y0PW71SCN519D3P1E7JV6N", + "id": "01M1Y0Q2ACD82AXZP31KT6JVE0", + "kind": "memory", + "score": 0.7143108248710632, + "summary": "project:fact - Lumen staging gateway port is 7101." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 347.7853, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 622, + "mcp_result_bytes": 721, + "wire_bytes": 757, + "reported_used_tokens": 721, + "working_set_bytes": 641011712, + "peak_working_set_bytes": 684969984 + }, + { + "query": "What is the Lumen test gateway port?", + "ranked": [ + "stage-port", + "prod-port", + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PW71SCN519D3P1E7JV6N", + "id": "01M1Y0Q2N8HEC0QFCAER3SB6Z6", + "kind": "memory", + "score": 0.9995205402374268, + "summary": "project:fact - Lumen staging gateway port is 7101." + }, + { + "expansion_handle": "memory:01M1Y0PW7ES0H7SPTF25PMPPC0", + "id": "01M1Y0Q2N8KVQ4N68GRYPWS8B5", + "kind": "memory", + "score": 0.9990516304969788, + "summary": "project:fact - Lumen production gateway port is 8101." + }, + { + "expansion_handle": "memory:01M1Y0PW8T9XDWH10BRQFTY15S", + "id": "01M1Y0Q2N8TNRK18RPQPBQZRB0", + "kind": "memory", + "score": 0.9248796105384828, + "summary": "project:fact - Lumen production database port is 5432." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 346.42830000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 824, + "mcp_result_bytes": 941, + "wire_bytes": 977, + "reported_used_tokens": 941, + "working_set_bytes": 641052672, + "peak_working_set_bytes": 684969984 + }, + { + "query": "What are the Lumen production gateway retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PW8HYXB43WXWS9DR5GZD", + "id": "01M1Y0Q30CMHSK5487RR9XXVC3", + "kind": "memory", + "score": 0.9977250695228576, + "summary": "project:fact - Lumen staging gateway retries are 3." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 356.238, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 425, + "mcp_result_bytes": 506, + "wire_bytes": 542, + "reported_used_tokens": 506, + "working_set_bytes": 641089536, + "peak_working_set_bytes": 684969984 + }, + { + "query": "What is the Lumen staging worker timeout?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 367.2558, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 641179648, + "peak_working_set_bytes": 684969984 + } + ], + "id": "lumen-structured-scope", + "dimension": "retrieval", + "tier": "hard", + "score": 0.5666666666666667, + "skipped": false, + "detail": "positive-recall@4=0.83 mrr=0.89 stale-hit=n/a resolution=n/a false-injection=0.833 (n=6) positive-n=9 negative-n=6 (15 queries)" + }, + { + "observations": [ + { + "query": "What is the Harbor staging gateway port?", + "ranked": [ + "stage-port", + "prod-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0Q4X8WSAJC0FF657A3PVA", + "id": "01M1Y0Q7MTFY31QS345W5Q2N5B", + "kind": "memory", + "score": 0.9975167512893676, + "summary": "project:fact - Harbor staging gateway port is 7102." + }, + { + "expansion_handle": "memory:01M1Y0Q4XNG8A210DJSC1BBVR7", + "id": "01M1Y0Q7MT0R3M6GT917VNFNP5", + "kind": "memory", + "score": 0.9971925616264344, + "summary": "project:fact - Harbor production gateway port is 8102." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2362.7338, + "first_query": true, + "server_startup_ms": 71.2713, + "model_text_bytes": 626, + "mcp_result_bytes": 725, + "wire_bytes": 760, + "reported_used_tokens": 725, + "working_set_bytes": 635957248, + "peak_working_set_bytes": 684736512 + }, + { + "query": "What is the Harbor production gateway port?", + "ranked": [ + "prod-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0Q4XNG8A210DJSC1BBVR7", + "id": "01M1Y0Q7ZRXZR1FC1PPJVPSZ6T", + "kind": "memory", + "score": 0.9997510313987732, + "summary": "project:fact - Harbor production gateway port is 8102." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 336.2588, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 428, + "mcp_result_bytes": 509, + "wire_bytes": 544, + "reported_used_tokens": 509, + "working_set_bytes": 636350464, + "peak_working_set_bytes": 684736512 + }, + { + "query": "What are the Harbor staging gateway retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0Q4YM2ZBXDHZFCDSA71VR", + "id": "01M1Y0Q8AHE3CR4AJKEFRAFWW9", + "kind": "memory", + "score": 0.9980675578117372, + "summary": "project:fact - Harbor staging gateway retries are 3." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 349.8158, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 426, + "mcp_result_bytes": 507, + "wire_bytes": 542, + "reported_used_tokens": 507, + "working_set_bytes": 636702720, + "peak_working_set_bytes": 684736512 + }, + { + "query": "What is `cache.max_entries` for Harbor?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0Q50CMTYQJV85HJZ5PAB1", + "id": "01M1Y0Q8NBFFCXF4FZJ9C29XP4", + "kind": "memory", + "score": 0.9999717473983764, + "summary": "project:fact - Harbor cache.max_entries = 200." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 347.9292, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 420, + "mcp_result_bytes": 501, + "wire_bytes": 536, + "reported_used_tokens": 501, + "working_set_bytes": 638803968, + "peak_working_set_bytes": 684736512 + }, + { + "query": "Which database stores local state for Harbor?", + "ranked": [ + "runtime", + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0Q502599FG51RMQ41QP8D", + "id": "01M1Y0Q90G1YD5B39HCS4QCXHQ", + "kind": "memory", + "score": 0.9999715089797974, + "summary": "project:fact - Harbor stores its local state in SQLite using WAL mode." + }, + { + "expansion_handle": "memory:01M1Y0Q4YXVDYM4NQN34QNKBEH", + "id": "01M1Y0Q90G1TT065XCZ2BQ2WRD", + "kind": "memory", + "score": 0.8901878595352173, + "summary": "project:fact - Harbor production database port is 5432." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 349.4072, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 646, + "mcp_result_bytes": 745, + "wire_bytes": 780, + "reported_used_tokens": 745, + "working_set_bytes": 639074304, + "peak_working_set_bytes": 684736512 + }, + { + "query": "What are the Harbor staging gateway port and password?", + "ranked": [ + "no-password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0Q4Z57VE2XFT3AGWFC10J", + "id": "01M1Y0Q9BE30K0YB8KG6YMQZ6X", + "kind": "memory", + "score": 0.9481525421142578, + "summary": "project:fact - No password is required for the Harbor production gateway." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 366.5064, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 447, + "mcp_result_bytes": 528, + "wire_bytes": 563, + "reported_used_tokens": 528, + "working_set_bytes": 639283200, + "peak_working_set_bytes": 684736512 + }, + { + "query": "What are the Harbor staging gateway port and retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0Q4YM2ZBXDHZFCDSA71VR", + "id": "01M1Y0Q9QD9195EKXY9481RBY1", + "kind": "memory", + "score": 0.9916656613349916, + "summary": "project:fact - Harbor staging gateway retries are 3." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 373.0637, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 426, + "mcp_result_bytes": 507, + "wire_bytes": 542, + "reported_used_tokens": 507, + "working_set_bytes": 639414272, + "peak_working_set_bytes": 684736512 + }, + { + "query": "What is the Harbor staging gateway timeout?", + "ranked": [ + "timeout-b", + "timeout-a" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0Q4Y9RH8CQJRN9Z9NP3WP", + "id": "01M1Y0QA29A2057EMCKCEEWNM6", + "kind": "memory", + "score": 0.9999679327011108, + "summary": "project:fact - Harbor staging gateway timeout is 45 seconds. The deployment checklist records a different current value." + }, + { + "expansion_handle": "memory:01M1Y0Q4XYQ2KH386PS64AADCH", + "id": "01M1Y0QA29Q2RZZY4D8A96ZJ0X", + "kind": "memory", + "score": 0.9999622106552124, + "summary": "project:fact - Harbor staging gateway timeout is 30 seconds. Operators record this in the request settings." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 349.5391, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 748, + "mcp_result_bytes": 847, + "wire_bytes": 882, + "reported_used_tokens": 847, + "working_set_bytes": 639508480, + "peak_working_set_bytes": 684736512 + }, + { + "query": "What password does the Harbor production gateway require?", + "ranked": [ + "no-password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0Q4Z57VE2XFT3AGWFC10J", + "id": "01M1Y0QAD8032K1XGE6SKB1T1N", + "kind": "memory", + "score": 0.999948024749756, + "summary": "project:fact - No password is required for the Harbor production gateway." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 351.27619999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 447, + "mcp_result_bytes": 528, + "wire_bytes": 564, + "reported_used_tokens": 528, + "working_set_bytes": 644329472, + "peak_working_set_bytes": 684736512 + }, + { + "query": "What is the Harbor staging database port?", + "ranked": [ + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0Q4YXVDYM4NQN34QNKBEH", + "id": "01M1Y0QAR60B9RJQC4877BCQJY", + "kind": "memory", + "score": 0.9995362758636476, + "summary": "project:fact - Harbor production database port is 5432." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 350.35699999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 429, + "mcp_result_bytes": 510, + "wire_bytes": 546, + "reported_used_tokens": 510, + "working_set_bytes": 644395008, + "peak_working_set_bytes": 684736512 + }, + { + "query": "What is the Harbor production gateway timeout?", + "ranked": [ + "timeout-a", + "timeout-b" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0Q4XYQ2KH386PS64AADCH", + "id": "01M1Y0QB336MP6YN6YQF05VZ5Z", + "kind": "memory", + "score": 0.9995842576026917, + "summary": "project:fact - Harbor staging gateway timeout is 30 seconds. Operators record this in the request settings." + }, + { + "expansion_handle": "memory:01M1Y0Q4Y9RH8CQJRN9Z9NP3WP", + "id": "01M1Y0QB3377MCVTQM1V2Y5DZK", + "kind": "memory", + "score": 0.9994773268699646, + "summary": "project:fact - Harbor staging gateway timeout is 45 seconds. The deployment checklist records a different current value." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 354.5375, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 748, + "mcp_result_bytes": 847, + "wire_bytes": 883, + "reported_used_tokens": 847, + "working_set_bytes": 644403200, + "peak_working_set_bytes": 684736512 + }, + { + "query": "What is the Unknown staging gateway port?", + "ranked": [ + "foreign-port", + "stage-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0Q4ZT6W41D733CPYDSTZQ", + "id": "01M1Y0QBE99TW9G8S0XD3ZJ0B3", + "kind": "memory", + "score": 0.873483419418335, + "summary": "project:fact - Foreign staging gateway port is 9944." + }, + { + "expansion_handle": "memory:01M1Y0Q4X8WSAJC0FF657A3PVA", + "id": "01M1Y0QBE9SD7PJYTHM7PKXDEM", + "kind": "memory", + "score": 0.7721561789512634, + "summary": "project:fact - Harbor staging gateway port is 7102." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 347.7335, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 622, + "mcp_result_bytes": 721, + "wire_bytes": 757, + "reported_used_tokens": 721, + "working_set_bytes": 644407296, + "peak_working_set_bytes": 684736512 + }, + { + "query": "What is the Harbor test gateway port?", + "ranked": [ + "prod-port", + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0Q4XNG8A210DJSC1BBVR7", + "id": "01M1Y0QBS37W6XXHWVEC82J61P", + "kind": "memory", + "score": 0.9944294691085817, + "summary": "project:fact - Harbor production gateway port is 8102." + }, + { + "expansion_handle": "memory:01M1Y0Q4YXVDYM4NQN34QNKBEH", + "id": "01M1Y0QBS3T0GH53NFRDY1E2N4", + "kind": "memory", + "score": 0.5744403600692749, + "summary": "project:fact - Harbor production database port is 5432." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 346.8607, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 629, + "mcp_result_bytes": 728, + "wire_bytes": 764, + "reported_used_tokens": 728, + "working_set_bytes": 644415488, + "peak_working_set_bytes": 684736512 + }, + { + "query": "What are the Harbor production gateway retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0Q4YM2ZBXDHZFCDSA71VR", + "id": "01M1Y0QC47K4MQRGYX3MV46EM7", + "kind": "memory", + "score": 0.9974480867385864, + "summary": "project:fact - Harbor staging gateway retries are 3." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 354.8752, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 426, + "mcp_result_bytes": 507, + "wire_bytes": 543, + "reported_used_tokens": 507, + "working_set_bytes": 644423680, + "peak_working_set_bytes": 684736512 + }, + { + "query": "What is the Harbor staging worker timeout?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 366.1587, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644521984, + "peak_working_set_bytes": 684736512 + } + ], + "id": "harbor-structured-scope", + "dimension": "retrieval", + "tier": "hard", + "score": 0.5666666666666667, + "skipped": false, + "detail": "positive-recall@4=0.83 mrr=0.89 stale-hit=n/a resolution=n/a false-injection=0.833 (n=6) positive-n=9 negative-n=6 (15 queries)" + }, + { + "observations": [ + { + "query": "What is the Sable staging gateway port?", + "ranked": [ + "stage-port", + "prod-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0QE34JGEX72VA3QGWEY73", + "id": "01M1Y0QGERXZQQEP7DEAXR0YTG", + "kind": "memory", + "score": 0.9981032609939576, + "summary": "project:fact - Sable staging gateway port is 7103." + }, + { + "expansion_handle": "memory:01M1Y0QE3J8BHWD1S33QP7SB09", + "id": "01M1Y0QGER6JMAMQ0NQV3H5TVX", + "kind": "memory", + "score": 0.9930086135864258, + "summary": "project:fact - Sable production gateway port is 8103." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2312.4004, + "first_query": true, + "server_startup_ms": 76.5259, + "model_text_bytes": 624, + "mcp_result_bytes": 723, + "wire_bytes": 758, + "reported_used_tokens": 723, + "working_set_bytes": 632696832, + "peak_working_set_bytes": 684744704 + }, + { + "query": "What is the Sable production gateway port?", + "ranked": [ + "prod-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0QE3J8BHWD1S33QP7SB09", + "id": "01M1Y0QGSR9V5HCDCB1HM8HXY7", + "kind": "memory", + "score": 0.9996471405029296, + "summary": "project:fact - Sable production gateway port is 8103." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 344.4859, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 543, + "reported_used_tokens": 508, + "working_set_bytes": 633135104, + "peak_working_set_bytes": 684744704 + }, + { + "query": "What are the Sable staging gateway retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0QE4NE4KZV1ET5ZBDTNCY", + "id": "01M1Y0QH4JDVQB39MJXNN458WK", + "kind": "memory", + "score": 0.9971465468406676, + "summary": "project:fact - Sable staging gateway retries are 3." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 341.5524, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 425, + "mcp_result_bytes": 506, + "wire_bytes": 541, + "reported_used_tokens": 506, + "working_set_bytes": 633323520, + "peak_working_set_bytes": 684744704 + }, + { + "query": "What is `cache.max_entries` for Sable?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0QE6KJ715HZN12MHBG1GM", + "id": "01M1Y0QHFJSXX0MSKP0ZQMS1SG", + "kind": "memory", + "score": 0.9999657869338988, + "summary": "project:fact - Sable cache.max_entries = 200." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 362.80899999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 419, + "mcp_result_bytes": 500, + "wire_bytes": 535, + "reported_used_tokens": 500, + "working_set_bytes": 635412480, + "peak_working_set_bytes": 684744704 + }, + { + "query": "Which database stores local state for Sable?", + "ranked": [ + "runtime", + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0QE67MQM0MY77FTJJ6R8M", + "id": "01M1Y0QHTNP2X3CVZMJZ5BTF3X", + "kind": "memory", + "score": 0.9999470710754396, + "summary": "project:fact - Sable stores its local state in SQLite using WAL mode." + }, + { + "expansion_handle": "memory:01M1Y0QE4YK9E7EH1W0GA1WP6V", + "id": "01M1Y0QHTNYKCA3QQC54XYDVC8", + "kind": "memory", + "score": 0.7779185175895691, + "summary": "project:fact - Sable production database port is 5432." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 341.8795, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 644, + "mcp_result_bytes": 743, + "wire_bytes": 778, + "reported_used_tokens": 743, + "working_set_bytes": 635596800, + "peak_working_set_bytes": 684744704 + }, + { + "query": "What are the Sable staging gateway port and password?", + "ranked": [ + "no-password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0QE58P9D4DX0EXV0GBF44", + "id": "01M1Y0QJ5XE1AQQKEXPFDTW3MC", + "kind": "memory", + "score": 0.9167110919952391, + "summary": "project:fact - No password is required for the Sable production gateway." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 375.5496, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 446, + "mcp_result_bytes": 527, + "wire_bytes": 562, + "reported_used_tokens": 527, + "working_set_bytes": 635662336, + "peak_working_set_bytes": 684744704 + }, + { + "query": "What are the Sable staging gateway port and retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0QE4NE4KZV1ET5ZBDTNCY", + "id": "01M1Y0QJJ2241JAY2G3508EQF3", + "kind": "memory", + "score": 0.9917003512382508, + "summary": "project:fact - Sable staging gateway retries are 3." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 381.121, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 425, + "mcp_result_bytes": 506, + "wire_bytes": 541, + "reported_used_tokens": 506, + "working_set_bytes": 635858944, + "peak_working_set_bytes": 684744704 + }, + { + "query": "What is the Sable staging gateway timeout?", + "ranked": [ + "timeout-b", + "timeout-a" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0QE493E0Y19JD73MKPZMD", + "id": "01M1Y0QJX3R2J0JSWMFGSXA1G8", + "kind": "memory", + "score": 0.9999639987945556, + "summary": "project:fact - Sable staging gateway timeout is 45 seconds. The deployment checklist records a different current value." + }, + { + "expansion_handle": "memory:01M1Y0QE3WTQGSH9YPVEGR5B74", + "id": "01M1Y0QJX3G6JR9HQVJZBTSA7B", + "kind": "memory", + "score": 0.9999442100524902, + "summary": "project:fact - Sable staging gateway timeout is 30 seconds. Operators record this in the request settings." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 350.1278, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 746, + "mcp_result_bytes": 845, + "wire_bytes": 880, + "reported_used_tokens": 845, + "working_set_bytes": 635998208, + "peak_working_set_bytes": 684744704 + }, + { + "query": "What password does the Sable production gateway require?", + "ranked": [ + "no-password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0QE58P9D4DX0EXV0GBF44", + "id": "01M1Y0QK7YZZ5GHP9AKHRATZPY", + "kind": "memory", + "score": 0.9999281167984008, + "summary": "project:fact - No password is required for the Sable production gateway." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 349.11379999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 446, + "mcp_result_bytes": 527, + "wire_bytes": 563, + "reported_used_tokens": 527, + "working_set_bytes": 640741376, + "peak_working_set_bytes": 684744704 + }, + { + "query": "What is the Sable staging database port?", + "ranked": [ + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0QE4YK9E7EH1W0GA1WP6V", + "id": "01M1Y0QKJTDM6QJTSQ5E2NCN7Q", + "kind": "memory", + "score": 0.9992688298225404, + "summary": "project:fact - Sable production database port is 5432." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 344.6001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 428, + "mcp_result_bytes": 509, + "wire_bytes": 545, + "reported_used_tokens": 509, + "working_set_bytes": 640876544, + "peak_working_set_bytes": 684744704 + }, + { + "query": "What is the Sable production gateway timeout?", + "ranked": [ + "timeout-b", + "timeout-a" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0QE493E0Y19JD73MKPZMD", + "id": "01M1Y0QKY0Y1AV9YEQCNH1H63R", + "kind": "memory", + "score": 0.9997990727424622, + "summary": "project:fact - Sable staging gateway timeout is 45 seconds. The deployment checklist records a different current value." + }, + { + "expansion_handle": "memory:01M1Y0QE3WTQGSH9YPVEGR5B74", + "id": "01M1Y0QKY04REEEM5FF75ZRGPF", + "kind": "memory", + "score": 0.9994401335716248, + "summary": "project:fact - Sable staging gateway timeout is 30 seconds. Operators record this in the request settings." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 368.4126, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 746, + "mcp_result_bytes": 845, + "wire_bytes": 881, + "reported_used_tokens": 845, + "working_set_bytes": 640991232, + "peak_working_set_bytes": 684744704 + }, + { + "query": "What is the Unknown staging gateway port?", + "ranked": [ + "stage-port", + "foreign-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0QE34JGEX72VA3QGWEY73", + "id": "01M1Y0QM94H247VPRTT1YT0CC7", + "kind": "memory", + "score": 0.8858267068862915, + "summary": "project:fact - Sable staging gateway port is 7103." + }, + { + "expansion_handle": "memory:01M1Y0QE5YFNTPAZR767CTCCAS", + "id": "01M1Y0QM943WTR83NAT28EB8A7", + "kind": "memory", + "score": 0.8669298887252808, + "summary": "project:fact - Foreign staging gateway port is 9944." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 346.792, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 622, + "mcp_result_bytes": 721, + "wire_bytes": 757, + "reported_used_tokens": 721, + "working_set_bytes": 640995328, + "peak_working_set_bytes": 684744704 + }, + { + "query": "What is the Sable test gateway port?", + "ranked": [ + "prod-port", + "stage-port", + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0QE3J8BHWD1S33QP7SB09", + "id": "01M1Y0QMKZB4P8SMFMSDYBEJ2S", + "kind": "memory", + "score": 0.9929980039596558, + "summary": "project:fact - Sable production gateway port is 8103." + }, + { + "expansion_handle": "memory:01M1Y0QE34JGEX72VA3QGWEY73", + "id": "01M1Y0QMKZB972Q77YECY12P6Y", + "kind": "memory", + "score": 0.9868773221969604, + "summary": "project:fact - Sable staging gateway port is 7103." + }, + { + "expansion_handle": "memory:01M1Y0QE4YK9E7EH1W0GA1WP6V", + "id": "01M1Y0QMM0EWM4DEEGCVB583G6", + "kind": "memory", + "score": 0.6837578415870667, + "summary": "project:fact - Sable production database port is 5432." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 345.86220000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 824, + "mcp_result_bytes": 941, + "wire_bytes": 977, + "reported_used_tokens": 941, + "working_set_bytes": 641036288, + "peak_working_set_bytes": 684744704 + }, + { + "query": "What are the Sable production gateway retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0QE4NE4KZV1ET5ZBDTNCY", + "id": "01M1Y0QMZ3A7K0R4QJBZQBCFFW", + "kind": "memory", + "score": 0.992603600025177, + "summary": "project:fact - Sable staging gateway retries are 3." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 353.9762, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 424, + "mcp_result_bytes": 505, + "wire_bytes": 541, + "reported_used_tokens": 505, + "working_set_bytes": 641073152, + "peak_working_set_bytes": 684744704 + }, + { + "query": "What is the Sable staging worker timeout?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 365.00260000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 641110016, + "peak_working_set_bytes": 684744704 + } + ], + "id": "sable-structured-scope", + "dimension": "retrieval", + "tier": "hard", + "score": 0.5666666666666667, + "skipped": false, + "detail": "positive-recall@4=0.83 mrr=0.89 stale-hit=n/a resolution=n/a false-injection=0.833 (n=6) positive-n=9 negative-n=6 (15 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 1.7, + 3 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 0.5666666666666667, + "n": 3, + "ci95": 0.0 + } + }, + "overall_index": 0.5666666666666667, + "scenario_weighted_index": 0.5666666666666667 +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-structured-facts/results/validation/2-baseline.stderr.log b/docs/audits/2026-09-07-structured-facts/results/validation/2-baseline.stderr.log new file mode 100644 index 0000000..6cdd112 --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/results/validation/2-baseline.stderr.log @@ -0,0 +1,8 @@ +brainbench: 3 scenario(s) to run + [1/3] lumen-structured-scope | dim=retrieval tier=hard ... + -> score=0.57 | positive-recall@4=0.83 mrr=0.89 stale-hit=n/a resolution=n/a false-injection=0.833 (n=6) positive-n=9 negative-n=6 (15 queries) + [2/3] harbor-structured-scope | dim=retrieval tier=hard ... + -> score=0.57 | positive-recall@4=0.83 mrr=0.89 stale-hit=n/a resolution=n/a false-injection=0.833 (n=6) positive-n=9 negative-n=6 (15 queries) + [3/3] sable-structured-scope | dim=retrieval tier=hard ... + -> score=0.57 | positive-recall@4=0.83 mrr=0.89 stale-hit=n/a resolution=n/a false-injection=0.833 (n=6) positive-n=9 negative-n=6 (15 queries) +kbench brainbench: report saved -> E:\tmp\kimetsu-brain-hardening\bench\local\runs\brainbench\2026-09-07T13-25-45.6109557Z.json diff --git a/docs/audits/2026-09-07-structured-facts/results/validation/2-baseline.stdout.log b/docs/audits/2026-09-07-structured-facts/results/validation/2-baseline.stdout.log new file mode 100644 index 0000000..444a5ab --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/results/validation/2-baseline.stdout.log @@ -0,0 +1,1495 @@ +{ + "generated_at": "2026-09-07T13:25:45.6106275Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-structured-facts\\validation-frozen.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "What is the Lumen staging gateway port?", + "ranked": [ + "stage-port", + "prod-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PW71SCN519D3P1E7JV6N", + "id": "01M1Y0PYGGKMCK725S6MT96DVZ", + "kind": "memory", + "score": 0.999786913394928, + "summary": "project:fact - Lumen staging gateway port is 7101." + }, + { + "expansion_handle": "memory:01M1Y0PW7ES0H7SPTF25PMPPC0", + "id": "01M1Y0PYGGDD68F17GSY0F1EMB", + "kind": "memory", + "score": 0.9992988109588624, + "summary": "project:fact - Lumen production gateway port is 8101." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2250.6062, + "first_query": true, + "server_startup_ms": 71.6788, + "model_text_bytes": 623, + "mcp_result_bytes": 722, + "wire_bytes": 757, + "reported_used_tokens": 722, + "working_set_bytes": 632786944, + "peak_working_set_bytes": 684969984 + }, + { + "query": "What is the Lumen production gateway port?", + "ranked": [ + "prod-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PW7ES0H7SPTF25PMPPC0", + "id": "01M1Y0PYVHV4ANG0QYVYYGJBAM", + "kind": "memory", + "score": 0.9999414682388306, + "summary": "project:fact - Lumen production gateway port is 8101." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 339.5843, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 543, + "reported_used_tokens": 508, + "working_set_bytes": 633176064, + "peak_working_set_bytes": 684969984 + }, + { + "query": "What are the Lumen staging gateway retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PW8HYXB43WXWS9DR5GZD", + "id": "01M1Y0PZ6DT4CBTD2FQF8988Q3", + "kind": "memory", + "score": 0.9987403750419616, + "summary": "project:fact - Lumen staging gateway retries are 3." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 347.3059, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 425, + "mcp_result_bytes": 506, + "wire_bytes": 541, + "reported_used_tokens": 506, + "working_set_bytes": 633380864, + "peak_working_set_bytes": 684969984 + }, + { + "query": "What is `cache.max_entries` for Lumen?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PWADQZ2KTSPM3QZE6XYA", + "id": "01M1Y0PZH99F4BVR7CFQZQ9C04", + "kind": "memory", + "score": 0.9999709129333496, + "summary": "project:fact - Lumen cache.max_entries = 200." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 355.9296, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 419, + "mcp_result_bytes": 500, + "wire_bytes": 535, + "reported_used_tokens": 500, + "working_set_bytes": 635588608, + "peak_working_set_bytes": 684969984 + }, + { + "query": "Which database stores local state for Lumen?", + "ranked": [ + "runtime", + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PWA2S52EKYB611SYK5Z4", + "id": "01M1Y0PZWJRTE2FDBEXDEMFMPW", + "kind": "memory", + "score": 0.9999797344207764, + "summary": "project:fact - Lumen stores its local state in SQLite using WAL mode." + }, + { + "expansion_handle": "memory:01M1Y0PW8T9XDWH10BRQFTY15S", + "id": "01M1Y0PZWJHN7SA0YDAP3CE398", + "kind": "memory", + "score": 0.9386039972305298, + "summary": "project:fact - Lumen production database port is 5432." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 352.5213, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 644, + "mcp_result_bytes": 743, + "wire_bytes": 778, + "reported_used_tokens": 743, + "working_set_bytes": 635682816, + "peak_working_set_bytes": 684969984 + }, + { + "query": "What are the Lumen staging gateway port and password?", + "ranked": [ + "no-password", + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PW93BK5M00R96WPXMF7Y", + "id": "01M1Y0Q07KR344ZEQCGWZG3AMG", + "kind": "memory", + "score": 0.9715816974639891, + "summary": "project:fact - No password is required for the Lumen production gateway." + }, + { + "expansion_handle": "memory:01M1Y0PW8T9XDWH10BRQFTY15S", + "id": "01M1Y0Q07K2M8EQ92MNH35B6WX", + "kind": "memory", + "score": 0.5804274678230286, + "summary": "project:fact - Lumen production database port is 5432." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 365.0141, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 647, + "mcp_result_bytes": 746, + "wire_bytes": 781, + "reported_used_tokens": 746, + "working_set_bytes": 635822080, + "peak_working_set_bytes": 684969984 + }, + { + "query": "What are the Lumen staging gateway port and retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PW8HYXB43WXWS9DR5GZD", + "id": "01M1Y0Q0KJJZ9FAVDXYM8Q6PHA", + "kind": "memory", + "score": 0.9976552724838256, + "summary": "project:fact - Lumen staging gateway retries are 3." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 376.5827, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 425, + "mcp_result_bytes": 506, + "wire_bytes": 541, + "reported_used_tokens": 506, + "working_set_bytes": 635928576, + "peak_working_set_bytes": 684969984 + }, + { + "query": "What is the Lumen staging gateway timeout?", + "ranked": [ + "timeout-b", + "timeout-a" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PW84ZR83ZYE5E8WA1MBC", + "id": "01M1Y0Q0YG3RBWNQAY08R1PT1A", + "kind": "memory", + "score": 0.9999746084213256, + "summary": "project:fact - Lumen staging gateway timeout is 45 seconds. The deployment checklist records a different current value." + }, + { + "expansion_handle": "memory:01M1Y0PW7R430BPXFAK184NY7Q", + "id": "01M1Y0Q0YGGAG6JXA1PFBY4P4Q", + "kind": "memory", + "score": 0.9999712705612184, + "summary": "project:fact - Lumen staging gateway timeout is 30 seconds. Operators record this in the request settings." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 348.817, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 746, + "mcp_result_bytes": 845, + "wire_bytes": 880, + "reported_used_tokens": 845, + "working_set_bytes": 636026880, + "peak_working_set_bytes": 684969984 + }, + { + "query": "What password does the Lumen production gateway require?", + "ranked": [ + "no-password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PW93BK5M00R96WPXMF7Y", + "id": "01M1Y0Q19CVG05SPY6CH7TJTNX", + "kind": "memory", + "score": 0.9999558925628662, + "summary": "project:fact - No password is required for the Lumen production gateway." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 348.4909, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 446, + "mcp_result_bytes": 527, + "wire_bytes": 563, + "reported_used_tokens": 527, + "working_set_bytes": 640757760, + "peak_working_set_bytes": 684969984 + }, + { + "query": "What is the Lumen staging database port?", + "ranked": [ + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PW8T9XDWH10BRQFTY15S", + "id": "01M1Y0Q1MCV5XZV9VNHABP8YJP", + "kind": "memory", + "score": 0.9998140931129456, + "summary": "project:fact - Lumen production database port is 5432." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 349.334, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 428, + "mcp_result_bytes": 509, + "wire_bytes": 545, + "reported_used_tokens": 509, + "working_set_bytes": 640839680, + "peak_working_set_bytes": 684969984 + }, + { + "query": "What is the Lumen production gateway timeout?", + "ranked": [ + "timeout-a", + "timeout-b" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PW7R430BPXFAK184NY7Q", + "id": "01M1Y0Q1Z7P3ZE4SPDGFR4E9HG", + "kind": "memory", + "score": 0.999944806098938, + "summary": "project:fact - Lumen staging gateway timeout is 30 seconds. Operators record this in the request settings." + }, + { + "expansion_handle": "memory:01M1Y0PW84ZR83ZYE5E8WA1MBC", + "id": "01M1Y0Q1Z7SK17QX0ZQ3RT0NJN", + "kind": "memory", + "score": 0.9999274015426636, + "summary": "project:fact - Lumen staging gateway timeout is 45 seconds. The deployment checklist records a different current value." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 357.54720000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 745, + "mcp_result_bytes": 844, + "wire_bytes": 880, + "reported_used_tokens": 844, + "working_set_bytes": 640909312, + "peak_working_set_bytes": 684969984 + }, + { + "query": "What is the Unknown staging gateway port?", + "ranked": [ + "foreign-port", + "stage-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PW9RF3CZSTP0HS2NK0ST", + "id": "01M1Y0Q2ACNV5JYM5PAQB5CV7N", + "kind": "memory", + "score": 0.7973032593727112, + "summary": "project:fact - Foreign staging gateway port is 9944." + }, + { + "expansion_handle": "memory:01M1Y0PW71SCN519D3P1E7JV6N", + "id": "01M1Y0Q2ACD82AXZP31KT6JVE0", + "kind": "memory", + "score": 0.7143108248710632, + "summary": "project:fact - Lumen staging gateway port is 7101." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 347.7853, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 622, + "mcp_result_bytes": 721, + "wire_bytes": 757, + "reported_used_tokens": 721, + "working_set_bytes": 641011712, + "peak_working_set_bytes": 684969984 + }, + { + "query": "What is the Lumen test gateway port?", + "ranked": [ + "stage-port", + "prod-port", + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PW71SCN519D3P1E7JV6N", + "id": "01M1Y0Q2N8HEC0QFCAER3SB6Z6", + "kind": "memory", + "score": 0.9995205402374268, + "summary": "project:fact - Lumen staging gateway port is 7101." + }, + { + "expansion_handle": "memory:01M1Y0PW7ES0H7SPTF25PMPPC0", + "id": "01M1Y0Q2N8KVQ4N68GRYPWS8B5", + "kind": "memory", + "score": 0.9990516304969788, + "summary": "project:fact - Lumen production gateway port is 8101." + }, + { + "expansion_handle": "memory:01M1Y0PW8T9XDWH10BRQFTY15S", + "id": "01M1Y0Q2N8TNRK18RPQPBQZRB0", + "kind": "memory", + "score": 0.9248796105384828, + "summary": "project:fact - Lumen production database port is 5432." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 346.42830000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 824, + "mcp_result_bytes": 941, + "wire_bytes": 977, + "reported_used_tokens": 941, + "working_set_bytes": 641052672, + "peak_working_set_bytes": 684969984 + }, + { + "query": "What are the Lumen production gateway retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PW8HYXB43WXWS9DR5GZD", + "id": "01M1Y0Q30CMHSK5487RR9XXVC3", + "kind": "memory", + "score": 0.9977250695228576, + "summary": "project:fact - Lumen staging gateway retries are 3." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 356.238, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 425, + "mcp_result_bytes": 506, + "wire_bytes": 542, + "reported_used_tokens": 506, + "working_set_bytes": 641089536, + "peak_working_set_bytes": 684969984 + }, + { + "query": "What is the Lumen staging worker timeout?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 367.2558, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 641179648, + "peak_working_set_bytes": 684969984 + } + ], + "id": "lumen-structured-scope", + "dimension": "retrieval", + "tier": "hard", + "score": 0.5666666666666667, + "skipped": false, + "detail": "positive-recall@4=0.83 mrr=0.89 stale-hit=n/a resolution=n/a false-injection=0.833 (n=6) positive-n=9 negative-n=6 (15 queries)" + }, + { + "observations": [ + { + "query": "What is the Harbor staging gateway port?", + "ranked": [ + "stage-port", + "prod-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0Q4X8WSAJC0FF657A3PVA", + "id": "01M1Y0Q7MTFY31QS345W5Q2N5B", + "kind": "memory", + "score": 0.9975167512893676, + "summary": "project:fact - Harbor staging gateway port is 7102." + }, + { + "expansion_handle": "memory:01M1Y0Q4XNG8A210DJSC1BBVR7", + "id": "01M1Y0Q7MT0R3M6GT917VNFNP5", + "kind": "memory", + "score": 0.9971925616264344, + "summary": "project:fact - Harbor production gateway port is 8102." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2362.7338, + "first_query": true, + "server_startup_ms": 71.2713, + "model_text_bytes": 626, + "mcp_result_bytes": 725, + "wire_bytes": 760, + "reported_used_tokens": 725, + "working_set_bytes": 635957248, + "peak_working_set_bytes": 684736512 + }, + { + "query": "What is the Harbor production gateway port?", + "ranked": [ + "prod-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0Q4XNG8A210DJSC1BBVR7", + "id": "01M1Y0Q7ZRXZR1FC1PPJVPSZ6T", + "kind": "memory", + "score": 0.9997510313987732, + "summary": "project:fact - Harbor production gateway port is 8102." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 336.2588, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 428, + "mcp_result_bytes": 509, + "wire_bytes": 544, + "reported_used_tokens": 509, + "working_set_bytes": 636350464, + "peak_working_set_bytes": 684736512 + }, + { + "query": "What are the Harbor staging gateway retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0Q4YM2ZBXDHZFCDSA71VR", + "id": "01M1Y0Q8AHE3CR4AJKEFRAFWW9", + "kind": "memory", + "score": 0.9980675578117372, + "summary": "project:fact - Harbor staging gateway retries are 3." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 349.8158, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 426, + "mcp_result_bytes": 507, + "wire_bytes": 542, + "reported_used_tokens": 507, + "working_set_bytes": 636702720, + "peak_working_set_bytes": 684736512 + }, + { + "query": "What is `cache.max_entries` for Harbor?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0Q50CMTYQJV85HJZ5PAB1", + "id": "01M1Y0Q8NBFFCXF4FZJ9C29XP4", + "kind": "memory", + "score": 0.9999717473983764, + "summary": "project:fact - Harbor cache.max_entries = 200." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 347.9292, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 420, + "mcp_result_bytes": 501, + "wire_bytes": 536, + "reported_used_tokens": 501, + "working_set_bytes": 638803968, + "peak_working_set_bytes": 684736512 + }, + { + "query": "Which database stores local state for Harbor?", + "ranked": [ + "runtime", + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0Q502599FG51RMQ41QP8D", + "id": "01M1Y0Q90G1YD5B39HCS4QCXHQ", + "kind": "memory", + "score": 0.9999715089797974, + "summary": "project:fact - Harbor stores its local state in SQLite using WAL mode." + }, + { + "expansion_handle": "memory:01M1Y0Q4YXVDYM4NQN34QNKBEH", + "id": "01M1Y0Q90G1TT065XCZ2BQ2WRD", + "kind": "memory", + "score": 0.8901878595352173, + "summary": "project:fact - Harbor production database port is 5432." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 349.4072, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 646, + "mcp_result_bytes": 745, + "wire_bytes": 780, + "reported_used_tokens": 745, + "working_set_bytes": 639074304, + "peak_working_set_bytes": 684736512 + }, + { + "query": "What are the Harbor staging gateway port and password?", + "ranked": [ + "no-password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0Q4Z57VE2XFT3AGWFC10J", + "id": "01M1Y0Q9BE30K0YB8KG6YMQZ6X", + "kind": "memory", + "score": 0.9481525421142578, + "summary": "project:fact - No password is required for the Harbor production gateway." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 366.5064, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 447, + "mcp_result_bytes": 528, + "wire_bytes": 563, + "reported_used_tokens": 528, + "working_set_bytes": 639283200, + "peak_working_set_bytes": 684736512 + }, + { + "query": "What are the Harbor staging gateway port and retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0Q4YM2ZBXDHZFCDSA71VR", + "id": "01M1Y0Q9QD9195EKXY9481RBY1", + "kind": "memory", + "score": 0.9916656613349916, + "summary": "project:fact - Harbor staging gateway retries are 3." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 373.0637, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 426, + "mcp_result_bytes": 507, + "wire_bytes": 542, + "reported_used_tokens": 507, + "working_set_bytes": 639414272, + "peak_working_set_bytes": 684736512 + }, + { + "query": "What is the Harbor staging gateway timeout?", + "ranked": [ + "timeout-b", + "timeout-a" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0Q4Y9RH8CQJRN9Z9NP3WP", + "id": "01M1Y0QA29A2057EMCKCEEWNM6", + "kind": "memory", + "score": 0.9999679327011108, + "summary": "project:fact - Harbor staging gateway timeout is 45 seconds. The deployment checklist records a different current value." + }, + { + "expansion_handle": "memory:01M1Y0Q4XYQ2KH386PS64AADCH", + "id": "01M1Y0QA29Q2RZZY4D8A96ZJ0X", + "kind": "memory", + "score": 0.9999622106552124, + "summary": "project:fact - Harbor staging gateway timeout is 30 seconds. Operators record this in the request settings." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 349.5391, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 748, + "mcp_result_bytes": 847, + "wire_bytes": 882, + "reported_used_tokens": 847, + "working_set_bytes": 639508480, + "peak_working_set_bytes": 684736512 + }, + { + "query": "What password does the Harbor production gateway require?", + "ranked": [ + "no-password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0Q4Z57VE2XFT3AGWFC10J", + "id": "01M1Y0QAD8032K1XGE6SKB1T1N", + "kind": "memory", + "score": 0.999948024749756, + "summary": "project:fact - No password is required for the Harbor production gateway." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 351.27619999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 447, + "mcp_result_bytes": 528, + "wire_bytes": 564, + "reported_used_tokens": 528, + "working_set_bytes": 644329472, + "peak_working_set_bytes": 684736512 + }, + { + "query": "What is the Harbor staging database port?", + "ranked": [ + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0Q4YXVDYM4NQN34QNKBEH", + "id": "01M1Y0QAR60B9RJQC4877BCQJY", + "kind": "memory", + "score": 0.9995362758636476, + "summary": "project:fact - Harbor production database port is 5432." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 350.35699999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 429, + "mcp_result_bytes": 510, + "wire_bytes": 546, + "reported_used_tokens": 510, + "working_set_bytes": 644395008, + "peak_working_set_bytes": 684736512 + }, + { + "query": "What is the Harbor production gateway timeout?", + "ranked": [ + "timeout-a", + "timeout-b" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0Q4XYQ2KH386PS64AADCH", + "id": "01M1Y0QB336MP6YN6YQF05VZ5Z", + "kind": "memory", + "score": 0.9995842576026917, + "summary": "project:fact - Harbor staging gateway timeout is 30 seconds. Operators record this in the request settings." + }, + { + "expansion_handle": "memory:01M1Y0Q4Y9RH8CQJRN9Z9NP3WP", + "id": "01M1Y0QB3377MCVTQM1V2Y5DZK", + "kind": "memory", + "score": 0.9994773268699646, + "summary": "project:fact - Harbor staging gateway timeout is 45 seconds. The deployment checklist records a different current value." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 354.5375, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 748, + "mcp_result_bytes": 847, + "wire_bytes": 883, + "reported_used_tokens": 847, + "working_set_bytes": 644403200, + "peak_working_set_bytes": 684736512 + }, + { + "query": "What is the Unknown staging gateway port?", + "ranked": [ + "foreign-port", + "stage-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0Q4ZT6W41D733CPYDSTZQ", + "id": "01M1Y0QBE99TW9G8S0XD3ZJ0B3", + "kind": "memory", + "score": 0.873483419418335, + "summary": "project:fact - Foreign staging gateway port is 9944." + }, + { + "expansion_handle": "memory:01M1Y0Q4X8WSAJC0FF657A3PVA", + "id": "01M1Y0QBE9SD7PJYTHM7PKXDEM", + "kind": "memory", + "score": 0.7721561789512634, + "summary": "project:fact - Harbor staging gateway port is 7102." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 347.7335, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 622, + "mcp_result_bytes": 721, + "wire_bytes": 757, + "reported_used_tokens": 721, + "working_set_bytes": 644407296, + "peak_working_set_bytes": 684736512 + }, + { + "query": "What is the Harbor test gateway port?", + "ranked": [ + "prod-port", + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0Q4XNG8A210DJSC1BBVR7", + "id": "01M1Y0QBS37W6XXHWVEC82J61P", + "kind": "memory", + "score": 0.9944294691085817, + "summary": "project:fact - Harbor production gateway port is 8102." + }, + { + "expansion_handle": "memory:01M1Y0Q4YXVDYM4NQN34QNKBEH", + "id": "01M1Y0QBS3T0GH53NFRDY1E2N4", + "kind": "memory", + "score": 0.5744403600692749, + "summary": "project:fact - Harbor production database port is 5432." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 346.8607, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 629, + "mcp_result_bytes": 728, + "wire_bytes": 764, + "reported_used_tokens": 728, + "working_set_bytes": 644415488, + "peak_working_set_bytes": 684736512 + }, + { + "query": "What are the Harbor production gateway retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0Q4YM2ZBXDHZFCDSA71VR", + "id": "01M1Y0QC47K4MQRGYX3MV46EM7", + "kind": "memory", + "score": 0.9974480867385864, + "summary": "project:fact - Harbor staging gateway retries are 3." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 354.8752, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 426, + "mcp_result_bytes": 507, + "wire_bytes": 543, + "reported_used_tokens": 507, + "working_set_bytes": 644423680, + "peak_working_set_bytes": 684736512 + }, + { + "query": "What is the Harbor staging worker timeout?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 366.1587, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 644521984, + "peak_working_set_bytes": 684736512 + } + ], + "id": "harbor-structured-scope", + "dimension": "retrieval", + "tier": "hard", + "score": 0.5666666666666667, + "skipped": false, + "detail": "positive-recall@4=0.83 mrr=0.89 stale-hit=n/a resolution=n/a false-injection=0.833 (n=6) positive-n=9 negative-n=6 (15 queries)" + }, + { + "observations": [ + { + "query": "What is the Sable staging gateway port?", + "ranked": [ + "stage-port", + "prod-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0QE34JGEX72VA3QGWEY73", + "id": "01M1Y0QGERXZQQEP7DEAXR0YTG", + "kind": "memory", + "score": 0.9981032609939576, + "summary": "project:fact - Sable staging gateway port is 7103." + }, + { + "expansion_handle": "memory:01M1Y0QE3J8BHWD1S33QP7SB09", + "id": "01M1Y0QGER6JMAMQ0NQV3H5TVX", + "kind": "memory", + "score": 0.9930086135864258, + "summary": "project:fact - Sable production gateway port is 8103." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2312.4004, + "first_query": true, + "server_startup_ms": 76.5259, + "model_text_bytes": 624, + "mcp_result_bytes": 723, + "wire_bytes": 758, + "reported_used_tokens": 723, + "working_set_bytes": 632696832, + "peak_working_set_bytes": 684744704 + }, + { + "query": "What is the Sable production gateway port?", + "ranked": [ + "prod-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0QE3J8BHWD1S33QP7SB09", + "id": "01M1Y0QGSR9V5HCDCB1HM8HXY7", + "kind": "memory", + "score": 0.9996471405029296, + "summary": "project:fact - Sable production gateway port is 8103." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 344.4859, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 427, + "mcp_result_bytes": 508, + "wire_bytes": 543, + "reported_used_tokens": 508, + "working_set_bytes": 633135104, + "peak_working_set_bytes": 684744704 + }, + { + "query": "What are the Sable staging gateway retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0QE4NE4KZV1ET5ZBDTNCY", + "id": "01M1Y0QH4JDVQB39MJXNN458WK", + "kind": "memory", + "score": 0.9971465468406676, + "summary": "project:fact - Sable staging gateway retries are 3." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 341.5524, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 425, + "mcp_result_bytes": 506, + "wire_bytes": 541, + "reported_used_tokens": 506, + "working_set_bytes": 633323520, + "peak_working_set_bytes": 684744704 + }, + { + "query": "What is `cache.max_entries` for Sable?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0QE6KJ715HZN12MHBG1GM", + "id": "01M1Y0QHFJSXX0MSKP0ZQMS1SG", + "kind": "memory", + "score": 0.9999657869338988, + "summary": "project:fact - Sable cache.max_entries = 200." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 362.80899999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 419, + "mcp_result_bytes": 500, + "wire_bytes": 535, + "reported_used_tokens": 500, + "working_set_bytes": 635412480, + "peak_working_set_bytes": 684744704 + }, + { + "query": "Which database stores local state for Sable?", + "ranked": [ + "runtime", + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0QE67MQM0MY77FTJJ6R8M", + "id": "01M1Y0QHTNP2X3CVZMJZ5BTF3X", + "kind": "memory", + "score": 0.9999470710754396, + "summary": "project:fact - Sable stores its local state in SQLite using WAL mode." + }, + { + "expansion_handle": "memory:01M1Y0QE4YK9E7EH1W0GA1WP6V", + "id": "01M1Y0QHTNYKCA3QQC54XYDVC8", + "kind": "memory", + "score": 0.7779185175895691, + "summary": "project:fact - Sable production database port is 5432." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 341.8795, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 644, + "mcp_result_bytes": 743, + "wire_bytes": 778, + "reported_used_tokens": 743, + "working_set_bytes": 635596800, + "peak_working_set_bytes": 684744704 + }, + { + "query": "What are the Sable staging gateway port and password?", + "ranked": [ + "no-password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0QE58P9D4DX0EXV0GBF44", + "id": "01M1Y0QJ5XE1AQQKEXPFDTW3MC", + "kind": "memory", + "score": 0.9167110919952391, + "summary": "project:fact - No password is required for the Sable production gateway." + } + ], + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 375.5496, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 446, + "mcp_result_bytes": 527, + "wire_bytes": 562, + "reported_used_tokens": 527, + "working_set_bytes": 635662336, + "peak_working_set_bytes": 684744704 + }, + { + "query": "What are the Sable staging gateway port and retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0QE4NE4KZV1ET5ZBDTNCY", + "id": "01M1Y0QJJ2241JAY2G3508EQF3", + "kind": "memory", + "score": 0.9917003512382508, + "summary": "project:fact - Sable staging gateway retries are 3." + } + ], + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 381.121, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 425, + "mcp_result_bytes": 506, + "wire_bytes": 541, + "reported_used_tokens": 506, + "working_set_bytes": 635858944, + "peak_working_set_bytes": 684744704 + }, + { + "query": "What is the Sable staging gateway timeout?", + "ranked": [ + "timeout-b", + "timeout-a" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0QE493E0Y19JD73MKPZMD", + "id": "01M1Y0QJX3R2J0JSWMFGSXA1G8", + "kind": "memory", + "score": 0.9999639987945556, + "summary": "project:fact - Sable staging gateway timeout is 45 seconds. The deployment checklist records a different current value." + }, + { + "expansion_handle": "memory:01M1Y0QE3WTQGSH9YPVEGR5B74", + "id": "01M1Y0QJX3G6JR9HQVJZBTSA7B", + "kind": "memory", + "score": 0.9999442100524902, + "summary": "project:fact - Sable staging gateway timeout is 30 seconds. Operators record this in the request settings." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 350.1278, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 746, + "mcp_result_bytes": 845, + "wire_bytes": 880, + "reported_used_tokens": 845, + "working_set_bytes": 635998208, + "peak_working_set_bytes": 684744704 + }, + { + "query": "What password does the Sable production gateway require?", + "ranked": [ + "no-password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0QE58P9D4DX0EXV0GBF44", + "id": "01M1Y0QK7YZZ5GHP9AKHRATZPY", + "kind": "memory", + "score": 0.9999281167984008, + "summary": "project:fact - No password is required for the Sable production gateway." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 349.11379999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 446, + "mcp_result_bytes": 527, + "wire_bytes": 563, + "reported_used_tokens": 527, + "working_set_bytes": 640741376, + "peak_working_set_bytes": 684744704 + }, + { + "query": "What is the Sable staging database port?", + "ranked": [ + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0QE4YK9E7EH1W0GA1WP6V", + "id": "01M1Y0QKJTDM6QJTSQ5E2NCN7Q", + "kind": "memory", + "score": 0.9992688298225404, + "summary": "project:fact - Sable production database port is 5432." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 344.6001, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 428, + "mcp_result_bytes": 509, + "wire_bytes": 545, + "reported_used_tokens": 509, + "working_set_bytes": 640876544, + "peak_working_set_bytes": 684744704 + }, + { + "query": "What is the Sable production gateway timeout?", + "ranked": [ + "timeout-b", + "timeout-a" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0QE493E0Y19JD73MKPZMD", + "id": "01M1Y0QKY0Y1AV9YEQCNH1H63R", + "kind": "memory", + "score": 0.9997990727424622, + "summary": "project:fact - Sable staging gateway timeout is 45 seconds. The deployment checklist records a different current value." + }, + { + "expansion_handle": "memory:01M1Y0QE3WTQGSH9YPVEGR5B74", + "id": "01M1Y0QKY04REEEM5FF75ZRGPF", + "kind": "memory", + "score": 0.9994401335716248, + "summary": "project:fact - Sable staging gateway timeout is 30 seconds. Operators record this in the request settings." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 368.4126, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 746, + "mcp_result_bytes": 845, + "wire_bytes": 881, + "reported_used_tokens": 845, + "working_set_bytes": 640991232, + "peak_working_set_bytes": 684744704 + }, + { + "query": "What is the Unknown staging gateway port?", + "ranked": [ + "stage-port", + "foreign-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0QE34JGEX72VA3QGWEY73", + "id": "01M1Y0QM94H247VPRTT1YT0CC7", + "kind": "memory", + "score": 0.8858267068862915, + "summary": "project:fact - Sable staging gateway port is 7103." + }, + { + "expansion_handle": "memory:01M1Y0QE5YFNTPAZR767CTCCAS", + "id": "01M1Y0QM943WTR83NAT28EB8A7", + "kind": "memory", + "score": 0.8669298887252808, + "summary": "project:fact - Foreign staging gateway port is 9944." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 346.792, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 622, + "mcp_result_bytes": 721, + "wire_bytes": 757, + "reported_used_tokens": 721, + "working_set_bytes": 640995328, + "peak_working_set_bytes": 684744704 + }, + { + "query": "What is the Sable test gateway port?", + "ranked": [ + "prod-port", + "stage-port", + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0QE3J8BHWD1S33QP7SB09", + "id": "01M1Y0QMKZB4P8SMFMSDYBEJ2S", + "kind": "memory", + "score": 0.9929980039596558, + "summary": "project:fact - Sable production gateway port is 8103." + }, + { + "expansion_handle": "memory:01M1Y0QE34JGEX72VA3QGWEY73", + "id": "01M1Y0QMKZB972Q77YECY12P6Y", + "kind": "memory", + "score": 0.9868773221969604, + "summary": "project:fact - Sable staging gateway port is 7103." + }, + { + "expansion_handle": "memory:01M1Y0QE4YK9E7EH1W0GA1WP6V", + "id": "01M1Y0QMM0EWM4DEEGCVB583G6", + "kind": "memory", + "score": 0.6837578415870667, + "summary": "project:fact - Sable production database port is 5432." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 345.86220000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 824, + "mcp_result_bytes": 941, + "wire_bytes": 977, + "reported_used_tokens": 941, + "working_set_bytes": 641036288, + "peak_working_set_bytes": 684744704 + }, + { + "query": "What are the Sable production gateway retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0QE4NE4KZV1ET5ZBDTNCY", + "id": "01M1Y0QMZ3A7K0R4QJBZQBCFFW", + "kind": "memory", + "score": 0.992603600025177, + "summary": "project:fact - Sable staging gateway retries are 3." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 353.9762, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 424, + "mcp_result_bytes": 505, + "wire_bytes": 541, + "reported_used_tokens": 505, + "working_set_bytes": 641073152, + "peak_working_set_bytes": 684744704 + }, + { + "query": "What is the Sable staging worker timeout?", + "ranked": [], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 365.00260000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 226, + "mcp_result_bytes": 289, + "wire_bytes": 325, + "reported_used_tokens": 289, + "working_set_bytes": 641110016, + "peak_working_set_bytes": 684744704 + } + ], + "id": "sable-structured-scope", + "dimension": "retrieval", + "tier": "hard", + "score": 0.5666666666666667, + "skipped": false, + "detail": "positive-recall@4=0.83 mrr=0.89 stale-hit=n/a resolution=n/a false-injection=0.833 (n=6) positive-n=9 negative-n=6 (15 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 1.7, + 3 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 0.5666666666666667, + "n": 3, + "ci95": 0.0 + } + }, + "overall_index": 0.5666666666666667, + "scenario_weighted_index": 0.5666666666666667 +} diff --git a/docs/audits/2026-09-07-structured-facts/results/validation/2-candidate.json b/docs/audits/2026-09-07-structured-facts/results/validation/2-candidate.json new file mode 100644 index 0000000..0c8187f --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/results/validation/2-candidate.json @@ -0,0 +1,1741 @@ +{ + "generated_at": "2026-09-07T13:25:18.2055245Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-structured-facts\\validation-frozen.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "What is the Lumen staging gateway port?", + "ranked": [ + "stage-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0P2D4HMQX4CKMHHFKYWG2", + "id": "01M1Y0P4A7YB33J30TBF0PJJNQ", + "kind": "memory", + "score": 0.999786913394928, + "summary": "project:fact - Lumen staging gateway port is 7101." + } + ], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [], + "status": "supported", + "subject": "lumen gateway", + "supported": [ + { + "attribute": "port", + "sources": [ + "memory:01M1Y0P2D4HMQX4CKMHHFKYWG2" + ], + "value": "7101" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1861.7631000000001, + "first_query": true, + "server_startup_ms": 71.9683, + "model_text_bytes": 640, + "mcp_result_bytes": 753, + "wire_bytes": 788, + "reported_used_tokens": 753, + "working_set_bytes": 634466304, + "peak_working_set_bytes": 685039616 + }, + { + "query": "What is the Lumen production gateway port?", + "ranked": [ + "prod-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0P2DMWVBGJ2QA6H7YZ2KA", + "id": "01M1Y0P4N4CC65N5Z1BGD0SZKM", + "kind": "memory", + "score": 0.9999414682388306, + "summary": "project:fact - Lumen production gateway port is 8101." + } + ], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [], + "status": "supported", + "subject": "lumen gateway", + "supported": [ + { + "attribute": "port", + "sources": [ + "memory:01M1Y0P2DMWVBGJ2QA6H7YZ2KA" + ], + "value": "8101" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 336.5082, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 647, + "mcp_result_bytes": 760, + "wire_bytes": 795, + "reported_used_tokens": 760, + "working_set_bytes": 634912768, + "peak_working_set_bytes": 685039616 + }, + { + "query": "What are the Lumen staging gateway retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0P2EMFH197467B7148VE7", + "id": "01M1Y0P4ZNKAW3MF5VC075DXDH", + "kind": "memory", + "score": 0.9987403750419616, + "summary": "project:fact - Lumen staging gateway retries are 3." + } + ], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [], + "status": "supported", + "subject": "lumen gateway", + "supported": [ + { + "attribute": "retries", + "sources": [ + "memory:01M1Y0P2EMFH197467B7148VE7" + ], + "value": "3" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 346.8058, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 642, + "mcp_result_bytes": 755, + "wire_bytes": 790, + "reported_used_tokens": 755, + "working_set_bytes": 635109376, + "peak_working_set_bytes": 685039616 + }, + { + "query": "What is `cache.max_entries` for Lumen?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0P2GE2182TFSNCJQ65SQN", + "id": "01M1Y0P5AK1ZXJXWDHVXKBA8BZ", + "kind": "memory", + "score": 0.9999709129333496, + "summary": "project:fact - Lumen cache.max_entries = 200." + } + ], + "answerability": { + "conflicting": [], + "environment": null, + "missing": [], + "status": "supported", + "subject": "lumen", + "supported": [ + { + "attribute": "cache.max_entries", + "sources": [ + "memory:01M1Y0P2GE2182TFSNCJQ65SQN" + ], + "value": "200" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 341.24539999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 635, + "mcp_result_bytes": 746, + "wire_bytes": 781, + "reported_used_tokens": 746, + "working_set_bytes": 637173760, + "peak_working_set_bytes": 685039616 + }, + { + "query": "Which database stores local state for Lumen?", + "ranked": [ + "runtime", + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0P2G4XN000K0DFZ1GDV7G", + "id": "01M1Y0P5NA5ZNZ0PM4GTVHF0DY", + "kind": "memory", + "score": 0.9999797344207764, + "summary": "project:fact - Lumen stores its local state in SQLite using WAL mode." + }, + { + "expansion_handle": "memory:01M1Y0P2EX944Z70ZSCMHQA2C2", + "id": "01M1Y0P5NAXATJW5APS013NBEK", + "kind": "memory", + "score": 0.9386039972305298, + "summary": "project:fact - Lumen production database port is 5432." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 347.26550000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 644, + "mcp_result_bytes": 743, + "wire_bytes": 778, + "reported_used_tokens": 743, + "working_set_bytes": 637267968, + "peak_working_set_bytes": 685039616 + }, + { + "query": "What are the Lumen staging gateway port and password?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port", + "password" + ], + "status": "missing", + "subject": "lumen gateway", + "supported": [] + }, + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 357.6883, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 375, + "mcp_result_bytes": 462, + "wire_bytes": 497, + "reported_used_tokens": 462, + "working_set_bytes": 637382656, + "peak_working_set_bytes": 685039616 + }, + { + "query": "What are the Lumen staging gateway port and retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0P2EMFH197467B7148VE7", + "id": "01M1Y0P6BNDAGBKQRNVQSMFHYA", + "kind": "memory", + "score": 0.9976552724838256, + "summary": "project:fact - Lumen staging gateway retries are 3." + } + ], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port" + ], + "status": "partial", + "subject": "lumen gateway", + "supported": [ + { + "attribute": "retries", + "sources": [ + "memory:01M1Y0P2EMFH197467B7148VE7" + ], + "value": "3" + } + ] + }, + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 380.12620000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 645, + "mcp_result_bytes": 760, + "wire_bytes": 795, + "reported_used_tokens": 760, + "working_set_bytes": 637464576, + "peak_working_set_bytes": 685039616 + }, + { + "query": "What is the Lumen staging gateway timeout?", + "ranked": [ + "timeout-b", + "timeout-a" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0P2E8V5GVQH7HDZAHWHZJ", + "id": "01M1Y0P6QE1AMZJ935GVQZNQ50", + "kind": "memory", + "score": 0.9999746084213256, + "summary": "project:fact - Lumen staging gateway timeout is 45 seconds. The deployment checklist records a different current value." + }, + { + "expansion_handle": "memory:01M1Y0P2DXX8Q58KAD1EEH25VS", + "id": "01M1Y0P6QEMM73ESMV8155A8AK", + "kind": "memory", + "score": 0.9999712705612184, + "summary": "project:fact - Lumen staging gateway timeout is 30 seconds. Operators record this in the request settings." + } + ], + "answerability": { + "conflicting": [ + "timeout" + ], + "environment": "staging", + "missing": [], + "status": "conflicting", + "subject": "lumen gateway", + "supported": [] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 359.2731, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 891, + "mcp_result_bytes": 1012, + "wire_bytes": 1047, + "reported_used_tokens": 1012, + "working_set_bytes": 637534208, + "peak_working_set_bytes": 685039616 + }, + { + "query": "What password does the Lumen production gateway require?", + "ranked": [ + "no-password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0P2F7Q9R5TGE0WDTB2KE2", + "id": "01M1Y0P72GQAQJFBFPD312G4HQ", + "kind": "memory", + "score": 0.9999558925628662, + "summary": "project:fact - No password is required for the Lumen production gateway." + } + ], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [], + "status": "supported", + "subject": "lumen gateway", + "supported": [ + { + "attribute": "password", + "sources": [ + "memory:01M1Y0P2F7Q9R5TGE0WDTB2KE2" + ], + "value": "not required" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 347.8757, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 678, + "mcp_result_bytes": 791, + "wire_bytes": 827, + "reported_used_tokens": 791, + "working_set_bytes": 642359296, + "peak_working_set_bytes": 685039616 + }, + { + "query": "What is the Lumen staging database port?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port" + ], + "status": "missing", + "subject": "lumen database", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 346.05989999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 365, + "mcp_result_bytes": 450, + "wire_bytes": 486, + "reported_used_tokens": 450, + "working_set_bytes": 642359296, + "peak_working_set_bytes": 685039616 + }, + { + "query": "What is the Lumen production gateway timeout?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [ + "timeout" + ], + "status": "missing", + "subject": "lumen gateway", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 342.5394, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 370, + "mcp_result_bytes": 455, + "wire_bytes": 491, + "reported_used_tokens": 455, + "working_set_bytes": 642363392, + "peak_working_set_bytes": 685039616 + }, + { + "query": "What is the Unknown staging gateway port?", + "ranked": [ + "foreign-port", + "stage-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0P2FTAX8XT9ZE0SFJE244", + "id": "01M1Y0P835XYKN8NG5D0KRFQ5Y", + "kind": "memory", + "score": 0.7973032593727112, + "summary": "project:fact - Foreign staging gateway port is 9944." + }, + { + "expansion_handle": "memory:01M1Y0P2D4HMQX4CKMHHFKYWG2", + "id": "01M1Y0P8349RAB4KZKRFG4F1ZT", + "kind": "memory", + "score": 0.7143108248710632, + "summary": "project:fact - Lumen staging gateway port is 7101." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 354.9717, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 622, + "mcp_result_bytes": 721, + "wire_bytes": 757, + "reported_used_tokens": 721, + "working_set_bytes": 642465792, + "peak_working_set_bytes": 685039616 + }, + { + "query": "What is the Lumen test gateway port?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "test", + "missing": [ + "port" + ], + "status": "missing", + "subject": "lumen gateway", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 350.1993, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 361, + "mcp_result_bytes": 446, + "wire_bytes": 482, + "reported_used_tokens": 446, + "working_set_bytes": 642510848, + "peak_working_set_bytes": 685039616 + }, + { + "query": "What are the Lumen production gateway retries?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [ + "retries" + ], + "status": "missing", + "subject": "lumen gateway", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 354.6758, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 370, + "mcp_result_bytes": 455, + "wire_bytes": 491, + "reported_used_tokens": 455, + "working_set_bytes": 642637824, + "peak_working_set_bytes": 685039616 + }, + { + "query": "What is the Lumen staging worker timeout?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "timeout" + ], + "status": "missing", + "subject": "lumen worker", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 358.1914, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 366, + "mcp_result_bytes": 451, + "wire_bytes": 487, + "reported_used_tokens": 451, + "working_set_bytes": 642736128, + "peak_working_set_bytes": 685039616 + } + ], + "id": "lumen-structured-scope", + "dimension": "retrieval", + "tier": "hard", + "score": 0.8333333333333334, + "skipped": false, + "detail": "positive-recall@4=0.83 mrr=0.89 stale-hit=n/a resolution=n/a false-injection=0.167 (n=6) positive-n=9 negative-n=6 (15 queries)" + }, + { + "observations": [ + { + "query": "What is the Harbor staging gateway port?", + "ranked": [ + "stage-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PAGBXFV2B66G2BPQHK9S", + "id": "01M1Y0PCKNA00RERA9AMWJH42A", + "kind": "memory", + "score": 0.9975167512893676, + "summary": "project:fact - Harbor staging gateway port is 7102." + } + ], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [], + "status": "supported", + "subject": "harbor gateway", + "supported": [ + { + "attribute": "port", + "sources": [ + "memory:01M1Y0PAGBXFV2B66G2BPQHK9S" + ], + "value": "7102" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2026.8579, + "first_query": true, + "server_startup_ms": 109.63000000000001, + "model_text_bytes": 643, + "mcp_result_bytes": 756, + "wire_bytes": 791, + "reported_used_tokens": 756, + "working_set_bytes": 632832000, + "peak_working_set_bytes": 684998656 + }, + { + "query": "What is the Harbor production gateway port?", + "ranked": [ + "prod-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PAGVPTE51KVKFZW04GWP", + "id": "01M1Y0PCYPMYQGY637FC3V8479", + "kind": "memory", + "score": 0.9997510313987732, + "summary": "project:fact - Harbor production gateway port is 8102." + } + ], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [], + "status": "supported", + "subject": "harbor gateway", + "supported": [ + { + "attribute": "port", + "sources": [ + "memory:01M1Y0PAGVPTE51KVKFZW04GWP" + ], + "value": "8102" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 339.7532, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 649, + "mcp_result_bytes": 762, + "wire_bytes": 797, + "reported_used_tokens": 762, + "working_set_bytes": 633229312, + "peak_working_set_bytes": 684998656 + }, + { + "query": "What are the Harbor staging gateway retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PAHT983ZMMXFWF7B6MDY", + "id": "01M1Y0PD9HF4DWHEN5FV5HMJQS", + "kind": "memory", + "score": 0.9980675578117372, + "summary": "project:fact - Harbor staging gateway retries are 3." + } + ], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [], + "status": "supported", + "subject": "harbor gateway", + "supported": [ + { + "attribute": "retries", + "sources": [ + "memory:01M1Y0PAHT983ZMMXFWF7B6MDY" + ], + "value": "3" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 347.4554, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 644, + "mcp_result_bytes": 757, + "wire_bytes": 792, + "reported_used_tokens": 757, + "working_set_bytes": 633384960, + "peak_working_set_bytes": 684998656 + }, + { + "query": "What is `cache.max_entries` for Harbor?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PAKK2YJD5R5FMPRYE1CK", + "id": "01M1Y0PDM6BAP0A9RWJP3E6GAJ", + "kind": "memory", + "score": 0.9999717473983764, + "summary": "project:fact - Harbor cache.max_entries = 200." + } + ], + "answerability": { + "conflicting": [], + "environment": null, + "missing": [], + "status": "supported", + "subject": "harbor", + "supported": [ + { + "attribute": "cache.max_entries", + "sources": [ + "memory:01M1Y0PAKK2YJD5R5FMPRYE1CK" + ], + "value": "200" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 345.8969, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 637, + "mcp_result_bytes": 748, + "wire_bytes": 783, + "reported_used_tokens": 748, + "working_set_bytes": 635691008, + "peak_working_set_bytes": 684998656 + }, + { + "query": "Which database stores local state for Harbor?", + "ranked": [ + "runtime", + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PAK9KGF0B1VYYVX6WWEV", + "id": "01M1Y0PDZ91C2T2YCYF7T6XN18", + "kind": "memory", + "score": 0.9999715089797974, + "summary": "project:fact - Harbor stores its local state in SQLite using WAL mode." + }, + { + "expansion_handle": "memory:01M1Y0PAJ34FFHTQ61721FHG26", + "id": "01M1Y0PDZAP68R37PFRNPPJKNJ", + "kind": "memory", + "score": 0.8901878595352173, + "summary": "project:fact - Harbor production database port is 5432." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 348.9192, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 646, + "mcp_result_bytes": 745, + "wire_bytes": 780, + "reported_used_tokens": 745, + "working_set_bytes": 635834368, + "peak_working_set_bytes": 684998656 + }, + { + "query": "What are the Harbor staging gateway port and password?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port", + "password" + ], + "status": "missing", + "subject": "harbor gateway", + "supported": [] + }, + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 358.04110000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 376, + "mcp_result_bytes": 463, + "wire_bytes": 498, + "reported_used_tokens": 463, + "working_set_bytes": 635994112, + "peak_working_set_bytes": 684998656 + }, + { + "query": "What are the Harbor staging gateway port and retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PAHT983ZMMXFWF7B6MDY", + "id": "01M1Y0PEP828WVBBBKZV96PFE2", + "kind": "memory", + "score": 0.9916656613349916, + "summary": "project:fact - Harbor staging gateway retries are 3." + } + ], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port" + ], + "status": "partial", + "subject": "harbor gateway", + "supported": [ + { + "attribute": "retries", + "sources": [ + "memory:01M1Y0PAHT983ZMMXFWF7B6MDY" + ], + "value": "3" + } + ] + }, + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 385.32129999999995, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 647, + "mcp_result_bytes": 762, + "wire_bytes": 797, + "reported_used_tokens": 762, + "working_set_bytes": 636223488, + "peak_working_set_bytes": 684998656 + }, + { + "query": "What is the Harbor staging gateway timeout?", + "ranked": [ + "timeout-b", + "timeout-a" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PAHF0Y4CRCY2R5XE01AC", + "id": "01M1Y0PF16922M7SGQ3DHXVTNP", + "kind": "memory", + "score": 0.9999679327011108, + "summary": "project:fact - Harbor staging gateway timeout is 45 seconds. The deployment checklist records a different current value." + }, + { + "expansion_handle": "memory:01M1Y0PAH4B8AGXBE6KFT8BYA7", + "id": "01M1Y0PF16GPJVX9EYR3NCYY8E", + "kind": "memory", + "score": 0.9999622106552124, + "summary": "project:fact - Harbor staging gateway timeout is 30 seconds. Operators record this in the request settings." + } + ], + "answerability": { + "conflicting": [ + "timeout" + ], + "environment": "staging", + "missing": [], + "status": "conflicting", + "subject": "harbor gateway", + "supported": [] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 348.0892, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 894, + "mcp_result_bytes": 1015, + "wire_bytes": 1050, + "reported_used_tokens": 1015, + "working_set_bytes": 636289024, + "peak_working_set_bytes": 684998656 + }, + { + "query": "What password does the Harbor production gateway require?", + "ranked": [ + "no-password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PAJCC4PZECM6XGJCCYH1", + "id": "01M1Y0PFC4NGV14X2EY6PZGSYN", + "kind": "memory", + "score": 0.999948024749756, + "summary": "project:fact - No password is required for the Harbor production gateway." + } + ], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [], + "status": "supported", + "subject": "harbor gateway", + "supported": [ + { + "attribute": "password", + "sources": [ + "memory:01M1Y0PAJCC4PZECM6XGJCCYH1" + ], + "value": "not required" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 348.6189, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 680, + "mcp_result_bytes": 793, + "wire_bytes": 829, + "reported_used_tokens": 793, + "working_set_bytes": 640974848, + "peak_working_set_bytes": 684998656 + }, + { + "query": "What is the Harbor staging database port?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port" + ], + "status": "missing", + "subject": "harbor database", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 348.33570000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 366, + "mcp_result_bytes": 451, + "wire_bytes": 487, + "reported_used_tokens": 451, + "working_set_bytes": 641003520, + "peak_working_set_bytes": 684998656 + }, + { + "query": "What is the Harbor production gateway timeout?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [ + "timeout" + ], + "status": "missing", + "subject": "harbor gateway", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 354.03790000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 372, + "mcp_result_bytes": 457, + "wire_bytes": 493, + "reported_used_tokens": 457, + "working_set_bytes": 641007616, + "peak_working_set_bytes": 684998656 + }, + { + "query": "What is the Unknown staging gateway port?", + "ranked": [ + "foreign-port", + "stage-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PAJZGF324N1QFMA7MGHV", + "id": "01M1Y0PGEM0MPXQM82HKSZB28T", + "kind": "memory", + "score": 0.873483419418335, + "summary": "project:fact - Foreign staging gateway port is 9944." + }, + { + "expansion_handle": "memory:01M1Y0PAGBXFV2B66G2BPQHK9S", + "id": "01M1Y0PGEMD2NCGS7PQ8GGFCDR", + "kind": "memory", + "score": 0.7721561789512634, + "summary": "project:fact - Harbor staging gateway port is 7102." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 409.16560000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 622, + "mcp_result_bytes": 721, + "wire_bytes": 757, + "reported_used_tokens": 721, + "working_set_bytes": 641024000, + "peak_working_set_bytes": 684998656 + }, + { + "query": "What is the Harbor test gateway port?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "test", + "missing": [ + "port" + ], + "status": "missing", + "subject": "harbor gateway", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 405.8336, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 362, + "mcp_result_bytes": 447, + "wire_bytes": 483, + "reported_used_tokens": 447, + "working_set_bytes": 641040384, + "peak_working_set_bytes": 684998656 + }, + { + "query": "What are the Harbor production gateway retries?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [ + "retries" + ], + "status": "missing", + "subject": "harbor gateway", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 392.4661, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 371, + "mcp_result_bytes": 456, + "wire_bytes": 492, + "reported_used_tokens": 456, + "working_set_bytes": 641089536, + "peak_working_set_bytes": 684998656 + }, + { + "query": "What is the Harbor staging worker timeout?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "timeout" + ], + "status": "missing", + "subject": "harbor worker", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 385.4673, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 367, + "mcp_result_bytes": 452, + "wire_bytes": 488, + "reported_used_tokens": 452, + "working_set_bytes": 641159168, + "peak_working_set_bytes": 684998656 + } + ], + "id": "harbor-structured-scope", + "dimension": "retrieval", + "tier": "hard", + "score": 0.8333333333333334, + "skipped": false, + "detail": "positive-recall@4=0.83 mrr=0.89 stale-hit=n/a resolution=n/a false-injection=0.167 (n=6) positive-n=9 negative-n=6 (15 queries)" + }, + { + "observations": [ + { + "query": "What is the Sable staging gateway port?", + "ranked": [ + "stage-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PK866758C6STCZQVQ554", + "id": "01M1Y0PNP39WMR7YANPFRTPBWP", + "kind": "memory", + "score": 0.9981032609939576, + "summary": "project:fact - Sable staging gateway port is 7103." + } + ], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [], + "status": "supported", + "subject": "sable gateway", + "supported": [ + { + "attribute": "port", + "sources": [ + "memory:01M1Y0PK866758C6STCZQVQ554" + ], + "value": "7103" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2354.7148, + "first_query": true, + "server_startup_ms": 73.96610000000001, + "model_text_bytes": 641, + "mcp_result_bytes": 754, + "wire_bytes": 789, + "reported_used_tokens": 754, + "working_set_bytes": 635375616, + "peak_working_set_bytes": 684900352 + }, + { + "query": "What is the Sable production gateway port?", + "ranked": [ + "prod-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PK8PZWPJWR5PPMDYG26M", + "id": "01M1Y0PP113HZDD5PP2NXVEVP3", + "kind": "memory", + "score": 0.9996471405029296, + "summary": "project:fact - Sable production gateway port is 8103." + } + ], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [], + "status": "supported", + "subject": "sable gateway", + "supported": [ + { + "attribute": "port", + "sources": [ + "memory:01M1Y0PK8PZWPJWR5PPMDYG26M" + ], + "value": "8103" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 339.63100000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 647, + "mcp_result_bytes": 760, + "wire_bytes": 795, + "reported_used_tokens": 760, + "working_set_bytes": 635801600, + "peak_working_set_bytes": 684900352 + }, + { + "query": "What are the Sable staging gateway retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PK9Q5NNQDBCP58G4DGKV", + "id": "01M1Y0PPBZ25S6W76HWHAYGSCH", + "kind": "memory", + "score": 0.9971465468406676, + "summary": "project:fact - Sable staging gateway retries are 3." + } + ], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [], + "status": "supported", + "subject": "sable gateway", + "supported": [ + { + "attribute": "retries", + "sources": [ + "memory:01M1Y0PK9Q5NNQDBCP58G4DGKV" + ], + "value": "3" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 353.3684, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 642, + "mcp_result_bytes": 755, + "wire_bytes": 790, + "reported_used_tokens": 755, + "working_set_bytes": 635912192, + "peak_working_set_bytes": 684900352 + }, + { + "query": "What is `cache.max_entries` for Sable?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PKBFV2262AJ8MPFYNCQD", + "id": "01M1Y0PPPWPCHFRC4FT032TS03", + "kind": "memory", + "score": 0.9999657869338988, + "summary": "project:fact - Sable cache.max_entries = 200." + } + ], + "answerability": { + "conflicting": [], + "environment": null, + "missing": [], + "status": "supported", + "subject": "sable", + "supported": [ + { + "attribute": "cache.max_entries", + "sources": [ + "memory:01M1Y0PKBFV2262AJ8MPFYNCQD" + ], + "value": "200" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 367.2114, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 635, + "mcp_result_bytes": 746, + "wire_bytes": 781, + "reported_used_tokens": 746, + "working_set_bytes": 637939712, + "peak_working_set_bytes": 684900352 + }, + { + "query": "Which database stores local state for Sable?", + "ranked": [ + "runtime", + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PKB5FKQT86DD2P22GC4B", + "id": "01M1Y0PQ2PTS9YZTFPRA6JQZ28", + "kind": "memory", + "score": 0.9999470710754396, + "summary": "project:fact - Sable stores its local state in SQLite using WAL mode." + }, + { + "expansion_handle": "memory:01M1Y0PKA0J6P05F03M8RAM3ER", + "id": "01M1Y0PQ2PCSFC8VR1D0H23GCH", + "kind": "memory", + "score": 0.7779185175895691, + "summary": "project:fact - Sable production database port is 5432." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 357.5433, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 644, + "mcp_result_bytes": 743, + "wire_bytes": 778, + "reported_used_tokens": 743, + "working_set_bytes": 637984768, + "peak_working_set_bytes": 684900352 + }, + { + "query": "What are the Sable staging gateway port and password?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port", + "password" + ], + "status": "missing", + "subject": "sable gateway", + "supported": [] + }, + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.7282, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 375, + "mcp_result_bytes": 462, + "wire_bytes": 497, + "reported_used_tokens": 462, + "working_set_bytes": 638083072, + "peak_working_set_bytes": 684900352 + }, + { + "query": "What are the Sable staging gateway port and retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PK9Q5NNQDBCP58G4DGKV", + "id": "01M1Y0PQSP0DPJEWAJG430EAB9", + "kind": "memory", + "score": 0.9917003512382508, + "summary": "project:fact - Sable staging gateway retries are 3." + } + ], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port" + ], + "status": "partial", + "subject": "sable gateway", + "supported": [ + { + "attribute": "retries", + "sources": [ + "memory:01M1Y0PK9Q5NNQDBCP58G4DGKV" + ], + "value": "3" + } + ] + }, + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 386.5749, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 645, + "mcp_result_bytes": 760, + "wire_bytes": 795, + "reported_used_tokens": 760, + "working_set_bytes": 638279680, + "peak_working_set_bytes": 684900352 + }, + { + "query": "What is the Sable staging gateway timeout?", + "ranked": [ + "timeout-b", + "timeout-a" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PK9C24APYRKZ59M4CGZF", + "id": "01M1Y0PR4S81RX1S517XFAXKZE", + "kind": "memory", + "score": 0.9999639987945556, + "summary": "project:fact - Sable staging gateway timeout is 45 seconds. The deployment checklist records a different current value." + }, + { + "expansion_handle": "memory:01M1Y0PK90E11XHQE8DC47F89E", + "id": "01M1Y0PR4SEWRQD7P0HDPHR7SM", + "kind": "memory", + "score": 0.9999442100524902, + "summary": "project:fact - Sable staging gateway timeout is 30 seconds. Operators record this in the request settings." + } + ], + "answerability": { + "conflicting": [ + "timeout" + ], + "environment": "staging", + "missing": [], + "status": "conflicting", + "subject": "sable gateway", + "supported": [] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 352.3854, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 891, + "mcp_result_bytes": 1012, + "wire_bytes": 1047, + "reported_used_tokens": 1012, + "working_set_bytes": 638455808, + "peak_working_set_bytes": 684900352 + }, + { + "query": "What password does the Sable production gateway require?", + "ranked": [ + "no-password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PKA9CGHM9BCQQ83GXPMZ", + "id": "01M1Y0PRFPGY4ANZBRSZ0Y9BFM", + "kind": "memory", + "score": 0.9999281167984008, + "summary": "project:fact - No password is required for the Sable production gateway." + } + ], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [], + "status": "supported", + "subject": "sable gateway", + "supported": [ + { + "attribute": "password", + "sources": [ + "memory:01M1Y0PKA9CGHM9BCQQ83GXPMZ" + ], + "value": "not required" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 348.2401, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 678, + "mcp_result_bytes": 791, + "wire_bytes": 827, + "reported_used_tokens": 791, + "working_set_bytes": 643198976, + "peak_working_set_bytes": 684900352 + }, + { + "query": "What is the Sable staging database port?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port" + ], + "status": "missing", + "subject": "sable database", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 351.6295, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 365, + "mcp_result_bytes": 450, + "wire_bytes": 486, + "reported_used_tokens": 450, + "working_set_bytes": 643256320, + "peak_working_set_bytes": 684900352 + }, + { + "query": "What is the Sable production gateway timeout?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [ + "timeout" + ], + "status": "missing", + "subject": "sable gateway", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 346.0297, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 370, + "mcp_result_bytes": 455, + "wire_bytes": 491, + "reported_used_tokens": 455, + "working_set_bytes": 643297280, + "peak_working_set_bytes": 684900352 + }, + { + "query": "What is the Unknown staging gateway port?", + "ranked": [ + "stage-port", + "foreign-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PK866758C6STCZQVQ554", + "id": "01M1Y0PSGSJVKMCGT6QYRYDG2G", + "kind": "memory", + "score": 0.8858267068862915, + "summary": "project:fact - Sable staging gateway port is 7103." + }, + { + "expansion_handle": "memory:01M1Y0PKAWEK94Z6EXD6T47JWJ", + "id": "01M1Y0PSGS5S4PN8H66BV9WVRQ", + "kind": "memory", + "score": 0.8669298887252808, + "summary": "project:fact - Foreign staging gateway port is 9944." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 365.358, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 622, + "mcp_result_bytes": 721, + "wire_bytes": 757, + "reported_used_tokens": 721, + "working_set_bytes": 643350528, + "peak_working_set_bytes": 684900352 + }, + { + "query": "What is the Sable test gateway port?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "test", + "missing": [ + "port" + ], + "status": "missing", + "subject": "sable gateway", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 345.90110000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 361, + "mcp_result_bytes": 446, + "wire_bytes": 482, + "reported_used_tokens": 446, + "working_set_bytes": 643432448, + "peak_working_set_bytes": 684900352 + }, + { + "query": "What are the Sable production gateway retries?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [ + "retries" + ], + "status": "missing", + "subject": "sable gateway", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 355.3758, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 370, + "mcp_result_bytes": 455, + "wire_bytes": 491, + "reported_used_tokens": 455, + "working_set_bytes": 643448832, + "peak_working_set_bytes": 684900352 + }, + { + "query": "What is the Sable staging worker timeout?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "timeout" + ], + "status": "missing", + "subject": "sable worker", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 359.8004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 366, + "mcp_result_bytes": 451, + "wire_bytes": 487, + "reported_used_tokens": 451, + "working_set_bytes": 643506176, + "peak_working_set_bytes": 684900352 + } + ], + "id": "sable-structured-scope", + "dimension": "retrieval", + "tier": "hard", + "score": 0.8333333333333334, + "skipped": false, + "detail": "positive-recall@4=0.83 mrr=0.89 stale-hit=n/a resolution=n/a false-injection=0.167 (n=6) positive-n=9 negative-n=6 (15 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 2.5, + 3 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 0.8333333333333334, + "n": 3, + "ci95": 0.0 + } + }, + "overall_index": 0.8333333333333334, + "scenario_weighted_index": 0.8333333333333334 +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-structured-facts/results/validation/2-candidate.stderr.log b/docs/audits/2026-09-07-structured-facts/results/validation/2-candidate.stderr.log new file mode 100644 index 0000000..b30108b --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/results/validation/2-candidate.stderr.log @@ -0,0 +1,8 @@ +brainbench: 3 scenario(s) to run + [1/3] lumen-structured-scope | dim=retrieval tier=hard ... + -> score=0.83 | positive-recall@4=0.83 mrr=0.89 stale-hit=n/a resolution=n/a false-injection=0.167 (n=6) positive-n=9 negative-n=6 (15 queries) + [2/3] harbor-structured-scope | dim=retrieval tier=hard ... + -> score=0.83 | positive-recall@4=0.83 mrr=0.89 stale-hit=n/a resolution=n/a false-injection=0.167 (n=6) positive-n=9 negative-n=6 (15 queries) + [3/3] sable-structured-scope | dim=retrieval tier=hard ... + -> score=0.83 | positive-recall@4=0.83 mrr=0.89 stale-hit=n/a resolution=n/a false-injection=0.167 (n=6) positive-n=9 negative-n=6 (15 queries) +kbench brainbench: report saved -> E:\tmp\kimetsu-brain-hardening\bench\local\runs\brainbench\2026-09-07T13-25-18.2058846Z.json diff --git a/docs/audits/2026-09-07-structured-facts/results/validation/2-candidate.stdout.log b/docs/audits/2026-09-07-structured-facts/results/validation/2-candidate.stdout.log new file mode 100644 index 0000000..ed61f20 --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/results/validation/2-candidate.stdout.log @@ -0,0 +1,1741 @@ +{ + "generated_at": "2026-09-07T13:25:18.2055245Z", + "dataset": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-structured-facts\\validation-frozen.json", + "session_configuration": { + "include_ambient": false, + "warm_start": false + }, + "scenarios": [ + { + "observations": [ + { + "query": "What is the Lumen staging gateway port?", + "ranked": [ + "stage-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0P2D4HMQX4CKMHHFKYWG2", + "id": "01M1Y0P4A7YB33J30TBF0PJJNQ", + "kind": "memory", + "score": 0.999786913394928, + "summary": "project:fact - Lumen staging gateway port is 7101." + } + ], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [], + "status": "supported", + "subject": "lumen gateway", + "supported": [ + { + "attribute": "port", + "sources": [ + "memory:01M1Y0P2D4HMQX4CKMHHFKYWG2" + ], + "value": "7101" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 1861.7631000000001, + "first_query": true, + "server_startup_ms": 71.9683, + "model_text_bytes": 640, + "mcp_result_bytes": 753, + "wire_bytes": 788, + "reported_used_tokens": 753, + "working_set_bytes": 634466304, + "peak_working_set_bytes": 685039616 + }, + { + "query": "What is the Lumen production gateway port?", + "ranked": [ + "prod-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0P2DMWVBGJ2QA6H7YZ2KA", + "id": "01M1Y0P4N4CC65N5Z1BGD0SZKM", + "kind": "memory", + "score": 0.9999414682388306, + "summary": "project:fact - Lumen production gateway port is 8101." + } + ], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [], + "status": "supported", + "subject": "lumen gateway", + "supported": [ + { + "attribute": "port", + "sources": [ + "memory:01M1Y0P2DMWVBGJ2QA6H7YZ2KA" + ], + "value": "8101" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 336.5082, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 647, + "mcp_result_bytes": 760, + "wire_bytes": 795, + "reported_used_tokens": 760, + "working_set_bytes": 634912768, + "peak_working_set_bytes": 685039616 + }, + { + "query": "What are the Lumen staging gateway retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0P2EMFH197467B7148VE7", + "id": "01M1Y0P4ZNKAW3MF5VC075DXDH", + "kind": "memory", + "score": 0.9987403750419616, + "summary": "project:fact - Lumen staging gateway retries are 3." + } + ], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [], + "status": "supported", + "subject": "lumen gateway", + "supported": [ + { + "attribute": "retries", + "sources": [ + "memory:01M1Y0P2EMFH197467B7148VE7" + ], + "value": "3" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 346.8058, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 642, + "mcp_result_bytes": 755, + "wire_bytes": 790, + "reported_used_tokens": 755, + "working_set_bytes": 635109376, + "peak_working_set_bytes": 685039616 + }, + { + "query": "What is `cache.max_entries` for Lumen?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0P2GE2182TFSNCJQ65SQN", + "id": "01M1Y0P5AK1ZXJXWDHVXKBA8BZ", + "kind": "memory", + "score": 0.9999709129333496, + "summary": "project:fact - Lumen cache.max_entries = 200." + } + ], + "answerability": { + "conflicting": [], + "environment": null, + "missing": [], + "status": "supported", + "subject": "lumen", + "supported": [ + { + "attribute": "cache.max_entries", + "sources": [ + "memory:01M1Y0P2GE2182TFSNCJQ65SQN" + ], + "value": "200" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 341.24539999999996, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 635, + "mcp_result_bytes": 746, + "wire_bytes": 781, + "reported_used_tokens": 746, + "working_set_bytes": 637173760, + "peak_working_set_bytes": 685039616 + }, + { + "query": "Which database stores local state for Lumen?", + "ranked": [ + "runtime", + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0P2G4XN000K0DFZ1GDV7G", + "id": "01M1Y0P5NA5ZNZ0PM4GTVHF0DY", + "kind": "memory", + "score": 0.9999797344207764, + "summary": "project:fact - Lumen stores its local state in SQLite using WAL mode." + }, + { + "expansion_handle": "memory:01M1Y0P2EX944Z70ZSCMHQA2C2", + "id": "01M1Y0P5NAXATJW5APS013NBEK", + "kind": "memory", + "score": 0.9386039972305298, + "summary": "project:fact - Lumen production database port is 5432." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 347.26550000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 644, + "mcp_result_bytes": 743, + "wire_bytes": 778, + "reported_used_tokens": 743, + "working_set_bytes": 637267968, + "peak_working_set_bytes": 685039616 + }, + { + "query": "What are the Lumen staging gateway port and password?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port", + "password" + ], + "status": "missing", + "subject": "lumen gateway", + "supported": [] + }, + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 357.6883, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 375, + "mcp_result_bytes": 462, + "wire_bytes": 497, + "reported_used_tokens": 462, + "working_set_bytes": 637382656, + "peak_working_set_bytes": 685039616 + }, + { + "query": "What are the Lumen staging gateway port and retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0P2EMFH197467B7148VE7", + "id": "01M1Y0P6BNDAGBKQRNVQSMFHYA", + "kind": "memory", + "score": 0.9976552724838256, + "summary": "project:fact - Lumen staging gateway retries are 3." + } + ], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port" + ], + "status": "partial", + "subject": "lumen gateway", + "supported": [ + { + "attribute": "retries", + "sources": [ + "memory:01M1Y0P2EMFH197467B7148VE7" + ], + "value": "3" + } + ] + }, + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 380.12620000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 645, + "mcp_result_bytes": 760, + "wire_bytes": 795, + "reported_used_tokens": 760, + "working_set_bytes": 637464576, + "peak_working_set_bytes": 685039616 + }, + { + "query": "What is the Lumen staging gateway timeout?", + "ranked": [ + "timeout-b", + "timeout-a" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0P2E8V5GVQH7HDZAHWHZJ", + "id": "01M1Y0P6QE1AMZJ935GVQZNQ50", + "kind": "memory", + "score": 0.9999746084213256, + "summary": "project:fact - Lumen staging gateway timeout is 45 seconds. The deployment checklist records a different current value." + }, + { + "expansion_handle": "memory:01M1Y0P2DXX8Q58KAD1EEH25VS", + "id": "01M1Y0P6QEMM73ESMV8155A8AK", + "kind": "memory", + "score": 0.9999712705612184, + "summary": "project:fact - Lumen staging gateway timeout is 30 seconds. Operators record this in the request settings." + } + ], + "answerability": { + "conflicting": [ + "timeout" + ], + "environment": "staging", + "missing": [], + "status": "conflicting", + "subject": "lumen gateway", + "supported": [] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 359.2731, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 891, + "mcp_result_bytes": 1012, + "wire_bytes": 1047, + "reported_used_tokens": 1012, + "working_set_bytes": 637534208, + "peak_working_set_bytes": 685039616 + }, + { + "query": "What password does the Lumen production gateway require?", + "ranked": [ + "no-password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0P2F7Q9R5TGE0WDTB2KE2", + "id": "01M1Y0P72GQAQJFBFPD312G4HQ", + "kind": "memory", + "score": 0.9999558925628662, + "summary": "project:fact - No password is required for the Lumen production gateway." + } + ], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [], + "status": "supported", + "subject": "lumen gateway", + "supported": [ + { + "attribute": "password", + "sources": [ + "memory:01M1Y0P2F7Q9R5TGE0WDTB2KE2" + ], + "value": "not required" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 347.8757, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 678, + "mcp_result_bytes": 791, + "wire_bytes": 827, + "reported_used_tokens": 791, + "working_set_bytes": 642359296, + "peak_working_set_bytes": 685039616 + }, + { + "query": "What is the Lumen staging database port?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port" + ], + "status": "missing", + "subject": "lumen database", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 346.05989999999997, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 365, + "mcp_result_bytes": 450, + "wire_bytes": 486, + "reported_used_tokens": 450, + "working_set_bytes": 642359296, + "peak_working_set_bytes": 685039616 + }, + { + "query": "What is the Lumen production gateway timeout?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [ + "timeout" + ], + "status": "missing", + "subject": "lumen gateway", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 342.5394, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 370, + "mcp_result_bytes": 455, + "wire_bytes": 491, + "reported_used_tokens": 455, + "working_set_bytes": 642363392, + "peak_working_set_bytes": 685039616 + }, + { + "query": "What is the Unknown staging gateway port?", + "ranked": [ + "foreign-port", + "stage-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0P2FTAX8XT9ZE0SFJE244", + "id": "01M1Y0P835XYKN8NG5D0KRFQ5Y", + "kind": "memory", + "score": 0.7973032593727112, + "summary": "project:fact - Foreign staging gateway port is 9944." + }, + { + "expansion_handle": "memory:01M1Y0P2D4HMQX4CKMHHFKYWG2", + "id": "01M1Y0P8349RAB4KZKRFG4F1ZT", + "kind": "memory", + "score": 0.7143108248710632, + "summary": "project:fact - Lumen staging gateway port is 7101." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 354.9717, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 622, + "mcp_result_bytes": 721, + "wire_bytes": 757, + "reported_used_tokens": 721, + "working_set_bytes": 642465792, + "peak_working_set_bytes": 685039616 + }, + { + "query": "What is the Lumen test gateway port?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "test", + "missing": [ + "port" + ], + "status": "missing", + "subject": "lumen gateway", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 350.1993, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 361, + "mcp_result_bytes": 446, + "wire_bytes": 482, + "reported_used_tokens": 446, + "working_set_bytes": 642510848, + "peak_working_set_bytes": 685039616 + }, + { + "query": "What are the Lumen production gateway retries?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [ + "retries" + ], + "status": "missing", + "subject": "lumen gateway", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 354.6758, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 370, + "mcp_result_bytes": 455, + "wire_bytes": 491, + "reported_used_tokens": 455, + "working_set_bytes": 642637824, + "peak_working_set_bytes": 685039616 + }, + { + "query": "What is the Lumen staging worker timeout?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "timeout" + ], + "status": "missing", + "subject": "lumen worker", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 358.1914, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 366, + "mcp_result_bytes": 451, + "wire_bytes": 487, + "reported_used_tokens": 451, + "working_set_bytes": 642736128, + "peak_working_set_bytes": 685039616 + } + ], + "id": "lumen-structured-scope", + "dimension": "retrieval", + "tier": "hard", + "score": 0.8333333333333334, + "skipped": false, + "detail": "positive-recall@4=0.83 mrr=0.89 stale-hit=n/a resolution=n/a false-injection=0.167 (n=6) positive-n=9 negative-n=6 (15 queries)" + }, + { + "observations": [ + { + "query": "What is the Harbor staging gateway port?", + "ranked": [ + "stage-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PAGBXFV2B66G2BPQHK9S", + "id": "01M1Y0PCKNA00RERA9AMWJH42A", + "kind": "memory", + "score": 0.9975167512893676, + "summary": "project:fact - Harbor staging gateway port is 7102." + } + ], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [], + "status": "supported", + "subject": "harbor gateway", + "supported": [ + { + "attribute": "port", + "sources": [ + "memory:01M1Y0PAGBXFV2B66G2BPQHK9S" + ], + "value": "7102" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2026.8579, + "first_query": true, + "server_startup_ms": 109.63000000000001, + "model_text_bytes": 643, + "mcp_result_bytes": 756, + "wire_bytes": 791, + "reported_used_tokens": 756, + "working_set_bytes": 632832000, + "peak_working_set_bytes": 684998656 + }, + { + "query": "What is the Harbor production gateway port?", + "ranked": [ + "prod-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PAGVPTE51KVKFZW04GWP", + "id": "01M1Y0PCYPMYQGY637FC3V8479", + "kind": "memory", + "score": 0.9997510313987732, + "summary": "project:fact - Harbor production gateway port is 8102." + } + ], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [], + "status": "supported", + "subject": "harbor gateway", + "supported": [ + { + "attribute": "port", + "sources": [ + "memory:01M1Y0PAGVPTE51KVKFZW04GWP" + ], + "value": "8102" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 339.7532, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 649, + "mcp_result_bytes": 762, + "wire_bytes": 797, + "reported_used_tokens": 762, + "working_set_bytes": 633229312, + "peak_working_set_bytes": 684998656 + }, + { + "query": "What are the Harbor staging gateway retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PAHT983ZMMXFWF7B6MDY", + "id": "01M1Y0PD9HF4DWHEN5FV5HMJQS", + "kind": "memory", + "score": 0.9980675578117372, + "summary": "project:fact - Harbor staging gateway retries are 3." + } + ], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [], + "status": "supported", + "subject": "harbor gateway", + "supported": [ + { + "attribute": "retries", + "sources": [ + "memory:01M1Y0PAHT983ZMMXFWF7B6MDY" + ], + "value": "3" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 347.4554, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 644, + "mcp_result_bytes": 757, + "wire_bytes": 792, + "reported_used_tokens": 757, + "working_set_bytes": 633384960, + "peak_working_set_bytes": 684998656 + }, + { + "query": "What is `cache.max_entries` for Harbor?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PAKK2YJD5R5FMPRYE1CK", + "id": "01M1Y0PDM6BAP0A9RWJP3E6GAJ", + "kind": "memory", + "score": 0.9999717473983764, + "summary": "project:fact - Harbor cache.max_entries = 200." + } + ], + "answerability": { + "conflicting": [], + "environment": null, + "missing": [], + "status": "supported", + "subject": "harbor", + "supported": [ + { + "attribute": "cache.max_entries", + "sources": [ + "memory:01M1Y0PAKK2YJD5R5FMPRYE1CK" + ], + "value": "200" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 345.8969, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 637, + "mcp_result_bytes": 748, + "wire_bytes": 783, + "reported_used_tokens": 748, + "working_set_bytes": 635691008, + "peak_working_set_bytes": 684998656 + }, + { + "query": "Which database stores local state for Harbor?", + "ranked": [ + "runtime", + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PAK9KGF0B1VYYVX6WWEV", + "id": "01M1Y0PDZ91C2T2YCYF7T6XN18", + "kind": "memory", + "score": 0.9999715089797974, + "summary": "project:fact - Harbor stores its local state in SQLite using WAL mode." + }, + { + "expansion_handle": "memory:01M1Y0PAJ34FFHTQ61721FHG26", + "id": "01M1Y0PDZAP68R37PFRNPPJKNJ", + "kind": "memory", + "score": 0.8901878595352173, + "summary": "project:fact - Harbor production database port is 5432." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 348.9192, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 646, + "mcp_result_bytes": 745, + "wire_bytes": 780, + "reported_used_tokens": 745, + "working_set_bytes": 635834368, + "peak_working_set_bytes": 684998656 + }, + { + "query": "What are the Harbor staging gateway port and password?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port", + "password" + ], + "status": "missing", + "subject": "harbor gateway", + "supported": [] + }, + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 358.04110000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 376, + "mcp_result_bytes": 463, + "wire_bytes": 498, + "reported_used_tokens": 463, + "working_set_bytes": 635994112, + "peak_working_set_bytes": 684998656 + }, + { + "query": "What are the Harbor staging gateway port and retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PAHT983ZMMXFWF7B6MDY", + "id": "01M1Y0PEP828WVBBBKZV96PFE2", + "kind": "memory", + "score": 0.9916656613349916, + "summary": "project:fact - Harbor staging gateway retries are 3." + } + ], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port" + ], + "status": "partial", + "subject": "harbor gateway", + "supported": [ + { + "attribute": "retries", + "sources": [ + "memory:01M1Y0PAHT983ZMMXFWF7B6MDY" + ], + "value": "3" + } + ] + }, + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 385.32129999999995, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 647, + "mcp_result_bytes": 762, + "wire_bytes": 797, + "reported_used_tokens": 762, + "working_set_bytes": 636223488, + "peak_working_set_bytes": 684998656 + }, + { + "query": "What is the Harbor staging gateway timeout?", + "ranked": [ + "timeout-b", + "timeout-a" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PAHF0Y4CRCY2R5XE01AC", + "id": "01M1Y0PF16922M7SGQ3DHXVTNP", + "kind": "memory", + "score": 0.9999679327011108, + "summary": "project:fact - Harbor staging gateway timeout is 45 seconds. The deployment checklist records a different current value." + }, + { + "expansion_handle": "memory:01M1Y0PAH4B8AGXBE6KFT8BYA7", + "id": "01M1Y0PF16GPJVX9EYR3NCYY8E", + "kind": "memory", + "score": 0.9999622106552124, + "summary": "project:fact - Harbor staging gateway timeout is 30 seconds. Operators record this in the request settings." + } + ], + "answerability": { + "conflicting": [ + "timeout" + ], + "environment": "staging", + "missing": [], + "status": "conflicting", + "subject": "harbor gateway", + "supported": [] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 348.0892, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 894, + "mcp_result_bytes": 1015, + "wire_bytes": 1050, + "reported_used_tokens": 1015, + "working_set_bytes": 636289024, + "peak_working_set_bytes": 684998656 + }, + { + "query": "What password does the Harbor production gateway require?", + "ranked": [ + "no-password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PAJCC4PZECM6XGJCCYH1", + "id": "01M1Y0PFC4NGV14X2EY6PZGSYN", + "kind": "memory", + "score": 0.999948024749756, + "summary": "project:fact - No password is required for the Harbor production gateway." + } + ], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [], + "status": "supported", + "subject": "harbor gateway", + "supported": [ + { + "attribute": "password", + "sources": [ + "memory:01M1Y0PAJCC4PZECM6XGJCCYH1" + ], + "value": "not required" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 348.6189, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 680, + "mcp_result_bytes": 793, + "wire_bytes": 829, + "reported_used_tokens": 793, + "working_set_bytes": 640974848, + "peak_working_set_bytes": 684998656 + }, + { + "query": "What is the Harbor staging database port?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port" + ], + "status": "missing", + "subject": "harbor database", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 348.33570000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 366, + "mcp_result_bytes": 451, + "wire_bytes": 487, + "reported_used_tokens": 451, + "working_set_bytes": 641003520, + "peak_working_set_bytes": 684998656 + }, + { + "query": "What is the Harbor production gateway timeout?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [ + "timeout" + ], + "status": "missing", + "subject": "harbor gateway", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 354.03790000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 372, + "mcp_result_bytes": 457, + "wire_bytes": 493, + "reported_used_tokens": 457, + "working_set_bytes": 641007616, + "peak_working_set_bytes": 684998656 + }, + { + "query": "What is the Unknown staging gateway port?", + "ranked": [ + "foreign-port", + "stage-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PAJZGF324N1QFMA7MGHV", + "id": "01M1Y0PGEM0MPXQM82HKSZB28T", + "kind": "memory", + "score": 0.873483419418335, + "summary": "project:fact - Foreign staging gateway port is 9944." + }, + { + "expansion_handle": "memory:01M1Y0PAGBXFV2B66G2BPQHK9S", + "id": "01M1Y0PGEMD2NCGS7PQ8GGFCDR", + "kind": "memory", + "score": 0.7721561789512634, + "summary": "project:fact - Harbor staging gateway port is 7102." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 409.16560000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 622, + "mcp_result_bytes": 721, + "wire_bytes": 757, + "reported_used_tokens": 721, + "working_set_bytes": 641024000, + "peak_working_set_bytes": 684998656 + }, + { + "query": "What is the Harbor test gateway port?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "test", + "missing": [ + "port" + ], + "status": "missing", + "subject": "harbor gateway", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 405.8336, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 362, + "mcp_result_bytes": 447, + "wire_bytes": 483, + "reported_used_tokens": 447, + "working_set_bytes": 641040384, + "peak_working_set_bytes": 684998656 + }, + { + "query": "What are the Harbor production gateway retries?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [ + "retries" + ], + "status": "missing", + "subject": "harbor gateway", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 392.4661, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 371, + "mcp_result_bytes": 456, + "wire_bytes": 492, + "reported_used_tokens": 456, + "working_set_bytes": 641089536, + "peak_working_set_bytes": 684998656 + }, + { + "query": "What is the Harbor staging worker timeout?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "timeout" + ], + "status": "missing", + "subject": "harbor worker", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 385.4673, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 367, + "mcp_result_bytes": 452, + "wire_bytes": 488, + "reported_used_tokens": 452, + "working_set_bytes": 641159168, + "peak_working_set_bytes": 684998656 + } + ], + "id": "harbor-structured-scope", + "dimension": "retrieval", + "tier": "hard", + "score": 0.8333333333333334, + "skipped": false, + "detail": "positive-recall@4=0.83 mrr=0.89 stale-hit=n/a resolution=n/a false-injection=0.167 (n=6) positive-n=9 negative-n=6 (15 queries)" + }, + { + "observations": [ + { + "query": "What is the Sable staging gateway port?", + "ranked": [ + "stage-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PK866758C6STCZQVQ554", + "id": "01M1Y0PNP39WMR7YANPFRTPBWP", + "kind": "memory", + "score": 0.9981032609939576, + "summary": "project:fact - Sable staging gateway port is 7103." + } + ], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [], + "status": "supported", + "subject": "sable gateway", + "supported": [ + { + "attribute": "port", + "sources": [ + "memory:01M1Y0PK866758C6STCZQVQ554" + ], + "value": "7103" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 2354.7148, + "first_query": true, + "server_startup_ms": 73.96610000000001, + "model_text_bytes": 641, + "mcp_result_bytes": 754, + "wire_bytes": 789, + "reported_used_tokens": 754, + "working_set_bytes": 635375616, + "peak_working_set_bytes": 684900352 + }, + { + "query": "What is the Sable production gateway port?", + "ranked": [ + "prod-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PK8PZWPJWR5PPMDYG26M", + "id": "01M1Y0PP113HZDD5PP2NXVEVP3", + "kind": "memory", + "score": 0.9996471405029296, + "summary": "project:fact - Sable production gateway port is 8103." + } + ], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [], + "status": "supported", + "subject": "sable gateway", + "supported": [ + { + "attribute": "port", + "sources": [ + "memory:01M1Y0PK8PZWPJWR5PPMDYG26M" + ], + "value": "8103" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 339.63100000000003, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 647, + "mcp_result_bytes": 760, + "wire_bytes": 795, + "reported_used_tokens": 760, + "working_set_bytes": 635801600, + "peak_working_set_bytes": 684900352 + }, + { + "query": "What are the Sable staging gateway retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PK9Q5NNQDBCP58G4DGKV", + "id": "01M1Y0PPBZ25S6W76HWHAYGSCH", + "kind": "memory", + "score": 0.9971465468406676, + "summary": "project:fact - Sable staging gateway retries are 3." + } + ], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [], + "status": "supported", + "subject": "sable gateway", + "supported": [ + { + "attribute": "retries", + "sources": [ + "memory:01M1Y0PK9Q5NNQDBCP58G4DGKV" + ], + "value": "3" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 353.3684, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 642, + "mcp_result_bytes": 755, + "wire_bytes": 790, + "reported_used_tokens": 755, + "working_set_bytes": 635912192, + "peak_working_set_bytes": 684900352 + }, + { + "query": "What is `cache.max_entries` for Sable?", + "ranked": [ + "literal" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PKBFV2262AJ8MPFYNCQD", + "id": "01M1Y0PPPWPCHFRC4FT032TS03", + "kind": "memory", + "score": 0.9999657869338988, + "summary": "project:fact - Sable cache.max_entries = 200." + } + ], + "answerability": { + "conflicting": [], + "environment": null, + "missing": [], + "status": "supported", + "subject": "sable", + "supported": [ + { + "attribute": "cache.max_entries", + "sources": [ + "memory:01M1Y0PKBFV2262AJ8MPFYNCQD" + ], + "value": "200" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 367.2114, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 635, + "mcp_result_bytes": 746, + "wire_bytes": 781, + "reported_used_tokens": 746, + "working_set_bytes": 637939712, + "peak_working_set_bytes": 684900352 + }, + { + "query": "Which database stores local state for Sable?", + "ranked": [ + "runtime", + "database" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PKB5FKQT86DD2P22GC4B", + "id": "01M1Y0PQ2PTS9YZTFPRA6JQZ28", + "kind": "memory", + "score": 0.9999470710754396, + "summary": "project:fact - Sable stores its local state in SQLite using WAL mode." + }, + { + "expansion_handle": "memory:01M1Y0PKA0J6P05F03M8RAM3ER", + "id": "01M1Y0PQ2PCSFC8VR1D0H23GCH", + "kind": "memory", + "score": 0.7779185175895691, + "summary": "project:fact - Sable production database port is 5432." + } + ], + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 357.5433, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 644, + "mcp_result_bytes": 743, + "wire_bytes": 778, + "reported_used_tokens": 743, + "working_set_bytes": 637984768, + "peak_working_set_bytes": 684900352 + }, + { + "query": "What are the Sable staging gateway port and password?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port", + "password" + ], + "status": "missing", + "subject": "sable gateway", + "supported": [] + }, + "positive_recall_at_4": 0.0, + "positive_hit_at_4": false, + "positive_mrr": 0.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 356.7282, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 375, + "mcp_result_bytes": 462, + "wire_bytes": 497, + "reported_used_tokens": 462, + "working_set_bytes": 638083072, + "peak_working_set_bytes": 684900352 + }, + { + "query": "What are the Sable staging gateway port and retries?", + "ranked": [ + "retries" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PK9Q5NNQDBCP58G4DGKV", + "id": "01M1Y0PQSP0DPJEWAJG430EAB9", + "kind": "memory", + "score": 0.9917003512382508, + "summary": "project:fact - Sable staging gateway retries are 3." + } + ], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port" + ], + "status": "partial", + "subject": "sable gateway", + "supported": [ + { + "attribute": "retries", + "sources": [ + "memory:01M1Y0PK9Q5NNQDBCP58G4DGKV" + ], + "value": "3" + } + ] + }, + "positive_recall_at_4": 0.5, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 386.5749, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 645, + "mcp_result_bytes": 760, + "wire_bytes": 795, + "reported_used_tokens": 760, + "working_set_bytes": 638279680, + "peak_working_set_bytes": 684900352 + }, + { + "query": "What is the Sable staging gateway timeout?", + "ranked": [ + "timeout-b", + "timeout-a" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PK9C24APYRKZ59M4CGZF", + "id": "01M1Y0PR4S81RX1S517XFAXKZE", + "kind": "memory", + "score": 0.9999639987945556, + "summary": "project:fact - Sable staging gateway timeout is 45 seconds. The deployment checklist records a different current value." + }, + { + "expansion_handle": "memory:01M1Y0PK90E11XHQE8DC47F89E", + "id": "01M1Y0PR4SEWRQD7P0HDPHR7SM", + "kind": "memory", + "score": 0.9999442100524902, + "summary": "project:fact - Sable staging gateway timeout is 30 seconds. Operators record this in the request settings." + } + ], + "answerability": { + "conflicting": [ + "timeout" + ], + "environment": "staging", + "missing": [], + "status": "conflicting", + "subject": "sable gateway", + "supported": [] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 352.3854, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 891, + "mcp_result_bytes": 1012, + "wire_bytes": 1047, + "reported_used_tokens": 1012, + "working_set_bytes": 638455808, + "peak_working_set_bytes": 684900352 + }, + { + "query": "What password does the Sable production gateway require?", + "ranked": [ + "no-password" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PKA9CGHM9BCQQ83GXPMZ", + "id": "01M1Y0PRFPGY4ANZBRSZ0Y9BFM", + "kind": "memory", + "score": 0.9999281167984008, + "summary": "project:fact - No password is required for the Sable production gateway." + } + ], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [], + "status": "supported", + "subject": "sable gateway", + "supported": [ + { + "attribute": "password", + "sources": [ + "memory:01M1Y0PKA9CGHM9BCQQ83GXPMZ" + ], + "value": "not required" + } + ] + }, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": true, + "positive_mrr": 1.0, + "negative_injection": null, + "stale_injection": null, + "latency_ms": 348.2401, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 678, + "mcp_result_bytes": 791, + "wire_bytes": 827, + "reported_used_tokens": 791, + "working_set_bytes": 643198976, + "peak_working_set_bytes": 684900352 + }, + { + "query": "What is the Sable staging database port?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port" + ], + "status": "missing", + "subject": "sable database", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 351.6295, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 365, + "mcp_result_bytes": 450, + "wire_bytes": 486, + "reported_used_tokens": 450, + "working_set_bytes": 643256320, + "peak_working_set_bytes": 684900352 + }, + { + "query": "What is the Sable production gateway timeout?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [ + "timeout" + ], + "status": "missing", + "subject": "sable gateway", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 346.0297, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 370, + "mcp_result_bytes": 455, + "wire_bytes": 491, + "reported_used_tokens": 455, + "working_set_bytes": 643297280, + "peak_working_set_bytes": 684900352 + }, + { + "query": "What is the Unknown staging gateway port?", + "ranked": [ + "stage-port", + "foreign-port" + ], + "delivered_capsules": [ + { + "expansion_handle": "memory:01M1Y0PK866758C6STCZQVQ554", + "id": "01M1Y0PSGSJVKMCGT6QYRYDG2G", + "kind": "memory", + "score": 0.8858267068862915, + "summary": "project:fact - Sable staging gateway port is 7103." + }, + { + "expansion_handle": "memory:01M1Y0PKAWEK94Z6EXD6T47JWJ", + "id": "01M1Y0PSGS5S4PN8H66BV9WVRQ", + "kind": "memory", + "score": 0.8669298887252808, + "summary": "project:fact - Foreign staging gateway port is 9944." + } + ], + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": true, + "stale_injection": null, + "latency_ms": 365.358, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 622, + "mcp_result_bytes": 721, + "wire_bytes": 757, + "reported_used_tokens": 721, + "working_set_bytes": 643350528, + "peak_working_set_bytes": 684900352 + }, + { + "query": "What is the Sable test gateway port?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "test", + "missing": [ + "port" + ], + "status": "missing", + "subject": "sable gateway", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 345.90110000000004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 361, + "mcp_result_bytes": 446, + "wire_bytes": 482, + "reported_used_tokens": 446, + "working_set_bytes": 643432448, + "peak_working_set_bytes": 684900352 + }, + { + "query": "What are the Sable production gateway retries?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "production", + "missing": [ + "retries" + ], + "status": "missing", + "subject": "sable gateway", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 355.3758, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 370, + "mcp_result_bytes": 455, + "wire_bytes": 491, + "reported_used_tokens": 455, + "working_set_bytes": 643448832, + "peak_working_set_bytes": 684900352 + }, + { + "query": "What is the Sable staging worker timeout?", + "ranked": [], + "answerability": { + "conflicting": [], + "environment": "staging", + "missing": [ + "timeout" + ], + "status": "missing", + "subject": "sable worker", + "supported": [] + }, + "positive_recall_at_4": null, + "positive_hit_at_4": null, + "positive_mrr": null, + "negative_injection": false, + "stale_injection": null, + "latency_ms": 359.8004, + "first_query": false, + "server_startup_ms": 0.0, + "model_text_bytes": 366, + "mcp_result_bytes": 451, + "wire_bytes": 487, + "reported_used_tokens": 451, + "working_set_bytes": 643506176, + "peak_working_set_bytes": 684900352 + } + ], + "id": "sable-structured-scope", + "dimension": "retrieval", + "tier": "hard", + "score": 0.8333333333333334, + "skipped": false, + "detail": "positive-recall@4=0.83 mrr=0.89 stale-hit=n/a resolution=n/a false-injection=0.167 (n=6) positive-n=9 negative-n=6 (15 queries)" + } + ], + "by_dimension_tier": { + "retrieval/hard": [ + 2.5, + 3 + ] + }, + "by_dimension": { + "retrieval": { + "mean": 0.8333333333333334, + "n": 3, + "ci95": 0.0 + } + }, + "overall_index": 0.8333333333333334, + "scenario_weighted_index": 0.8333333333333334 +} diff --git a/docs/audits/2026-09-07-structured-facts/results/validation/comparison.json b/docs/audits/2026-09-07-structured-facts/results/validation/comparison.json new file mode 100644 index 0000000..d8d5ecf --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/results/validation/comparison.json @@ -0,0 +1,192 @@ +{ + "schema_version": 1, + "status": "complete", + "harness": { + "path": "E:\\Kimetsu\\bench\\target\\release\\kbench.exe", + "sha256": "5ba5065f9aaa28ced75a091bb43e01c8b995751e0e5cd3f9842ac6c822e844ff", + "bytes": 9405952 + }, + "runner": { + "path": "E:\\tmp\\kimetsu-brain-hardening\\bench\\scripts\\compare_brainbench.py", + "sha256": "738bad9404a4ec2b911fff661967ca56f48b584dfeb22f83823c972a1498df37", + "bytes": 24527 + }, + "binaries": { + "baseline": { + "path": "E:\\tmp\\kimetsu-brain-hardening\\tmp-tests\\kimetsu-answerability-candidate.exe", + "sha256": "405d3483fe320e76b0ec776bf9ada3b7771852b04a73f70f3b5da377a43d31c3", + "bytes": 47151104 + }, + "candidate": { + "path": "E:\\tmp\\kimetsu-brain-hardening\\tmp-tests\\kimetsu-structured-facts-candidate.exe", + "sha256": "b5672cfed1da5bbd03fdbc20b954582ad5216f4fb579c8e463914c0839126ee7", + "bytes": 47382016 + } + }, + "datasets": [ + { + "path": "E:\\tmp\\kimetsu-brain-hardening\\docs\\audits\\2026-09-07-structured-facts\\validation-frozen.json", + "sha256": "6b90f328e989fa0addf7c9a6d2468114fa1b233800f6e979a83830250dd4e469", + "bytes": 16318 + } + ], + "settings": { + "budget_tokens": 6000, + "dimensions": [ + "poisoning", + "render-contract", + "retrieval", + "workflow" + ], + "jobs": 1, + "warm_start": false, + "include_ambient": false, + "overrides": { + "KIMETSU_BRAIN_EMBEDDER": "bge-small-en-v1.5", + "KIMETSU_DETECT_CONFLICTS": "0", + "KIMETSU_RESOLVE_CONFLICTS": "0", + "FASTEMBED_CACHE_DIR": "E:\\Kimetsu\\.fastembed_cache", + "HF_HOME": "E:\\tmp\\kimetsu-brain-hardening\\tmp-tests\\hf-home" + }, + "baseline_threads": 0, + "candidate_threads": 0, + "baseline_reranker": "mmarco-minilm-l12-v2-int8", + "candidate_reranker": "mmarco-minilm-l12-v2-int8", + "baseline_rerank_floor": 0.55, + "candidate_rerank_floor": 0.55 + }, + "runs": [ + { + "label": "baseline", + "repeat": 1, + "intra_threads_override": null, + "rerank_floor_override": "0.55", + "explicit_fact_guard_override": "true", + "reranker_override": "mmarco-minilm-l12-v2-int8", + "wall_seconds": 26.897919400013052, + "report_file": "1-baseline.json" + }, + { + "label": "candidate", + "repeat": 1, + "intra_threads_override": null, + "rerank_floor_override": "0.55", + "explicit_fact_guard_override": "true", + "reranker_override": "mmarco-minilm-l12-v2-int8", + "wall_seconds": 26.46996349998517, + "report_file": "1-candidate.json" + }, + { + "label": "candidate", + "repeat": 2, + "intra_threads_override": null, + "rerank_floor_override": "0.55", + "explicit_fact_guard_override": "true", + "reranker_override": "mmarco-minilm-l12-v2-int8", + "wall_seconds": 26.217430600023363, + "report_file": "2-candidate.json" + }, + { + "label": "baseline", + "repeat": 2, + "intra_threads_override": null, + "rerank_floor_override": "0.55", + "explicit_fact_guard_override": "true", + "reranker_override": "mmarco-minilm-l12-v2-int8", + "wall_seconds": 27.397887099999934, + "report_file": "2-baseline.json" + } + ], + "comparison": { + "measurement_summary": { + "baseline": { + "unique_queries": 45, + "query_observations": 90, + "positive_queries": 27, + "negative_queries": 18, + "stale_queries": 0, + "positive_recall_at_4": 0.8333333333333334, + "positive_hit_at_4": 0.8888888888888888, + "positive_mrr": 0.8888888888888888, + "negative_injection_rate": 0.8333333333333334, + "stale_injection_rate": null, + "first_query_mean_ms": 2296.2604499999998, + "subsequent_query_p50_ms": 349.6612, + "subsequent_query_p95_ms": 376.5827, + "subsequent_observations": 84, + "mean_model_text_bytes": 525.2888888888889, + "mean_mcp_result_bytes": 613.4888888888889, + "memory_observations": 90, + "mean_mcp_working_set_bytes": 639685472.7111111, + "max_mcp_peak_working_set_bytes": 685244416, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + }, + "candidate": { + "unique_queries": 45, + "query_observations": 90, + "positive_queries": 27, + "negative_queries": 18, + "stale_queries": 0, + "positive_recall_at_4": 0.8333333333333334, + "positive_hit_at_4": 0.8888888888888888, + "positive_mrr": 0.8888888888888888, + "negative_injection_rate": 0.16666666666666666, + "stale_injection_rate": null, + "first_query_mean_ms": 2148.1309333333334, + "subsequent_query_p50_ms": 352.3854, + "subsequent_query_p95_ms": 386.5749, + "subsequent_observations": 84, + "mean_model_text_bytes": 550.6444444444444, + "mean_mcp_result_bytes": 651.2444444444444, + "memory_observations": 90, + "mean_mcp_working_set_bytes": 639069934.9333333, + "max_mcp_peak_working_set_bytes": 685252608, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + } + }, + "by_dimension": { + "retrieval": { + "n_scenarios": 3, + "baseline": 0.5666666666666667, + "candidate": 0.8333333333333334, + "mean_delta": 0.2666666666666667, + "ci95": [ + 0.2666666666666667, + 0.2666666666666667 + ], + "wins": 3, + "ties": 0, + "losses": 0 + } + }, + "scenarios": [ + { + "identity": "retrieval/harbor-structured-scope", + "dimension": "retrieval", + "baseline": 0.5666666666666667, + "candidate": 0.8333333333333334, + "delta": 0.2666666666666667 + }, + { + "identity": "retrieval/lumen-structured-scope", + "dimension": "retrieval", + "baseline": 0.5666666666666667, + "candidate": 0.8333333333333334, + "delta": 0.2666666666666667 + }, + { + "identity": "retrieval/sable-structured-scope", + "dimension": "retrieval", + "baseline": 0.5666666666666667, + "candidate": 0.8333333333333334, + "delta": 0.2666666666666667 + } + ], + "unpaired_scenarios": [], + "unpaired_details": [], + "baseline_errors": 0, + "candidate_errors": 0, + "repeats": 2, + "uncertainty_note": "Exploratory paired bootstrap over scenario IDs after averaging repeats; correlated task families require a separate grouped holdout." + } +} \ No newline at end of file diff --git a/docs/audits/2026-09-07-structured-facts/results/validation/comparison.md b/docs/audits/2026-09-07-structured-facts/results/validation/comparison.md new file mode 100644 index 0000000..e059b3c --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/results/validation/comparison.md @@ -0,0 +1,26 @@ +# Paired BrainBench comparison + +Same harness and fixture; run order alternates. Positive delta favors the candidate. + +| Dimension | Scenarios | Baseline | Candidate | Delta | Exploratory 95% interval | +|---|---:|---:|---:|---:|---| +| retrieval | 3 | 0.567 | 0.833 | +0.267 | [+0.267, +0.267] | + +Errors: baseline 0, candidate 0. +Unpaired/skipped scenarios: 0. + +Exploratory paired bootstrap over scenario IDs after averaging repeats; correlated task families require a separate grouped holdout. + +Wall times include process/model startup, corpus seeding and queries; they are not warm inference latency. + +baseline: mean complete-run time 27.15 s (2 repeats). +candidate: mean complete-run time 26.34 s (2 repeats). + +Query measurements through persistent MCP (subsequent queries reuse the process): + +| Build | Positive hit@4 | Positive recall@4 | False injection | Subsequent p50 / p95 ms | Mean MCP result bytes | Peak MCP working set MiB | +|---|---:|---:|---:|---:|---:|---:| +| baseline | 0.889 | 0.833 | 0.833 | 349.661 / 376.583 | 613.489 | 653.500 | +| candidate | 0.889 | 0.833 | 0.167 | 352.385 / 386.575 | 651.244 | 653.508 | + +Measured bytes include JSON escaping; reported token estimates are retained per query but may use different accounting rules across builds. Query timing excludes the separately recorded MCP initialization and corpus seeding. diff --git a/docs/audits/2026-09-07-structured-facts/run-comparisons.ps1 b/docs/audits/2026-09-07-structured-facts/run-comparisons.ps1 new file mode 100644 index 0000000..e78413e --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/run-comparisons.ps1 @@ -0,0 +1,38 @@ +param( + [Parameter(Mandatory=$true)][string]$Baseline, + [Parameter(Mandatory=$true)][string]$Candidate, + [Parameter(Mandatory=$true)][string]$Harness, + [Parameter(Mandatory=$true)][string]$OutputRoot, + [string]$ModelCache='E:/Kimetsu/.fastembed_cache', + [string]$HfHome +) +$ErrorActionPreference = 'Stop' +$baselinePath = (Resolve-Path -LiteralPath $Baseline).Path +$candidatePath = (Resolve-Path -LiteralPath $Candidate).Path +$harnessPath = (Resolve-Path -LiteralPath $Harness).Path +if (Test-Path -LiteralPath $OutputRoot) { throw 'Use a new output directory.' } +New-Item -ItemType Directory -Path $OutputRoot | Out-Null +$outputPath = (Resolve-Path -LiteralPath $OutputRoot).Path +$repo = (Resolve-Path "$PSScriptRoot/../../..").Path +$env:FASTEMBED_CACHE_DIR=(Resolve-Path -LiteralPath $ModelCache).Path +if (-not $HfHome) { $HfHome="$repo/tmp-tests/hf-home" } +$env:HF_HOME=(Resolve-Path -LiteralPath $HfHome).Path +$env:HF_HUB_OFFLINE='1' +$env:KIMETSU_USER_BRAIN='0' +$env:KIMETSU_EMBED_DAEMON='0' +$env:KIMETSU_BRAIN_EMBEDDER='bge-small-en-v1.5' +$env:KIMETSU_DETECT_CONFLICTS='0' +$env:KIMETSU_RESOLVE_CONFLICTS='0' +Remove-Item Env:KIMETSU_ABSTAIN_EVIDENCE -ErrorAction SilentlyContinue +$fixture="$PSScriptRoot/validation-frozen.json" +if ((Get-FileHash -LiteralPath $fixture -Algorithm SHA256).Hash.ToLowerInvariant() -ne '6b90f328e989fa0addf7c9a6d2468114fa1b233800f6e979a83830250dd4e469') { throw 'Frozen validation changed' } +if ((Get-FileHash -LiteralPath "$PSScriptRoot/answerability-gold.json" -Algorithm SHA256).Hash.ToLowerInvariant() -ne 'd4d8b3f84282ea52515a1e9dc4fe7fe20f1e26ef45c4905cefcd984db0540445') { throw 'Frozen answerability gold changed' } +$experiments=@( + @{name='development'; fixture="$PSScriptRoot/../2026-09-07-retrieval/development-100.json"; model='ms-marco-tinybert-l-2-v2'; floor=0.30; repeats=1}, + @{name='answerability-regression'; fixture="$PSScriptRoot/../2026-09-07-answerability/validation-frozen.json"; model='mmarco-minilm-l12-v2-int8'; floor=0.55; repeats=1}, + @{name='validation'; fixture=$fixture; model='mmarco-minilm-l12-v2-int8'; floor=0.55; repeats=2} +) +foreach ($experiment in $experiments) { + python "$repo/bench/scripts/compare_brainbench.py" --kbench $harnessPath --baseline $baselinePath --candidate $candidatePath --dataset $experiment.fixture --budget-tokens 6000 --repeats $experiment.repeats --out "$outputPath/$($experiment.name)" --baseline-threads 0 --candidate-threads 0 --baseline-reranker $experiment.model --candidate-reranker $experiment.model --baseline-rerank-floor $experiment.floor --candidate-rerank-floor $experiment.floor --baseline-explicit-fact-guard true --candidate-explicit-fact-guard true + if ($LASTEXITCODE -ne 0) { throw "Comparison failed: $($experiment.name)" } +} diff --git a/docs/audits/2026-09-07-structured-facts/summarize.py b/docs/audits/2026-09-07-structured-facts/summarize.py new file mode 100644 index 0000000..4d7c5cf --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/summarize.py @@ -0,0 +1,71 @@ +"""Summarize saved paired observations, including corpus-based fact expectations.""" +import json +from pathlib import Path + +root = Path(__file__).parent +fixture = json.loads((root / "validation-frozen.json").read_text(encoding="utf-8")) +gold = {(s["id"], q["query"]): q for s in fixture["scenarios"] for q in s["queries"]} +value_gold = {(q["scenario"], q["query"]): q for q in json.loads((root / "answerability-gold.json").read_text(encoding="utf-8"))["queries"]} +assert value_gold.keys() == gold.keys() +result = {} +def semantic_answerability(row): + value = row.get("answerability") + if value is None: + return None + # Each isolated repeat creates new memory IDs. Compare evidence meaning, + # retaining source handles in raw observations for separate provenance audit. + return {**value, "supported": [{k: v for k, v in fact.items() if k != "sources"} + for fact in value.get("supported", [])]} + +for name in ("development", "answerability-regression", "validation"): + folder = root / "results" / name + comparison = json.loads((folder / "comparison.json").read_text(encoding="utf-8")) + assert comparison["status"] == "complete", name + paired = comparison["comparison"] + assert not paired["baseline_errors"] and not paired["candidate_errors"] and not paired["unpaired_scenarios"] + observations, counts, variation, metadata = {}, {}, {}, {} + for side in ("baseline", "candidate"): + repeats = [] + for path in sorted(folder.glob(f"*-{side}.json")): + report = json.loads(path.read_text(encoding="utf-8")) + rows = {(s["id"], q["query"]): q for s in report["scenarios"] for q in s.get("observations", [])} + assert len(rows) == sum(len(s.get("observations", [])) for s in report["scenarios"]) + repeats.append(rows) + rows = repeats[0] + assert all(r.keys() == rows.keys() for r in repeats) + observations[side] = rows + for repeat in repeats: + for key, row in repeat.items(): + handles = {c["expansion_handle"] for c in row.get("delivered_capsules", [])} + for fact in (row.get("answerability") or {}).get("supported", []): + assert fact["sources"] and set(fact["sources"]) <= handles, (name, side, key, fact) + variation[side] = sum(any(r[key]["ranked"] != row["ranked"] or semantic_answerability(r[key]) != semantic_answerability(row) for r in repeats) for key, row in rows.items()) + counts[side] = { + "positive_queries": sum(q["positive_hit_at_4"] is not None for q in rows.values()), + "positive_hits": sum(q["positive_hit_at_4"] is True for q in rows.values()), + "negative_queries": sum(q["negative_injection"] is not None for q in rows.values()), + "negative_injections": sum(q["negative_injection"] is True for q in rows.values()), + "observations": sum(len(r) for r in repeats), + } + if name == "validation": + assert rows.keys() == gold.keys() + mismatches = [] + exact_per_repeat = [] + for repeat_index, repeat in enumerate(repeats, 1): + before = len(mismatches) + for key, row in repeat.items(): + expected = gold[key] + delivered = row.get("answerability") + status = delivered.get("status") if delivered else None + missing = delivered.get("missing", []) if delivered else [] + supported = {f["attribute"]: f["value"] for f in delivered.get("supported", [])} if delivered else None + conflicting = delivered.get("conflicting", []) if delivered else [] + if (status != expected["expected_answerability"] or sorted(missing) != sorted(expected["expected_missing"]) + or supported != value_gold[key]["supported"] or sorted(conflicting) != sorted(value_gold[key]["conflicting"])): + mismatches.append(dict(repeat=repeat_index, scenario=key[0], query=key[1], expected_status=expected["expected_answerability"], expected_missing=expected["expected_missing"], expected_supported=value_gold[key]["supported"], expected_conflicting=value_gold[key]["conflicting"], delivered=delivered)) + exact_per_repeat.append(len(repeat) - (len(mismatches) - before)) + metadata[side] = {"exact_per_repeat": exact_per_repeat, "exact_observations": sum(exact_per_repeat), "observations": sum(len(r) for r in repeats), "unique_queries": len(rows), "mismatches": mismatches} + assert observations["baseline"].keys() == observations["candidate"].keys() + losses = [list(key) for key, q in observations["baseline"].items() if q["positive_hit_at_4"] is True and observations["candidate"][key]["positive_hit_at_4"] is False] + result[name] = dict(counts=counts, positive_losses=losses, queries_with_repeat_variation=variation, answerability=metadata, measurements=paired["measurement_summary"]) +print(json.dumps(result, indent=2)) diff --git a/docs/audits/2026-09-07-structured-facts/summary.json b/docs/audits/2026-09-07-structured-facts/summary.json new file mode 100644 index 0000000..eda4b0c --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/summary.json @@ -0,0 +1,1657 @@ +{ + "development": { + "counts": { + "baseline": { + "positive_queries": 197, + "positive_hits": 169, + "negative_queries": 13, + "negative_injections": 7, + "observations": 210 + }, + "candidate": { + "positive_queries": 197, + "positive_hits": 169, + "negative_queries": 13, + "negative_injections": 7, + "observations": 210 + } + }, + "positive_losses": [], + "queries_with_repeat_variation": { + "baseline": 0, + "candidate": 0 + }, + "answerability": {}, + "measurements": { + "baseline": { + "unique_queries": 210, + "query_observations": 210, + "positive_queries": 197, + "negative_queries": 13, + "stale_queries": 0, + "positive_recall_at_4": 0.8417935702199661, + "positive_hit_at_4": 0.8578680203045685, + "positive_mrr": 0.850253807106599, + "negative_injection_rate": 0.5384615384615384, + "stale_injection_rate": null, + "first_query_mean_ms": 1075.8079, + "subsequent_query_p50_ms": 996.8795, + "subsequent_query_p95_ms": 1111.6677, + "subsequent_observations": 209, + "mean_model_text_bytes": 1167.5666666666666, + "mean_mcp_result_bytes": 1262.395238095238, + "memory_observations": 210, + "mean_mcp_working_set_bytes": 288490895.84761906, + "max_mcp_peak_working_set_bytes": 294875136, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + }, + "candidate": { + "unique_queries": 210, + "query_observations": 210, + "positive_queries": 197, + "negative_queries": 13, + "stale_queries": 0, + "positive_recall_at_4": 0.8417935702199661, + "positive_hit_at_4": 0.8578680203045685, + "positive_mrr": 0.850253807106599, + "negative_injection_rate": 0.5384615384615384, + "stale_injection_rate": null, + "first_query_mean_ms": 1064.4432, + "subsequent_query_p50_ms": 1011.3393, + "subsequent_query_p95_ms": 1094.0042999999998, + "subsequent_observations": 209, + "mean_model_text_bytes": 1167.5666666666666, + "mean_mcp_result_bytes": 1262.395238095238, + "memory_observations": 210, + "mean_mcp_working_set_bytes": 287489287.3142857, + "max_mcp_peak_working_set_bytes": 294866944, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + } + } + }, + "answerability-regression": { + "counts": { + "baseline": { + "positive_queries": 24, + "positive_hits": 24, + "negative_queries": 20, + "negative_injections": 0, + "observations": 44 + }, + "candidate": { + "positive_queries": 24, + "positive_hits": 24, + "negative_queries": 20, + "negative_injections": 0, + "observations": 44 + } + }, + "positive_losses": [], + "queries_with_repeat_variation": { + "baseline": 0, + "candidate": 0 + }, + "answerability": {}, + "measurements": { + "baseline": { + "unique_queries": 44, + "query_observations": 44, + "positive_queries": 24, + "negative_queries": 20, + "stale_queries": 0, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": 1, + "positive_mrr": 1.0, + "negative_injection_rate": 0, + "stale_injection_rate": null, + "first_query_mean_ms": 2432.32715, + "subsequent_query_p50_ms": 354.5775, + "subsequent_query_p95_ms": 409.1152, + "subsequent_observations": 42, + "mean_model_text_bytes": 340.5, + "mean_mcp_result_bytes": 413.3181818181818, + "memory_observations": 44, + "mean_mcp_working_set_bytes": 643425093.8181819, + "max_mcp_peak_working_set_bytes": 685092864, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + }, + "candidate": { + "unique_queries": 44, + "query_observations": 44, + "positive_queries": 24, + "negative_queries": 20, + "stale_queries": 0, + "positive_recall_at_4": 1.0, + "positive_hit_at_4": 1, + "positive_mrr": 1.0, + "negative_injection_rate": 0, + "stale_injection_rate": null, + "first_query_mean_ms": 2397.1848, + "subsequent_query_p50_ms": 353.576, + "subsequent_query_p95_ms": 380.6524, + "subsequent_observations": 42, + "mean_model_text_bytes": 383.3181818181818, + "mean_mcp_result_bytes": 462.04545454545456, + "memory_observations": 44, + "mean_mcp_working_set_bytes": 643632779.6363636, + "max_mcp_peak_working_set_bytes": 684916736, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + } + } + }, + "validation": { + "counts": { + "baseline": { + "positive_queries": 27, + "positive_hits": 24, + "negative_queries": 18, + "negative_injections": 15, + "observations": 90 + }, + "candidate": { + "positive_queries": 27, + "positive_hits": 24, + "negative_queries": 18, + "negative_injections": 3, + "observations": 90 + } + }, + "positive_losses": [], + "queries_with_repeat_variation": { + "baseline": 0, + "candidate": 0 + }, + "answerability": { + "baseline": { + "exact_per_repeat": [ + 3, + 3 + ], + "exact_observations": 6, + "observations": 90, + "unique_queries": 45, + "mismatches": [ + { + "repeat": 1, + "scenario": "lumen-structured-scope", + "query": "What is the Lumen staging gateway port?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "port": "7101" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 1, + "scenario": "lumen-structured-scope", + "query": "What is the Lumen production gateway port?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "port": "8101" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 1, + "scenario": "lumen-structured-scope", + "query": "What are the Lumen staging gateway retries?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "retries": "3" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 1, + "scenario": "lumen-structured-scope", + "query": "What is `cache.max_entries` for Lumen?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "cache.max_entries": "200" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 1, + "scenario": "lumen-structured-scope", + "query": "What are the Lumen staging gateway port and password?", + "expected_status": "partial", + "expected_missing": [ + "password" + ], + "expected_supported": { + "port": "7101" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 1, + "scenario": "lumen-structured-scope", + "query": "What are the Lumen staging gateway port and retries?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "port": "7101", + "retries": "3" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 1, + "scenario": "lumen-structured-scope", + "query": "What is the Lumen staging gateway timeout?", + "expected_status": "conflicting", + "expected_missing": [], + "expected_supported": {}, + "expected_conflicting": [ + "timeout" + ], + "delivered": null + }, + { + "repeat": 1, + "scenario": "lumen-structured-scope", + "query": "What password does the Lumen production gateway require?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "password": "not required" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 1, + "scenario": "lumen-structured-scope", + "query": "What is the Lumen staging database port?", + "expected_status": "missing", + "expected_missing": [ + "port" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 1, + "scenario": "lumen-structured-scope", + "query": "What is the Lumen production gateway timeout?", + "expected_status": "missing", + "expected_missing": [ + "timeout" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 1, + "scenario": "lumen-structured-scope", + "query": "What is the Unknown staging gateway port?", + "expected_status": "missing", + "expected_missing": [ + "port" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 1, + "scenario": "lumen-structured-scope", + "query": "What is the Lumen test gateway port?", + "expected_status": "missing", + "expected_missing": [ + "port" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 1, + "scenario": "lumen-structured-scope", + "query": "What are the Lumen production gateway retries?", + "expected_status": "missing", + "expected_missing": [ + "retries" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 1, + "scenario": "lumen-structured-scope", + "query": "What is the Lumen staging worker timeout?", + "expected_status": "missing", + "expected_missing": [ + "timeout" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 1, + "scenario": "harbor-structured-scope", + "query": "What is the Harbor staging gateway port?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "port": "7102" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 1, + "scenario": "harbor-structured-scope", + "query": "What is the Harbor production gateway port?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "port": "8102" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 1, + "scenario": "harbor-structured-scope", + "query": "What are the Harbor staging gateway retries?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "retries": "3" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 1, + "scenario": "harbor-structured-scope", + "query": "What is `cache.max_entries` for Harbor?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "cache.max_entries": "200" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 1, + "scenario": "harbor-structured-scope", + "query": "What are the Harbor staging gateway port and password?", + "expected_status": "partial", + "expected_missing": [ + "password" + ], + "expected_supported": { + "port": "7102" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 1, + "scenario": "harbor-structured-scope", + "query": "What are the Harbor staging gateway port and retries?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "port": "7102", + "retries": "3" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 1, + "scenario": "harbor-structured-scope", + "query": "What is the Harbor staging gateway timeout?", + "expected_status": "conflicting", + "expected_missing": [], + "expected_supported": {}, + "expected_conflicting": [ + "timeout" + ], + "delivered": null + }, + { + "repeat": 1, + "scenario": "harbor-structured-scope", + "query": "What password does the Harbor production gateway require?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "password": "not required" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 1, + "scenario": "harbor-structured-scope", + "query": "What is the Harbor staging database port?", + "expected_status": "missing", + "expected_missing": [ + "port" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 1, + "scenario": "harbor-structured-scope", + "query": "What is the Harbor production gateway timeout?", + "expected_status": "missing", + "expected_missing": [ + "timeout" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 1, + "scenario": "harbor-structured-scope", + "query": "What is the Unknown staging gateway port?", + "expected_status": "missing", + "expected_missing": [ + "port" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 1, + "scenario": "harbor-structured-scope", + "query": "What is the Harbor test gateway port?", + "expected_status": "missing", + "expected_missing": [ + "port" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 1, + "scenario": "harbor-structured-scope", + "query": "What are the Harbor production gateway retries?", + "expected_status": "missing", + "expected_missing": [ + "retries" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 1, + "scenario": "harbor-structured-scope", + "query": "What is the Harbor staging worker timeout?", + "expected_status": "missing", + "expected_missing": [ + "timeout" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 1, + "scenario": "sable-structured-scope", + "query": "What is the Sable staging gateway port?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "port": "7103" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 1, + "scenario": "sable-structured-scope", + "query": "What is the Sable production gateway port?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "port": "8103" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 1, + "scenario": "sable-structured-scope", + "query": "What are the Sable staging gateway retries?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "retries": "3" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 1, + "scenario": "sable-structured-scope", + "query": "What is `cache.max_entries` for Sable?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "cache.max_entries": "200" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 1, + "scenario": "sable-structured-scope", + "query": "What are the Sable staging gateway port and password?", + "expected_status": "partial", + "expected_missing": [ + "password" + ], + "expected_supported": { + "port": "7103" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 1, + "scenario": "sable-structured-scope", + "query": "What are the Sable staging gateway port and retries?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "port": "7103", + "retries": "3" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 1, + "scenario": "sable-structured-scope", + "query": "What is the Sable staging gateway timeout?", + "expected_status": "conflicting", + "expected_missing": [], + "expected_supported": {}, + "expected_conflicting": [ + "timeout" + ], + "delivered": null + }, + { + "repeat": 1, + "scenario": "sable-structured-scope", + "query": "What password does the Sable production gateway require?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "password": "not required" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 1, + "scenario": "sable-structured-scope", + "query": "What is the Sable staging database port?", + "expected_status": "missing", + "expected_missing": [ + "port" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 1, + "scenario": "sable-structured-scope", + "query": "What is the Sable production gateway timeout?", + "expected_status": "missing", + "expected_missing": [ + "timeout" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 1, + "scenario": "sable-structured-scope", + "query": "What is the Unknown staging gateway port?", + "expected_status": "missing", + "expected_missing": [ + "port" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 1, + "scenario": "sable-structured-scope", + "query": "What is the Sable test gateway port?", + "expected_status": "missing", + "expected_missing": [ + "port" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 1, + "scenario": "sable-structured-scope", + "query": "What are the Sable production gateway retries?", + "expected_status": "missing", + "expected_missing": [ + "retries" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 1, + "scenario": "sable-structured-scope", + "query": "What is the Sable staging worker timeout?", + "expected_status": "missing", + "expected_missing": [ + "timeout" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "lumen-structured-scope", + "query": "What is the Lumen staging gateway port?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "port": "7101" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "lumen-structured-scope", + "query": "What is the Lumen production gateway port?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "port": "8101" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "lumen-structured-scope", + "query": "What are the Lumen staging gateway retries?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "retries": "3" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "lumen-structured-scope", + "query": "What is `cache.max_entries` for Lumen?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "cache.max_entries": "200" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "lumen-structured-scope", + "query": "What are the Lumen staging gateway port and password?", + "expected_status": "partial", + "expected_missing": [ + "password" + ], + "expected_supported": { + "port": "7101" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "lumen-structured-scope", + "query": "What are the Lumen staging gateway port and retries?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "port": "7101", + "retries": "3" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "lumen-structured-scope", + "query": "What is the Lumen staging gateway timeout?", + "expected_status": "conflicting", + "expected_missing": [], + "expected_supported": {}, + "expected_conflicting": [ + "timeout" + ], + "delivered": null + }, + { + "repeat": 2, + "scenario": "lumen-structured-scope", + "query": "What password does the Lumen production gateway require?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "password": "not required" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "lumen-structured-scope", + "query": "What is the Lumen staging database port?", + "expected_status": "missing", + "expected_missing": [ + "port" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "lumen-structured-scope", + "query": "What is the Lumen production gateway timeout?", + "expected_status": "missing", + "expected_missing": [ + "timeout" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "lumen-structured-scope", + "query": "What is the Unknown staging gateway port?", + "expected_status": "missing", + "expected_missing": [ + "port" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "lumen-structured-scope", + "query": "What is the Lumen test gateway port?", + "expected_status": "missing", + "expected_missing": [ + "port" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "lumen-structured-scope", + "query": "What are the Lumen production gateway retries?", + "expected_status": "missing", + "expected_missing": [ + "retries" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "lumen-structured-scope", + "query": "What is the Lumen staging worker timeout?", + "expected_status": "missing", + "expected_missing": [ + "timeout" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "harbor-structured-scope", + "query": "What is the Harbor staging gateway port?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "port": "7102" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "harbor-structured-scope", + "query": "What is the Harbor production gateway port?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "port": "8102" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "harbor-structured-scope", + "query": "What are the Harbor staging gateway retries?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "retries": "3" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "harbor-structured-scope", + "query": "What is `cache.max_entries` for Harbor?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "cache.max_entries": "200" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "harbor-structured-scope", + "query": "What are the Harbor staging gateway port and password?", + "expected_status": "partial", + "expected_missing": [ + "password" + ], + "expected_supported": { + "port": "7102" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "harbor-structured-scope", + "query": "What are the Harbor staging gateway port and retries?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "port": "7102", + "retries": "3" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "harbor-structured-scope", + "query": "What is the Harbor staging gateway timeout?", + "expected_status": "conflicting", + "expected_missing": [], + "expected_supported": {}, + "expected_conflicting": [ + "timeout" + ], + "delivered": null + }, + { + "repeat": 2, + "scenario": "harbor-structured-scope", + "query": "What password does the Harbor production gateway require?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "password": "not required" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "harbor-structured-scope", + "query": "What is the Harbor staging database port?", + "expected_status": "missing", + "expected_missing": [ + "port" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "harbor-structured-scope", + "query": "What is the Harbor production gateway timeout?", + "expected_status": "missing", + "expected_missing": [ + "timeout" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "harbor-structured-scope", + "query": "What is the Unknown staging gateway port?", + "expected_status": "missing", + "expected_missing": [ + "port" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "harbor-structured-scope", + "query": "What is the Harbor test gateway port?", + "expected_status": "missing", + "expected_missing": [ + "port" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "harbor-structured-scope", + "query": "What are the Harbor production gateway retries?", + "expected_status": "missing", + "expected_missing": [ + "retries" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "harbor-structured-scope", + "query": "What is the Harbor staging worker timeout?", + "expected_status": "missing", + "expected_missing": [ + "timeout" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "sable-structured-scope", + "query": "What is the Sable staging gateway port?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "port": "7103" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "sable-structured-scope", + "query": "What is the Sable production gateway port?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "port": "8103" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "sable-structured-scope", + "query": "What are the Sable staging gateway retries?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "retries": "3" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "sable-structured-scope", + "query": "What is `cache.max_entries` for Sable?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "cache.max_entries": "200" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "sable-structured-scope", + "query": "What are the Sable staging gateway port and password?", + "expected_status": "partial", + "expected_missing": [ + "password" + ], + "expected_supported": { + "port": "7103" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "sable-structured-scope", + "query": "What are the Sable staging gateway port and retries?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "port": "7103", + "retries": "3" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "sable-structured-scope", + "query": "What is the Sable staging gateway timeout?", + "expected_status": "conflicting", + "expected_missing": [], + "expected_supported": {}, + "expected_conflicting": [ + "timeout" + ], + "delivered": null + }, + { + "repeat": 2, + "scenario": "sable-structured-scope", + "query": "What password does the Sable production gateway require?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "password": "not required" + }, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "sable-structured-scope", + "query": "What is the Sable staging database port?", + "expected_status": "missing", + "expected_missing": [ + "port" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "sable-structured-scope", + "query": "What is the Sable production gateway timeout?", + "expected_status": "missing", + "expected_missing": [ + "timeout" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "sable-structured-scope", + "query": "What is the Unknown staging gateway port?", + "expected_status": "missing", + "expected_missing": [ + "port" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "sable-structured-scope", + "query": "What is the Sable test gateway port?", + "expected_status": "missing", + "expected_missing": [ + "port" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "sable-structured-scope", + "query": "What are the Sable production gateway retries?", + "expected_status": "missing", + "expected_missing": [ + "retries" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "sable-structured-scope", + "query": "What is the Sable staging worker timeout?", + "expected_status": "missing", + "expected_missing": [ + "timeout" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + } + ] + }, + "candidate": { + "exact_per_repeat": [ + 36, + 36 + ], + "exact_observations": 72, + "observations": 90, + "unique_queries": 45, + "mismatches": [ + { + "repeat": 1, + "scenario": "lumen-structured-scope", + "query": "What are the Lumen staging gateway port and password?", + "expected_status": "partial", + "expected_missing": [ + "password" + ], + "expected_supported": { + "port": "7101" + }, + "expected_conflicting": [], + "delivered": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port", + "password" + ], + "status": "missing", + "subject": "lumen gateway", + "supported": [] + } + }, + { + "repeat": 1, + "scenario": "lumen-structured-scope", + "query": "What are the Lumen staging gateway port and retries?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "port": "7101", + "retries": "3" + }, + "expected_conflicting": [], + "delivered": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port" + ], + "status": "partial", + "subject": "lumen gateway", + "supported": [ + { + "attribute": "retries", + "sources": [ + "memory:01M1Y0N8MBH4F0B8YABX63NTNY" + ], + "value": "3" + } + ] + } + }, + { + "repeat": 1, + "scenario": "lumen-structured-scope", + "query": "What is the Unknown staging gateway port?", + "expected_status": "missing", + "expected_missing": [ + "port" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 1, + "scenario": "harbor-structured-scope", + "query": "What are the Harbor staging gateway port and password?", + "expected_status": "partial", + "expected_missing": [ + "password" + ], + "expected_supported": { + "port": "7102" + }, + "expected_conflicting": [], + "delivered": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port", + "password" + ], + "status": "missing", + "subject": "harbor gateway", + "supported": [] + } + }, + { + "repeat": 1, + "scenario": "harbor-structured-scope", + "query": "What are the Harbor staging gateway port and retries?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "port": "7102", + "retries": "3" + }, + "expected_conflicting": [], + "delivered": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port" + ], + "status": "partial", + "subject": "harbor gateway", + "supported": [ + { + "attribute": "retries", + "sources": [ + "memory:01M1Y0NH3MDPTMMT87NXX9R1NY" + ], + "value": "3" + } + ] + } + }, + { + "repeat": 1, + "scenario": "harbor-structured-scope", + "query": "What is the Unknown staging gateway port?", + "expected_status": "missing", + "expected_missing": [ + "port" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 1, + "scenario": "sable-structured-scope", + "query": "What are the Sable staging gateway port and password?", + "expected_status": "partial", + "expected_missing": [ + "password" + ], + "expected_supported": { + "port": "7103" + }, + "expected_conflicting": [], + "delivered": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port", + "password" + ], + "status": "missing", + "subject": "sable gateway", + "supported": [] + } + }, + { + "repeat": 1, + "scenario": "sable-structured-scope", + "query": "What are the Sable staging gateway port and retries?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "port": "7103", + "retries": "3" + }, + "expected_conflicting": [], + "delivered": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port" + ], + "status": "partial", + "subject": "sable gateway", + "supported": [ + { + "attribute": "retries", + "sources": [ + "memory:01M1Y0NSV5NZC1VQCG1R0WZYKP" + ], + "value": "3" + } + ] + } + }, + { + "repeat": 1, + "scenario": "sable-structured-scope", + "query": "What is the Unknown staging gateway port?", + "expected_status": "missing", + "expected_missing": [ + "port" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "lumen-structured-scope", + "query": "What are the Lumen staging gateway port and password?", + "expected_status": "partial", + "expected_missing": [ + "password" + ], + "expected_supported": { + "port": "7101" + }, + "expected_conflicting": [], + "delivered": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port", + "password" + ], + "status": "missing", + "subject": "lumen gateway", + "supported": [] + } + }, + { + "repeat": 2, + "scenario": "lumen-structured-scope", + "query": "What are the Lumen staging gateway port and retries?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "port": "7101", + "retries": "3" + }, + "expected_conflicting": [], + "delivered": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port" + ], + "status": "partial", + "subject": "lumen gateway", + "supported": [ + { + "attribute": "retries", + "sources": [ + "memory:01M1Y0P2EMFH197467B7148VE7" + ], + "value": "3" + } + ] + } + }, + { + "repeat": 2, + "scenario": "lumen-structured-scope", + "query": "What is the Unknown staging gateway port?", + "expected_status": "missing", + "expected_missing": [ + "port" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "harbor-structured-scope", + "query": "What are the Harbor staging gateway port and password?", + "expected_status": "partial", + "expected_missing": [ + "password" + ], + "expected_supported": { + "port": "7102" + }, + "expected_conflicting": [], + "delivered": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port", + "password" + ], + "status": "missing", + "subject": "harbor gateway", + "supported": [] + } + }, + { + "repeat": 2, + "scenario": "harbor-structured-scope", + "query": "What are the Harbor staging gateway port and retries?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "port": "7102", + "retries": "3" + }, + "expected_conflicting": [], + "delivered": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port" + ], + "status": "partial", + "subject": "harbor gateway", + "supported": [ + { + "attribute": "retries", + "sources": [ + "memory:01M1Y0PAHT983ZMMXFWF7B6MDY" + ], + "value": "3" + } + ] + } + }, + { + "repeat": 2, + "scenario": "harbor-structured-scope", + "query": "What is the Unknown staging gateway port?", + "expected_status": "missing", + "expected_missing": [ + "port" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + }, + { + "repeat": 2, + "scenario": "sable-structured-scope", + "query": "What are the Sable staging gateway port and password?", + "expected_status": "partial", + "expected_missing": [ + "password" + ], + "expected_supported": { + "port": "7103" + }, + "expected_conflicting": [], + "delivered": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port", + "password" + ], + "status": "missing", + "subject": "sable gateway", + "supported": [] + } + }, + { + "repeat": 2, + "scenario": "sable-structured-scope", + "query": "What are the Sable staging gateway port and retries?", + "expected_status": "supported", + "expected_missing": [], + "expected_supported": { + "port": "7103", + "retries": "3" + }, + "expected_conflicting": [], + "delivered": { + "conflicting": [], + "environment": "staging", + "missing": [ + "port" + ], + "status": "partial", + "subject": "sable gateway", + "supported": [ + { + "attribute": "retries", + "sources": [ + "memory:01M1Y0PK9Q5NNQDBCP58G4DGKV" + ], + "value": "3" + } + ] + } + }, + { + "repeat": 2, + "scenario": "sable-structured-scope", + "query": "What is the Unknown staging gateway port?", + "expected_status": "missing", + "expected_missing": [ + "port" + ], + "expected_supported": {}, + "expected_conflicting": [], + "delivered": null + } + ] + } + }, + "measurements": { + "baseline": { + "unique_queries": 45, + "query_observations": 90, + "positive_queries": 27, + "negative_queries": 18, + "stale_queries": 0, + "positive_recall_at_4": 0.8333333333333334, + "positive_hit_at_4": 0.8888888888888888, + "positive_mrr": 0.8888888888888888, + "negative_injection_rate": 0.8333333333333334, + "stale_injection_rate": null, + "first_query_mean_ms": 2296.2604499999998, + "subsequent_query_p50_ms": 349.6612, + "subsequent_query_p95_ms": 376.5827, + "subsequent_observations": 84, + "mean_model_text_bytes": 525.2888888888889, + "mean_mcp_result_bytes": 613.4888888888889, + "memory_observations": 90, + "mean_mcp_working_set_bytes": 639685472.7111111, + "max_mcp_peak_working_set_bytes": 685244416, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + }, + "candidate": { + "unique_queries": 45, + "query_observations": 90, + "positive_queries": 27, + "negative_queries": 18, + "stale_queries": 0, + "positive_recall_at_4": 0.8333333333333334, + "positive_hit_at_4": 0.8888888888888888, + "positive_mrr": 0.8888888888888888, + "negative_injection_rate": 0.16666666666666666, + "stale_injection_rate": null, + "first_query_mean_ms": 2148.1309333333334, + "subsequent_query_p50_ms": 352.3854, + "subsequent_query_p95_ms": 386.5749, + "subsequent_observations": 84, + "mean_model_text_bytes": 550.6444444444444, + "mean_mcp_result_bytes": 651.2444444444444, + "memory_observations": 90, + "mean_mcp_working_set_bytes": 639069934.9333333, + "max_mcp_peak_working_set_bytes": 685252608, + "note": "Quality averages repeats per query; latency percentiles pool repeated observations descriptively, not as independent evidence. First query includes model loading where applicable; server/process initialization is recorded separately." + } + } + } +} diff --git a/docs/audits/2026-09-07-structured-facts/validation-frozen.json b/docs/audits/2026-09-07-structured-facts/validation-frozen.json new file mode 100644 index 0000000..9cbe1d7 --- /dev/null +++ b/docs/audits/2026-09-07-structured-facts/validation-frozen.json @@ -0,0 +1,539 @@ +{ + "protocol": "Frozen before structured-fact candidate inference. Assistant-authored synthetic fixture, not independent or real-agent certification. Do not tune on results.", + "scenarios": [ + { + "id": "lumen-structured-scope", + "dimension": "retrieval", + "tier": "hard", + "description": "Frozen synthetic scope, environment, partial and conflict controls; expected metadata is corpus-based and may expose retrieval omissions.", + "memories": [ + { + "key": "stage-port", + "text": "Lumen staging gateway port is 7101." + }, + { + "key": "prod-port", + "text": "Lumen production gateway port is 8101." + }, + { + "key": "timeout-a", + "text": "Lumen staging gateway timeout is 30 seconds. Operators record this in the request settings." + }, + { + "key": "timeout-b", + "text": "Lumen staging gateway timeout is 45 seconds. The deployment checklist records a different current value." + }, + { + "key": "retries", + "text": "Lumen staging gateway retries are 3." + }, + { + "key": "database", + "text": "Lumen production database port is 5432." + }, + { + "key": "no-password", + "text": "No password is required for the Lumen production gateway." + }, + { + "key": "other-timeout", + "text": "Lumen staging database timeout is 5 seconds." + }, + { + "key": "foreign-port", + "text": "Foreign staging gateway port is 9944." + }, + { + "key": "runtime", + "text": "Lumen stores its local state in SQLite using WAL mode." + }, + { + "key": "literal", + "text": "Lumen cache.max_entries = 200." + } + ], + "queries": [ + { + "query": "What is the Lumen staging gateway port?", + "relevant": [ + "stage-port" + ], + "expected_answerability": "supported", + "expected_missing": [] + }, + { + "query": "What is the Lumen production gateway port?", + "relevant": [ + "prod-port" + ], + "expected_answerability": "supported", + "expected_missing": [] + }, + { + "query": "What are the Lumen staging gateway retries?", + "relevant": [ + "retries" + ], + "expected_answerability": "supported", + "expected_missing": [] + }, + { + "query": "What is `cache.max_entries` for Lumen?", + "relevant": [ + "literal" + ], + "expected_answerability": "supported", + "expected_missing": [] + }, + { + "query": "Which database stores local state for Lumen?", + "relevant": [ + "runtime" + ], + "expected_answerability": null, + "expected_missing": [] + }, + { + "query": "What are the Lumen staging gateway port and password?", + "relevant": [ + "stage-port" + ], + "expected_answerability": "partial", + "expected_missing": [ + "password" + ] + }, + { + "query": "What are the Lumen staging gateway port and retries?", + "relevant": [ + "stage-port", + "retries" + ], + "expected_answerability": "supported", + "expected_missing": [] + }, + { + "query": "What is the Lumen staging gateway timeout?", + "relevant": [ + "timeout-a", + "timeout-b" + ], + "expected_answerability": "conflicting", + "expected_missing": [] + }, + { + "query": "What password does the Lumen production gateway require?", + "relevant": [ + "no-password" + ], + "expected_answerability": "supported", + "expected_missing": [] + }, + { + "query": "What is the Lumen staging database port?", + "relevant": [], + "expected_answerability": "missing", + "expected_missing": [ + "port" + ] + }, + { + "query": "What is the Lumen production gateway timeout?", + "relevant": [], + "expected_answerability": "missing", + "expected_missing": [ + "timeout" + ] + }, + { + "query": "What is the Unknown staging gateway port?", + "relevant": [], + "expected_answerability": "missing", + "expected_missing": [ + "port" + ] + }, + { + "query": "What is the Lumen test gateway port?", + "relevant": [], + "expected_answerability": "missing", + "expected_missing": [ + "port" + ] + }, + { + "query": "What are the Lumen production gateway retries?", + "relevant": [], + "expected_answerability": "missing", + "expected_missing": [ + "retries" + ] + }, + { + "query": "What is the Lumen staging worker timeout?", + "relevant": [], + "expected_answerability": "missing", + "expected_missing": [ + "timeout" + ] + } + ] + }, + { + "id": "harbor-structured-scope", + "dimension": "retrieval", + "tier": "hard", + "description": "Frozen synthetic scope, environment, partial and conflict controls; expected metadata is corpus-based and may expose retrieval omissions.", + "memories": [ + { + "key": "stage-port", + "text": "Harbor staging gateway port is 7102." + }, + { + "key": "prod-port", + "text": "Harbor production gateway port is 8102." + }, + { + "key": "timeout-a", + "text": "Harbor staging gateway timeout is 30 seconds. Operators record this in the request settings." + }, + { + "key": "timeout-b", + "text": "Harbor staging gateway timeout is 45 seconds. The deployment checklist records a different current value." + }, + { + "key": "retries", + "text": "Harbor staging gateway retries are 3." + }, + { + "key": "database", + "text": "Harbor production database port is 5432." + }, + { + "key": "no-password", + "text": "No password is required for the Harbor production gateway." + }, + { + "key": "other-timeout", + "text": "Harbor staging database timeout is 5 seconds." + }, + { + "key": "foreign-port", + "text": "Foreign staging gateway port is 9944." + }, + { + "key": "runtime", + "text": "Harbor stores its local state in SQLite using WAL mode." + }, + { + "key": "literal", + "text": "Harbor cache.max_entries = 200." + } + ], + "queries": [ + { + "query": "What is the Harbor staging gateway port?", + "relevant": [ + "stage-port" + ], + "expected_answerability": "supported", + "expected_missing": [] + }, + { + "query": "What is the Harbor production gateway port?", + "relevant": [ + "prod-port" + ], + "expected_answerability": "supported", + "expected_missing": [] + }, + { + "query": "What are the Harbor staging gateway retries?", + "relevant": [ + "retries" + ], + "expected_answerability": "supported", + "expected_missing": [] + }, + { + "query": "What is `cache.max_entries` for Harbor?", + "relevant": [ + "literal" + ], + "expected_answerability": "supported", + "expected_missing": [] + }, + { + "query": "Which database stores local state for Harbor?", + "relevant": [ + "runtime" + ], + "expected_answerability": null, + "expected_missing": [] + }, + { + "query": "What are the Harbor staging gateway port and password?", + "relevant": [ + "stage-port" + ], + "expected_answerability": "partial", + "expected_missing": [ + "password" + ] + }, + { + "query": "What are the Harbor staging gateway port and retries?", + "relevant": [ + "stage-port", + "retries" + ], + "expected_answerability": "supported", + "expected_missing": [] + }, + { + "query": "What is the Harbor staging gateway timeout?", + "relevant": [ + "timeout-a", + "timeout-b" + ], + "expected_answerability": "conflicting", + "expected_missing": [] + }, + { + "query": "What password does the Harbor production gateway require?", + "relevant": [ + "no-password" + ], + "expected_answerability": "supported", + "expected_missing": [] + }, + { + "query": "What is the Harbor staging database port?", + "relevant": [], + "expected_answerability": "missing", + "expected_missing": [ + "port" + ] + }, + { + "query": "What is the Harbor production gateway timeout?", + "relevant": [], + "expected_answerability": "missing", + "expected_missing": [ + "timeout" + ] + }, + { + "query": "What is the Unknown staging gateway port?", + "relevant": [], + "expected_answerability": "missing", + "expected_missing": [ + "port" + ] + }, + { + "query": "What is the Harbor test gateway port?", + "relevant": [], + "expected_answerability": "missing", + "expected_missing": [ + "port" + ] + }, + { + "query": "What are the Harbor production gateway retries?", + "relevant": [], + "expected_answerability": "missing", + "expected_missing": [ + "retries" + ] + }, + { + "query": "What is the Harbor staging worker timeout?", + "relevant": [], + "expected_answerability": "missing", + "expected_missing": [ + "timeout" + ] + } + ] + }, + { + "id": "sable-structured-scope", + "dimension": "retrieval", + "tier": "hard", + "description": "Frozen synthetic scope, environment, partial and conflict controls; expected metadata is corpus-based and may expose retrieval omissions.", + "memories": [ + { + "key": "stage-port", + "text": "Sable staging gateway port is 7103." + }, + { + "key": "prod-port", + "text": "Sable production gateway port is 8103." + }, + { + "key": "timeout-a", + "text": "Sable staging gateway timeout is 30 seconds. Operators record this in the request settings." + }, + { + "key": "timeout-b", + "text": "Sable staging gateway timeout is 45 seconds. The deployment checklist records a different current value." + }, + { + "key": "retries", + "text": "Sable staging gateway retries are 3." + }, + { + "key": "database", + "text": "Sable production database port is 5432." + }, + { + "key": "no-password", + "text": "No password is required for the Sable production gateway." + }, + { + "key": "other-timeout", + "text": "Sable staging database timeout is 5 seconds." + }, + { + "key": "foreign-port", + "text": "Foreign staging gateway port is 9944." + }, + { + "key": "runtime", + "text": "Sable stores its local state in SQLite using WAL mode." + }, + { + "key": "literal", + "text": "Sable cache.max_entries = 200." + } + ], + "queries": [ + { + "query": "What is the Sable staging gateway port?", + "relevant": [ + "stage-port" + ], + "expected_answerability": "supported", + "expected_missing": [] + }, + { + "query": "What is the Sable production gateway port?", + "relevant": [ + "prod-port" + ], + "expected_answerability": "supported", + "expected_missing": [] + }, + { + "query": "What are the Sable staging gateway retries?", + "relevant": [ + "retries" + ], + "expected_answerability": "supported", + "expected_missing": [] + }, + { + "query": "What is `cache.max_entries` for Sable?", + "relevant": [ + "literal" + ], + "expected_answerability": "supported", + "expected_missing": [] + }, + { + "query": "Which database stores local state for Sable?", + "relevant": [ + "runtime" + ], + "expected_answerability": null, + "expected_missing": [] + }, + { + "query": "What are the Sable staging gateway port and password?", + "relevant": [ + "stage-port" + ], + "expected_answerability": "partial", + "expected_missing": [ + "password" + ] + }, + { + "query": "What are the Sable staging gateway port and retries?", + "relevant": [ + "stage-port", + "retries" + ], + "expected_answerability": "supported", + "expected_missing": [] + }, + { + "query": "What is the Sable staging gateway timeout?", + "relevant": [ + "timeout-a", + "timeout-b" + ], + "expected_answerability": "conflicting", + "expected_missing": [] + }, + { + "query": "What password does the Sable production gateway require?", + "relevant": [ + "no-password" + ], + "expected_answerability": "supported", + "expected_missing": [] + }, + { + "query": "What is the Sable staging database port?", + "relevant": [], + "expected_answerability": "missing", + "expected_missing": [ + "port" + ] + }, + { + "query": "What is the Sable production gateway timeout?", + "relevant": [], + "expected_answerability": "missing", + "expected_missing": [ + "timeout" + ] + }, + { + "query": "What is the Unknown staging gateway port?", + "relevant": [], + "expected_answerability": "missing", + "expected_missing": [ + "port" + ] + }, + { + "query": "What is the Sable test gateway port?", + "relevant": [], + "expected_answerability": "missing", + "expected_missing": [ + "port" + ] + }, + { + "query": "What are the Sable production gateway retries?", + "relevant": [], + "expected_answerability": "missing", + "expected_missing": [ + "retries" + ] + }, + { + "query": "What is the Sable staging worker timeout?", + "relevant": [], + "expected_answerability": "missing", + "expected_missing": [ + "timeout" + ] + } + ] + } + ] +} diff --git a/docs/superpowers/plans/2026-09-07-structured-facts.md b/docs/superpowers/plans/2026-09-07-structured-facts.md new file mode 100644 index 0000000..7acb300 --- /dev/null +++ b/docs/superpowers/plans/2026-09-07-structured-facts.md @@ -0,0 +1,42 @@ +# Structured Fact Evidence Implementation Plan + +> Use superpowers:subagent-driven-development task-by-task, with independent files and serialized Cargo verification. + +**Goal:** Store local fact evidence and deliver subject-bound partial-answer metadata. +**Architecture:** Redacted text -> SQLite fact projection -> capsule evidence -> final-slice answerability. +**Tech Stack:** Rust, rusqlite, regex, serde; no new models/dependencies. +**Spec:** docs/superpowers/specs/2026-09-07-structured-facts-design.md + +## Global constraints +Keep guard opt-in. Preserve lifecycle, claim revision and byte budgets. No live configuration changes. Cargo --locked --offline -j1, one build at a time. Do not benchmark during builds/tests. + +### 1. Conservative extraction +Files: create crates/kimetsu-brain/src/facts.rs; expose module in lib.rs. +Interface: `pub fn extract(text: &str) -> Vec` with the five fields in the spec. +- [x] RED tests: `extract("Orchid staging gateway port is 7319.")[0]` has subject orchid gateway, environment staging, attribute port, value7319; unrelated database clause cannot acquire gateway value; negated/example/redacted claims emit no facts. +- [x] Implement explicit patterns for existing configuration attributes and literal keys, conservative clause parsing and bounded output. +- [x] GREEN extraction tests and review. + +### 2. Durable projection +Files: fact_store.rs, schema.rs, migrate.rs, projector.rs, context.rs, core/lib.rs; update capsule initializers wherever needed. +Interfaces: `refresh(conn, memory_id)`, `backfill(conn)`, `load(conn, memory_id, claim_revision) -> KimetsuResult>`. +- [x] RED tests migration/backfill, acceptance/correction, rebuild equality, invalidation and temporal refresh, revision mismatch. +- [x] Create schema15 projection indexed bymemory/revision; refresh event writes after redaction; hydrate alongside capsule claim identity. +- [x] GREEN storage and hydration tests; review. + +### 3. Matching and final delivery +Files: fact_query.rs, answerability.rs, serving.rs, hooks.rs. +Interfaces: `parse`, `evaluate` and filtering/notice helpers consuming stored facts and finalcapsules. +- [x] RED tests wrong subject/environment, compound port+password partial, conflicting values, final-budget removal changes supported attributes, compression preservesvalue. +- [x] Implement conservative question decomposition and evidence reconciliation; integrate under guard; recompute metadata inside fit_json; hook notices from visible evidence. +- [x] GREEN focusedtests and review. + +### 4. Evaluation and finish +- [x] Freeze new synthetic scope/partial fixture before inference; extend benchmark observations for answerability metadata if necessary. +- [x] Full workspace and benchmark tests; build release; actual hook/MCP probes. +- [x] Paired measured comparison preserving baseline executable; review findings; document limits and performance; commit verified work/artifacts. + +## Ledger +Ruling: proceed within user's explicit go-ahead, without repeated approval gates. Prior answerabilityguard remains off by default. Storage/extractor independent tasks use subagents; main owns query/render integration. No concurrent Cargo builds. + +Final: source3ae8329, benchmark2c74dad. Workspace1470 pass/6ignored; benchmark132Rust+18Python;6releaseprobespass. Campaign688observations, nohitlosses, newnoise15/18->3/18, exactmetadata36/45bothrepeats. Remainingcompoundretrieval and unsupportedsubjectfallback documented; noheldoutretuning, defaultoff. diff --git a/docs/superpowers/specs/2026-09-07-structured-facts-design.md b/docs/superpowers/specs/2026-09-07-structured-facts-design.md new file mode 100644 index 0000000..c12ec0f --- /dev/null +++ b/docs/superpowers/specs/2026-09-07-structured-facts-design.md @@ -0,0 +1,20 @@ +# Structured fact evidence + +Approved intent: user asked to proceed with structured subject/attribute/value/environment/validity/provenance evidence and partial answers. Existing isolated worktree is retained. No deployment or live configuration changes. + +## Choice +A rebuildable SQLite projection derived from redacted memory text is preferable to more query-time regex scanning or an additional generative extractor. It preserves replay and avoids model calls. Conservative extraction leaves unsupported language unstructured. Existing relevance and lifecycle gates remain authoritative. + +## Contract +`facts::FactClaim { subject: String, attribute: String, value: String, environment: Option, evidence: String }`, serde serializable. `facts::extract(text: &str) -> Vec` extracts only explicit affirmative facts or explicit absence, at most32 per memory. Preserve exact evidence excerpt; normalize matching fields. Never infer project or component from unrelated clauses. + +`fact_store::StoredFact { memory_id: String, claim_revision: String, source_event_id: String, valid_from: Option, valid_to: Option, claim: FactClaim }`. Schema15 stores derived current facts. Acceptance, correction and temporal updates refresh within event transaction; rebuild clears/replays; migration backfills. Reads require matching claim revision and current lifecycle/validity. Historical retrieval must never attach current facts to older text; no match means no projected evidence. + +ContextCapsule carries optional `facts: Vec` internally, hydrated with the text snapshot. Existing constructors initialize empty. Non-memory capsules and daemon compatibility remain valid. + +`fact_query::parse(query) -> Option` recognizes direct single or coordinated attribute value questions with explicit subject and optional environment. Unsupported or ambiguous questions pass through. `fact_query::evaluate(query, capsules) -> Option` reports supported, missing and conflicting attributes with source handles from delivered capsules. Exact normalized subject/environment compatibility is mandatory; no guessing unknown scope. `filter_bundle` may exclude irrelevant capsules only for recognized requests with structured evidence available. Legacy guard remains fallback otherwise. Claims of support must also be visible in final rendered text. + +Serving emits optional answerability metadata inside final serialized budget accounting, recomputed for the final capsule slice. Full, partial, missing, conflicting statuses concern available evidence, not verified truth. Compression must retain evidence; no extra inference calls. Hook adds a concise missing/conflicting notice based on final visible evidence. Feature remains behind existing explicit_fact_guard defaultfalse. + +## Validation +Extraction subject/environment/negation/sensitive text tests; migration/backfill/replay/correction/lifecycle tests; query compound/unknown/contradiction tests; final-budget loss and compression tests; CLI/MCP smoke. Paired BrainBenchmark guardoff/on and earlier-guard/newguard comparisons on existing development plus frozen new subject/environment/partial controls. Measure delivered recall/noise and latency; do not tune on frozen results. One Cargo build at a time with --locked --offline -j1; no build/inference concurrency. Source and benchmark edits stay in their separate audit worktrees. From a961539c591c00bdf7a5670044bfcf86fd2db9c8 Mon Sep 17 00:00:00 2001 From: RodCor Date: Mon, 7 Sep 2026 13:08:36 -0300 Subject: [PATCH 30/34] Document unreleased answerability results and format fact modules --- CHANGELOG.md | 26 ++++++++++++++++++++ README.md | 5 ++++ crates/kimetsu-brain/src/answerability.rs | 12 +++++++--- crates/kimetsu-brain/src/facts.rs | 8 ++++--- crates/kimetsu-brain/src/lib.rs | 4 ++-- docs/answerability.md | 29 +++++++++++++++++++++++ docs/canonical-evaluation.md | 6 +++++ 7 files changed, 82 insertions(+), 8 deletions(-) create mode 100644 docs/answerability.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a9fd45..b93b61f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,32 @@ breaking changes require a major bump. ## Unreleased +### Added + +- Opt-in `broker.explicit_fact_guard` for scoped configuration evidence, partial + answers and conflicting values. Schema 15 stores a rebuildable fact projection + bound to source events, revisions and visible evidence. No additional model + calls; delivered evidence still consumes context tokens. +- Reproducible paired evaluation and per-query delivered evidence. The new + 45-query synthetic fixture retained 24/27 positive hits and reduced unwanted + injections from 15/18 to 3/18. Exact metadata matched 36/45 in both repeats; + p95 was 376.6 → 386.6 ms. Compound retrieval and unsupported subjects remain + gaps. See [answerability](docs/answerability.md) and the [report](docs/audits/2026-09-07-structured-facts.md). +- Configurable reranker cutoff, an optional pinned multilingual reranker and + opt-in shared ONNX inference thread control. Existing model defaults remain. + +### Fixed + +- Durable corrections and cross-writer ANN refresh; delayed feedback is bound to + delivered claim revisions. Lifecycle decisions preserve distinct claims and + archive/restore state; replay and merged sync imports are atomic. +- Final serialized delivery budgets, post-rerank policy, current warm-start + evidence and explicit episode lanes. Free-tier hooks avoid host harvesting. +- Conflict warnings survive intermediate budgets, capsule caps and daemon + transport. Tagged agent-recorded lessons can produce structured evidence. +- Remote context honors an absent server reranker instead of loading the local + stdio configuration and consulting its warm-start cache. + ### Security - Updated transitive `h2` from 0.4.14 to 0.4.16, fixing diff --git a/README.md b/README.md index 6579549..4648a26 100644 --- a/README.md +++ b/README.md @@ -202,6 +202,11 @@ ingest, TLS, Prometheus metrics, and a server-side reranker. Full setup in ## Docs +- **[Unreleased answerability work](docs/answerability.md)**: opt-in scoped facts, + partial/conflicting evidence, configuration and limits. On the new 45-query + synthetic fixture, unwanted injections fell **15/18 → 3/18**, with **24/27 hits + retained**; this is not a new overall benchmark score. [Measured report](docs/audits/2026-09-07-structured-facts.md). + - **[Install & host wiring](https://kimetsu.dev/docs/install)**: every install path, host wiring, auto-harvest and distiller setup, maintenance commands. - **[How Kimetsu Works](https://kimetsu.dev/docs/how-kimetsu-works)**: the brain, the broker, diff --git a/crates/kimetsu-brain/src/answerability.rs b/crates/kimetsu-brain/src/answerability.rs index 75b80e1..d9b5d56 100644 --- a/crates/kimetsu-brain/src/answerability.rs +++ b/crates/kimetsu-brain/src/answerability.rs @@ -337,9 +337,15 @@ pub fn assess(query: &str, text: &str) -> FactEvidence { pub fn filter_bundle(query: &str, bundle: &mut crate::context::ContextBundle) { let request = crate::fact_query::parse(query); for capsule in std::mem::take(&mut bundle.capsules) { - let rejected = if let Some(request) = request.as_ref().filter(|_| !capsule.facts.is_empty()) { - !capsule.facts.iter().any(|fact| crate::fact_query::visible(&capsule, fact) && crate::fact_query::matches(request, &fact.claim)) - } else { assess(query, &capsule.summary) == FactEvidence::MissingValue }; + let rejected = if let Some(request) = request.as_ref().filter(|_| !capsule.facts.is_empty()) + { + !capsule.facts.iter().any(|fact| { + crate::fact_query::visible(&capsule, fact) + && crate::fact_query::matches(request, &fact.claim) + }) + } else { + assess(query, &capsule.summary) == FactEvidence::MissingValue + }; if rejected { bundle.excluded.push(capsule); } else { diff --git a/crates/kimetsu-brain/src/facts.rs b/crates/kimetsu-brain/src/facts.rs index f4cb77f..39a8211 100644 --- a/crates/kimetsu-brain/src/facts.rs +++ b/crates/kimetsu-brain/src/facts.rs @@ -377,9 +377,11 @@ mod tests { canonical_subject("The Orchid’s staging gateway"), ("orchid gateway".into(), Some("staging".into())) ); - assert!(canonical_subject("Orchid staging production gateway") - .0 - .is_empty()); + assert!( + canonical_subject("Orchid staging production gateway") + .0 + .is_empty() + ); assert!(canonical_subject("the production").0.is_empty()); assert_eq!( extract("Orchid gateway `cache.max_entries` = 200.")[0].attribute, diff --git a/crates/kimetsu-brain/src/lib.rs b/crates/kimetsu-brain/src/lib.rs index 986b1d0..7400558 100644 --- a/crates/kimetsu-brain/src/lib.rs +++ b/crates/kimetsu-brain/src/lib.rs @@ -22,10 +22,10 @@ pub mod embeddings; /// Flagship 1 / Story 1.3: episodic work-resume capture, storage, and surface. pub mod episode; pub mod eval; -pub mod facts; -pub mod fact_values; pub mod fact_query; pub mod fact_store; +pub mod fact_values; +pub mod facts; pub mod feedback; pub mod framing; /// #2 knowledge graph: rule-based relation-edge extraction for `memory_edges`. diff --git a/docs/answerability.md b/docs/answerability.md new file mode 100644 index 0000000..e34e85c --- /dev/null +++ b/docs/answerability.md @@ -0,0 +1,29 @@ +# Structured fact answerability (unreleased, opt-in) + +The broker can attach evidence accounting to direct configuration questions: which attributes are supported, missing, or conflicting. This is a bounded syntactic check on retrieved evidence, not a general truth or entailment model. + +## Enable on a build containing this change + +```sh +kimetsu config set broker.explicit_fact_guard true +``` + +The default is `false`. Use the same command with `false` to disable it. If a warm embed daemon is running an older executable, restart it with the updated build before comparing daemon behavior. This documentation describes unreleased code; installing the current published version does not guarantee this feature is available. + +An explicit statement such as `Orchid staging gateway port is 7319.` can support the port part of `What are the Orchid staging gateway port and timeout?`. The response reports timeout missing unless matching evidence is retrieved. Production evidence cannot fill a staging request. Distinct eligible values produce a conflict; equivalent durations such as `30 seconds` and `30000 ms` compare using exact rational arithmetic. + +MCP context responses may contain `answerability` with `status`, `subject`, `environment`, `supported` values and source handles, `missing`, and `conflicting`. Hooks emit a short notice for incomplete or conflicting evidence. A `supported` status means the retrieved evidence supports the recognized attributes, not that the current code or outside world was independently verified. + +## Storage and cost + +Schema 15 adds a rebuildable SQLite fact projection. Facts bind to the memory's claim revision, source event and exact evidence excerpt; corrections refresh it transactionally and replay rebuilds it. Current lifecycle, temporal bounds and text digest checks prevent stale evidence from supporting another revision. The agent record API's tag prefix is recognized without using tags to infer subject or environment. + +Projection maintenance adds write/storage work even when the guard is disabled; ordinary retrieval skips fact hydration while it is disabled. No extra model calls are required. Delivered evidence and metadata still consume agent context tokens. MCP output admission includes serialized metadata in its byte upper bound; that bound is not a measured tokenizer count. + +## Measured results and limits + +On the frozen 45-query synthetic fixture, unwanted injections fell from **15/18 to 3/18**, with **24/27 positive hits retained**. Exact metadata matched **36/45** cases in each of two repeats: 33/42 direct fact questions and three broad controls. P95 was **376.6 → 386.6 ms**; mean MCP result bytes were **613.5 → 651.2**. The development and prior answerability fixtures had no positive-hit losses. + +These are authored evidence-delivery tests, not generated-answer accuracy or a new overall BrainBench score. Compound queries can still miss one requested attribute. Unsupported subject wording can fall back to legacy retrieval and return unrelated evidence. Conflict detection covers the bounded retrieved pool, not every memory in the corpus. The guard remains opt-in; the optional multilingual reranker is not promoted to the default. + +See the [full report and reproducible artifacts](audits/2026-09-07-structured-facts.md), [canonical evaluation contract](canonical-evaluation.md), and [maintenance guidance](memory-maintenance.md). diff --git a/docs/canonical-evaluation.md b/docs/canonical-evaluation.md index df7cd98..6193780 100644 --- a/docs/canonical-evaluation.md +++ b/docs/canonical-evaluation.md @@ -1,5 +1,11 @@ # Canonical retrieval evaluation +The [unreleased structured-fact evaluation](audits/2026-09-07-structured-facts.md) +adds exact status/value/missing/conflict checks for every repeat and validates +that support handles refer to final delivered capsules. Hit@4 alone is not +complete answerability: retrieving retries while omitting a requested port is +still a hit. See [configuration and limitations](answerability.md). + Brain context serving, the tuner, and local evaluation share `ServingPolicy`: pool 6, final cap 3, reranker score floor 0.30, and a default delivery budget of 6000. Explicit benchmark pool/cap experiments are reported as overrides. The final compact MCP renderer determines delivered capsules and cost after reranking, arbitration, compression, and admission. Cost is a conservative **serialized UTF-8 byte upper bound**, including the MCP text envelope and escaping. It is not billed tokens or a tokenizer measurement. Parity means the same effective query, configuration, models, cap, budget, and rendering inputs. Offline comparisons disable ambient augmentation and session warm-start; they cannot replay an unstored historical workspace snapshot. MCP can augment its query before this boundary and add a warm-start block through the final-budget helper. Evaluation substitutes a deterministic 26-character exposure ID for the real 26-character ULID. Capsule IDs are random but have the same serialized length. From a7efc93d89e16a33be95160a06b0215a1cb8350a Mon Sep 17 00:00:00 2001 From: RodCor Date: Mon, 7 Sep 2026 13:11:59 -0300 Subject: [PATCH 31/34] Resolve Clippy warnings in memory hardening code --- crates/kimetsu-brain/src/answerability.rs | 2 +- crates/kimetsu-brain/src/fact_store.rs | 2 +- crates/kimetsu-brain/src/project.rs | 4 +- crates/kimetsu-brain/src/schema.rs | 110 +++++++++++----------- 4 files changed, 59 insertions(+), 59 deletions(-) diff --git a/crates/kimetsu-brain/src/answerability.rs b/crates/kimetsu-brain/src/answerability.rs index d9b5d56..dd3ae22 100644 --- a/crates/kimetsu-brain/src/answerability.rs +++ b/crates/kimetsu-brain/src/answerability.rs @@ -99,7 +99,7 @@ fn clause_supports(query: &str, body: &str, start: usize, end: usize) -> bool { .char_indices() .filter(|(i, c)| boundary(*i, *c)) .map(|(i, _)| i + 1) - .last() + .next_back() .unwrap_or(0); let right = body[end..] .char_indices() diff --git a/crates/kimetsu-brain/src/fact_store.rs b/crates/kimetsu-brain/src/fact_store.rs index e644fff..2e893b3 100644 --- a/crates/kimetsu-brain/src/fact_store.rs +++ b/crates/kimetsu-brain/src/fact_store.rs @@ -239,7 +239,7 @@ mod load_tests { use kimetsu_core::{event::Event, ids::RunId}; fn apply(c: &Connection, kind: &str, payload: serde_json::Value) -> Event { let e = Event::new(RunId::new(), kind, payload); - crate::projector::apply_events(c, &[e.clone()]).unwrap(); + crate::projector::apply_events(c, std::slice::from_ref(&e)).unwrap(); e } fn seeded() -> Connection { diff --git a/crates/kimetsu-brain/src/project.rs b/crates/kimetsu-brain/src/project.rs index f428acb..15218a4 100644 --- a/crates/kimetsu-brain/src/project.rs +++ b/crates/kimetsu-brain/src/project.rs @@ -379,7 +379,7 @@ impl BrainSession { /// survives a second resolution at the injected/production boundary. pub fn resolve_request_floors(&self, request: &mut ContextRequest) { request.include_fact_evidence |= self.config.broker.explicit_fact_guard; - let semantic = request.min_semantic_score_override.unwrap_or_else(|| { + let semantic = request.min_semantic_score_override.unwrap_or({ if request.min_semantic_score == 0.0 { self.config.broker.min_semantic_score } else { @@ -392,7 +392,7 @@ impl BrainSession { } else { semantic }; - request.min_lexical_coverage = request.min_lexical_coverage_override.unwrap_or_else(|| { + request.min_lexical_coverage = request.min_lexical_coverage_override.unwrap_or({ if request.min_lexical_coverage == 0.0 { self.config.broker.min_lexical_coverage } else { diff --git a/crates/kimetsu-brain/src/schema.rs b/crates/kimetsu-brain/src/schema.rs index 0bd116c..960dbdb 100644 --- a/crates/kimetsu-brain/src/schema.rs +++ b/crates/kimetsu-brain/src/schema.rs @@ -689,6 +689,61 @@ fn table_has_column(conn: &Connection, table: &str, column: &str) -> KimetsuResu Ok(false) } +/// Retain correction lineage and invalidate indexes across connections. +pub fn migrate_v11_to_v12(conn: &Connection) -> KimetsuResult<()> { + conn.execute_batch("CREATE TABLE IF NOT EXISTS memory_revisions ( + revision_id INTEGER PRIMARY KEY, memory_id TEXT NOT NULL, + event_id TEXT NOT NULL UNIQUE, text TEXT NOT NULL, kind TEXT NOT NULL, + known_at TEXT NOT NULL, effective_at TEXT NOT NULL, + confidence REAL NOT NULL, use_count INTEGER NOT NULL, usefulness_score REAL NOT NULL); + CREATE INDEX IF NOT EXISTS idx_memory_revisions_time ON memory_revisions(memory_id, known_at, effective_at); + CREATE TABLE IF NOT EXISTS corpus_revision (id INTEGER PRIMARY KEY CHECK(id=1), revision INTEGER NOT NULL); + INSERT OR IGNORE INTO corpus_revision VALUES (1,0); + CREATE TRIGGER IF NOT EXISTS corpus_insert AFTER INSERT ON memories BEGIN UPDATE corpus_revision SET revision=revision+1 WHERE id=1; END; + CREATE TRIGGER IF NOT EXISTS corpus_delete AFTER DELETE ON memories BEGIN UPDATE corpus_revision SET revision=revision+1 WHERE id=1; END; + CREATE TRIGGER IF NOT EXISTS corpus_update AFTER UPDATE OF embedding, embedding_model, text, invalidated_at, superseded_by ON memories BEGIN UPDATE corpus_revision SET revision=revision+1 WHERE id=1; END;")?; + Ok(()) +} + +/// Keep proposed applicability through review and replay. +pub fn migrate_v12_to_v13(conn: &Connection) -> KimetsuResult<()> { + // Synthetic partial schemas used by migration tooling may omit proposals. + if !table_has_column(conn, "memory_proposals", "proposal_id")? { + return Ok(()); + } + add_column_if_missing(conn, "memory_proposals", "valid_from TEXT")?; + add_column_if_missing(conn, "memory_proposals", "valid_to TEXT")?; + Ok(()) +} + +/// Optional stable task identity. Empty string retains the original legacy lane. +pub(crate) fn migrate_v13_to_v14(conn: &Connection) -> KimetsuResult<()> { + crate::episode::create_work_episodes_table(conn)?; + add_column_if_missing(conn, "work_episodes", "identity TEXT NOT NULL DEFAULT ''")?; + conn.execute_batch("CREATE INDEX IF NOT EXISTS idx_episodes_identity ON work_episodes(repo_root, identity, superseded_by)")?; + Ok(()) +} + +/// Derived structured evidence is replayable and never replaces memory text. +pub(crate) fn migrate_v14_to_v15(conn: &Connection) -> KimetsuResult<()> { + conn.execute_batch( + "CREATE TABLE IF NOT EXISTS memory_facts ( + memory_id TEXT NOT NULL, claim_revision TEXT NOT NULL, ordinal INTEGER NOT NULL, + source_event_id TEXT NOT NULL, source_digest TEXT NOT NULL, claim_json TEXT NOT NULL, + PRIMARY KEY(memory_id,claim_revision,ordinal));", + )?; + // Migration tools/tests can intentionally provide incomplete old schemas. + for column in ["memory_id", "text", "source_event_id"] { + if !table_has_column(conn, "memories", column)? { + return Ok(()); + } + } + if !table_has_column(conn, "memory_revisions", "revision_id")? { + return Ok(()); + } + crate::fact_store::backfill(conn) +} + // --------------------------------------------------------------------------- // Tests // --------------------------------------------------------------------------- @@ -1201,58 +1256,3 @@ mod tests { ); } } - -/// Retain correction lineage and invalidate indexes across connections. -pub fn migrate_v11_to_v12(conn: &Connection) -> KimetsuResult<()> { - conn.execute_batch("CREATE TABLE IF NOT EXISTS memory_revisions ( - revision_id INTEGER PRIMARY KEY, memory_id TEXT NOT NULL, - event_id TEXT NOT NULL UNIQUE, text TEXT NOT NULL, kind TEXT NOT NULL, - known_at TEXT NOT NULL, effective_at TEXT NOT NULL, - confidence REAL NOT NULL, use_count INTEGER NOT NULL, usefulness_score REAL NOT NULL); - CREATE INDEX IF NOT EXISTS idx_memory_revisions_time ON memory_revisions(memory_id, known_at, effective_at); - CREATE TABLE IF NOT EXISTS corpus_revision (id INTEGER PRIMARY KEY CHECK(id=1), revision INTEGER NOT NULL); - INSERT OR IGNORE INTO corpus_revision VALUES (1,0); - CREATE TRIGGER IF NOT EXISTS corpus_insert AFTER INSERT ON memories BEGIN UPDATE corpus_revision SET revision=revision+1 WHERE id=1; END; - CREATE TRIGGER IF NOT EXISTS corpus_delete AFTER DELETE ON memories BEGIN UPDATE corpus_revision SET revision=revision+1 WHERE id=1; END; - CREATE TRIGGER IF NOT EXISTS corpus_update AFTER UPDATE OF embedding, embedding_model, text, invalidated_at, superseded_by ON memories BEGIN UPDATE corpus_revision SET revision=revision+1 WHERE id=1; END;")?; - Ok(()) -} - -/// Keep proposed applicability through review and replay. -pub fn migrate_v12_to_v13(conn: &Connection) -> KimetsuResult<()> { - // Synthetic partial schemas used by migration tooling may omit proposals. - if !table_has_column(conn, "memory_proposals", "proposal_id")? { - return Ok(()); - } - add_column_if_missing(conn, "memory_proposals", "valid_from TEXT")?; - add_column_if_missing(conn, "memory_proposals", "valid_to TEXT")?; - Ok(()) -} - -/// Optional stable task identity. Empty string retains the original legacy lane. -pub(crate) fn migrate_v13_to_v14(conn: &Connection) -> KimetsuResult<()> { - crate::episode::create_work_episodes_table(conn)?; - add_column_if_missing(conn, "work_episodes", "identity TEXT NOT NULL DEFAULT ''")?; - conn.execute_batch("CREATE INDEX IF NOT EXISTS idx_episodes_identity ON work_episodes(repo_root, identity, superseded_by)")?; - Ok(()) -} - -/// Derived structured evidence is replayable and never replaces memory text. -pub(crate) fn migrate_v14_to_v15(conn: &Connection) -> KimetsuResult<()> { - conn.execute_batch( - "CREATE TABLE IF NOT EXISTS memory_facts ( - memory_id TEXT NOT NULL, claim_revision TEXT NOT NULL, ordinal INTEGER NOT NULL, - source_event_id TEXT NOT NULL, source_digest TEXT NOT NULL, claim_json TEXT NOT NULL, - PRIMARY KEY(memory_id,claim_revision,ordinal));", - )?; - // Migration tools/tests can intentionally provide incomplete old schemas. - for column in ["memory_id", "text", "source_event_id"] { - if !table_has_column(conn, "memories", column)? { - return Ok(()); - } - } - if !table_has_column(conn, "memory_revisions", "revision_id")? { - return Ok(()); - } - crate::fact_store::backfill(conn) -} From b8990cf39d18adcaf1507d7a21bc5337945baa7c Mon Sep 17 00:00:00 2001 From: RodCor Date: Mon, 7 Sep 2026 13:13:37 -0300 Subject: [PATCH 32/34] Remove stale rerank floor comment from daemon state --- crates/kimetsu-cli/src/embed_daemon/server.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/kimetsu-cli/src/embed_daemon/server.rs b/crates/kimetsu-cli/src/embed_daemon/server.rs index ace1fab..f2f55e4 100644 --- a/crates/kimetsu-cli/src/embed_daemon/server.rs +++ b/crates/kimetsu-cli/src/embed_daemon/server.rs @@ -19,8 +19,6 @@ use std::time::Instant; /// NOTE: summaries must stay FULL — truncating them cratered recall. const RERANK_POOL: usize = kimetsu_brain::serving::RERANK_POOL; -/// Sigmoid-score floor — capsules the cross-encoder judges below this are noise. - /// Process-global state shared by all worker threads. pub struct DaemonState { pub embedder: Box, From f62b7b71a38835d05ab13c17894cf644be91120d Mon Sep 17 00:00:00 2001 From: RodCor Date: Mon, 7 Sep 2026 23:20:32 -0300 Subject: [PATCH 33/34] Prepare v2.8.0 and harden remote paths and Bedrock transport --- .github/workflows/release.yml | 6 +- CHANGELOG.md | 16 +++ Cargo.lock | 18 ++-- Cargo.toml | 2 +- README.md | 2 +- crates/kimetsu-agent/Cargo.toml | 4 +- crates/kimetsu-agent/src/bedrock.rs | 108 ++++++++++++++++----- crates/kimetsu-brain/Cargo.toml | 2 +- crates/kimetsu-chat/Cargo.toml | 6 +- crates/kimetsu-cli/Cargo.toml | 8 +- crates/kimetsu-e2e/Cargo.toml | 6 +- crates/kimetsu-remote/src/app.rs | 51 ++++++++++ crates/kimetsu-remote/src/repo.rs | 105 +++++++++++++++++++- docs/answerability.md | 4 +- docs/audits/2026-09-08-release-security.md | 107 ++++++++++++++++++++ 15 files changed, 395 insertions(+), 50 deletions(-) create mode 100644 docs/audits/2026-09-08-release-security.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 91d3a86..e1cd690 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -197,11 +197,15 @@ jobs: BEGIN { include = 0 } /^## / { if (include == 1) exit - if ($2 == v) include = 1 + if ($2 == v || $2 == v ":") include = 1 else next } include == 1 { print } ' CHANGELOG.md > release-notes.md + if [ ! -s release-notes.md ]; then + echo "::error::No changelog content extracted for v${VERSION}" + exit 1 + fi else echo "Release v${VERSION}" > release-notes.md echo "" >> release-notes.md diff --git a/CHANGELOG.md b/CHANGELOG.md index b93b61f..fa4b6ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,13 @@ breaking changes require a major bump. ## Unreleased +## v2.8.0: Durable memory and scoped answerability + +Structured answerability remains opt-in. Existing brains +upgrade to schema 15 when opened by this version. Memory projection maintenance +adds local storage/write work even with the guard disabled; no extra model calls +are required for fact extraction or evidence accounting. + ### Added - Opt-in `broker.explicit_fact_guard` for scoped configuration evidence, partial @@ -34,11 +41,20 @@ breaking changes require a major bump. transport. Tagged agent-recorded lessons can produce structured evidence. - Remote context honors an absent server reranker instead of loading the local stdio configuration and consulting its warm-start cache. +- Release notes correctly extract changelog headings with a version followed by + a colon; empty extracted notes now fail the release job. ### Security - Updated transitive `h2` from 0.4.14 to 0.4.16, fixing `RUSTSEC-2026-0258` (unbounded empty HTTP/2 DATA frames). +- Reject repository identifiers that alias Windows paths and reject redirected + repository roots. Validate existing brain state paths before first-use shortcuts. +- Restrict Bedrock region values to a hostname label, encode model IDs as path + segments, require HTTPS, and disable redirects for signed requests. +- Replace the yanked `der` 0.8.0 dependency with 0.8.2. See the + [release security review](docs/audits/2026-09-08-release-security.md) for the + dependency audit and the disposition of existing code-scanning alerts. ## v2.7.0: Retrieval that knows when to stay silent diff --git a/Cargo.lock b/Cargo.lock index a8e1d2f..843a68e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -982,9 +982,9 @@ dependencies = [ [[package]] name = "der" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71fd89660b2dc699704064e59e9dba0147b903e85319429e131620d022be411b" +checksum = "a878c850e9e421b20262e9b41f9c860e4785fa07541c266b62ff9d1ef998a80a" dependencies = [ "pem-rfc7468", "zeroize", @@ -1989,7 +1989,7 @@ dependencies = [ [[package]] name = "kimetsu-agent" -version = "2.7.0" +version = "2.8.0" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -2009,7 +2009,7 @@ dependencies = [ [[package]] name = "kimetsu-brain" -version = "2.7.0" +version = "2.8.0" dependencies = [ "blake3", "fastembed", @@ -2031,7 +2031,7 @@ dependencies = [ [[package]] name = "kimetsu-chat" -version = "2.7.0" +version = "2.8.0" dependencies = [ "base64 0.22.1", "crossterm", @@ -2047,7 +2047,7 @@ dependencies = [ [[package]] name = "kimetsu-cli" -version = "2.7.0" +version = "2.8.0" dependencies = [ "clap", "flate2", @@ -2072,7 +2072,7 @@ dependencies = [ [[package]] name = "kimetsu-core" -version = "2.7.0" +version = "2.8.0" dependencies = [ "serde", "serde_json", @@ -2083,7 +2083,7 @@ dependencies = [ [[package]] name = "kimetsu-e2e" -version = "2.7.0" +version = "2.8.0" dependencies = [ "kimetsu-agent", "kimetsu-brain", @@ -2096,7 +2096,7 @@ dependencies = [ [[package]] name = "kimetsu-remote" -version = "2.7.0" +version = "2.8.0" dependencies = [ "axum", "axum-server", diff --git a/Cargo.toml b/Cargo.toml index 77d977f..64702a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ resolver = "3" # everything below except the per-crate `description`, `keywords`, # and `categories` which live in each `[package]` block since # crates.io enforces them per crate. -version = "2.7.0" +version = "2.8.0" edition = "2024" # Rust ecosystem dual-license (matches tokio, serde, fastembed-rs, # etc.). UNLICENSED would block crates.io entirely. diff --git a/README.md b/README.md index 4648a26..299d651 100644 --- a/README.md +++ b/README.md @@ -202,7 +202,7 @@ ingest, TLS, Prometheus metrics, and a server-side reranker. Full setup in ## Docs -- **[Unreleased answerability work](docs/answerability.md)**: opt-in scoped facts, +- **[v2.8.0 answerability](docs/answerability.md)**: opt-in scoped facts, partial/conflicting evidence, configuration and limits. On the new 45-query synthetic fixture, unwanted injections fell **15/18 → 3/18**, with **24/27 hits retained**; this is not a new overall benchmark score. [Measured report](docs/audits/2026-09-07-structured-facts.md). diff --git a/crates/kimetsu-agent/Cargo.toml b/crates/kimetsu-agent/Cargo.toml index a45ebda..a816a1e 100644 --- a/crates/kimetsu-agent/Cargo.toml +++ b/crates/kimetsu-agent/Cargo.toml @@ -27,8 +27,8 @@ aws-sigv4.workspace = true aws-smithy-runtime-api.workspace = true blake3.workspace = true http.workspace = true -kimetsu-brain = { path = "../kimetsu-brain", version = "2.7.0" } -kimetsu-core = { path = "../kimetsu-core", version = "2.7.0" } +kimetsu-brain = { path = "../kimetsu-brain", version = "2.8.0" } +kimetsu-core = { path = "../kimetsu-core", version = "2.8.0" } regex.workspace = true reqwest.workspace = true rusqlite.workspace = true diff --git a/crates/kimetsu-agent/src/bedrock.rs b/crates/kimetsu-agent/src/bedrock.rs index 088000d..afd1fdd 100644 --- a/crates/kimetsu-agent/src/bedrock.rs +++ b/crates/kimetsu-agent/src/bedrock.rs @@ -94,9 +94,8 @@ impl BedrockProvider { return Ok(None); }; - let client = Client::builder() - .timeout(Duration::from_secs(config.model.request_timeout_secs)) - .build()?; + bedrock_endpoint(®ion, &config.model.model)?; + let client = bedrock_client(config.model.request_timeout_secs)?; Ok(Some(Self { client, @@ -123,16 +122,17 @@ impl BedrockProvider { temperature: f32, timeout_secs: u64, ) -> KimetsuResult { - let client = Client::builder() - .timeout(Duration::from_secs(timeout_secs)) - .build()?; + let region = region.into(); + let model_id = model_id.into(); + bedrock_endpoint(®ion, &model_id)?; + let client = bedrock_client(timeout_secs)?; Ok(Self { client, access_key: SecretString::new(access_key.into()), secret_key: SecretString::new(secret_key.into()), session_token: session_token.map(SecretString::new), - region: region.into(), - model_id: model_id.into(), + region, + model_id, max_output_tokens, temperature, timeout_secs, @@ -238,23 +238,19 @@ impl ModelProvider for BedrockProvider { &request, ); let payload = serde_json::to_vec(&body)?; - let url = format!( - "https://bedrock-runtime.{}.amazonaws.com/model/{}/invoke", - self.region, - url_encode_model_id(&self.model_id), - ); + let url = bedrock_endpoint(&self.region, &self.model_id)?; let headers = sign_bedrock_headers( self.access_key.expose_secret(), self.secret_key.expose_secret(), self.session_token.as_ref().map(|s| s.expose_secret()), &self.region, - &url, + url.as_str(), &payload, SystemTime::now(), )?; - let mut req = self.client.post(&url); + let mut req = self.client.post(url); for (name, value) in &headers { req = req.header(name.as_str(), value.as_str()); } @@ -274,13 +270,35 @@ impl ModelProvider for BedrockProvider { } } -/// Percent-encode characters in model IDs that could be misinterpreted in URL -/// paths. Bedrock model IDs typically contain only alphanumerics, hyphens, -/// dots, and colons — but the colon must be percent-encoded in URL paths to -/// avoid ambiguity with `scheme:`. -fn url_encode_model_id(model_id: &str) -> String { - // Only colons need encoding in practice; percent-encode the rest if needed. - model_id.replace(':', "%3A") +fn bedrock_client(timeout_secs: u64) -> KimetsuResult { + Ok(Client::builder() + .https_only(true) + .redirect(reqwest::redirect::Policy::none()) + .timeout(Duration::from_secs(timeout_secs)) + .build()?) +} + +/// Only an AWS region label can influence the fixed HTTPS authority. Model IDs +/// are encoded as one path segment; credentials are never sent across redirects. +fn bedrock_endpoint(region: &str, model_id: &str) -> KimetsuResult { + if region.is_empty() + || region.len() > 63 + || !region + .bytes() + .all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'-') + || region.starts_with('-') + || region.ends_with('-') + { + return Err("invalid AWS region: expected a lowercase region label".into()); + } + if model_id.is_empty() { + return Err("Bedrock model id is empty".into()); + } + let mut url = reqwest::Url::parse(&format!("https://bedrock-runtime.{region}.amazonaws.com"))?; + url.path_segments_mut() + .map_err(|_| "invalid Bedrock endpoint")? + .extend(["model", model_id, "invoke"]); + Ok(url) } #[cfg(test)] @@ -289,6 +307,52 @@ mod tests { use crate::model::{MessageContent, MessageRole, ModelMessage, ToolChoice}; use serde_json::json; + #[test] + fn endpoint_rejects_region_authority_injection_and_encodes_model_id() { + for region in [ + "", + "us-east-1@attacker.test", + "us-east-1/evil", + "us-east-1?x", + "us-east-1#x", + "us-east-1:443", + "US-EAST-1", + "us-east-1.evil", + ] { + assert!( + bedrock_endpoint(region, "test-model").is_err(), + "region {region}" + ); + } + let endpoint = bedrock_endpoint( + "us-east-1", + "arn:aws:bedrock:us-east-1:123:model/example?x#y", + ) + .unwrap(); + assert_eq!(endpoint.scheme(), "https"); + assert_eq!( + endpoint.host_str(), + Some("bedrock-runtime.us-east-1.amazonaws.com") + ); + assert_eq!(endpoint.query(), None); + assert_eq!(endpoint.fragment(), None); + assert!(endpoint.path().contains("%2F")); + assert!(endpoint.path().ends_with("/invoke")); + } + + #[test] + fn bedrock_client_rejects_plaintext_before_connecting() { + let error = bedrock_client(1) + .unwrap() + .post("http://127.0.0.1:9/") + .send() + .unwrap_err(); + assert!( + error.is_builder(), + "HTTPS-only validation must reject before I/O: {error}" + ); + } + fn simple_request() -> ModelRequest { ModelRequest { messages: vec![ModelMessage::user_text("Hello")], diff --git a/crates/kimetsu-brain/Cargo.toml b/crates/kimetsu-brain/Cargo.toml index 42d1d1f..aa29641 100644 --- a/crates/kimetsu-brain/Cargo.toml +++ b/crates/kimetsu-brain/Cargo.toml @@ -52,7 +52,7 @@ hf-hub = { version = "0.5", optional = true, default-features = false, features # already native (ort). The lean build never links it. usearch = { version = "2", optional = true } ignore.workspace = true -kimetsu-core = { path = "../kimetsu-core", version = "2.7.0" } +kimetsu-core = { path = "../kimetsu-core", version = "2.8.0" } # v0.4.5: regex backs the secret-redaction patterns in # `kimetsu_brain::redact`. The crate is already a workspace pin # elsewhere; we just opt this crate into it now. diff --git a/crates/kimetsu-chat/Cargo.toml b/crates/kimetsu-chat/Cargo.toml index b44d91b..7bbf31e 100644 --- a/crates/kimetsu-chat/Cargo.toml +++ b/crates/kimetsu-chat/Cargo.toml @@ -38,9 +38,9 @@ openclaw = ["dep:json5"] # surface, not a benchmark harness. [dependencies] -kimetsu-agent = { path = "../kimetsu-agent", version = "2.7.0" } -kimetsu-brain = { path = "../kimetsu-brain", version = "2.7.0" } -kimetsu-core = { path = "../kimetsu-core", version = "2.7.0" } +kimetsu-agent = { path = "../kimetsu-agent", version = "2.8.0" } +kimetsu-brain = { path = "../kimetsu-brain", version = "2.8.0" } +kimetsu-core = { path = "../kimetsu-core", version = "2.8.0" } base64.workspace = true crossterm.workspace = true json5 = { workspace = true, optional = true } diff --git a/crates/kimetsu-cli/Cargo.toml b/crates/kimetsu-cli/Cargo.toml index b16d7f8..0438519 100644 --- a/crates/kimetsu-cli/Cargo.toml +++ b/crates/kimetsu-cli/Cargo.toml @@ -43,10 +43,10 @@ path = "src/main.rs" [dependencies] clap.workspace = true -kimetsu-agent = { path = "../kimetsu-agent", version = "2.7.0" } -kimetsu-brain = { path = "../kimetsu-brain", version = "2.7.0" } -kimetsu-chat = { path = "../kimetsu-chat", version = "2.7.0" } -kimetsu-core = { path = "../kimetsu-core", version = "2.7.0" } +kimetsu-agent = { path = "../kimetsu-agent", version = "2.8.0" } +kimetsu-brain = { path = "../kimetsu-brain", version = "2.8.0" } +kimetsu-chat = { path = "../kimetsu-chat", version = "2.8.0" } +kimetsu-core = { path = "../kimetsu-core", version = "2.8.0" } # v0.4.6: `kimetsu doctor` serializes its report struct so --json # output can be piped into CI / hooks. flate2 = "1" diff --git a/crates/kimetsu-e2e/Cargo.toml b/crates/kimetsu-e2e/Cargo.toml index e267b3c..ba34c6e 100644 --- a/crates/kimetsu-e2e/Cargo.toml +++ b/crates/kimetsu-e2e/Cargo.toml @@ -20,9 +20,9 @@ publish = false # Real (non-dev) deps so the test fixtures + scripted provider can be # re-exported from `kimetsu_e2e::prelude` to the integration tests in # `tests/`. Integration tests treat this crate as a normal library. -kimetsu-agent = { path = "../kimetsu-agent", version = "2.7.0" } -kimetsu-brain = { path = "../kimetsu-brain", version = "2.7.0" } -kimetsu-core = { path = "../kimetsu-core", version = "2.7.0" } +kimetsu-agent = { path = "../kimetsu-agent", version = "2.8.0" } +kimetsu-brain = { path = "../kimetsu-brain", version = "2.8.0" } +kimetsu-core = { path = "../kimetsu-core", version = "2.8.0" } rusqlite.workspace = true serde_json.workspace = true time.workspace = true diff --git a/crates/kimetsu-remote/src/app.rs b/crates/kimetsu-remote/src/app.rs index 0e73dbf..606e4fd 100644 --- a/crates/kimetsu-remote/src/app.rs +++ b/crates/kimetsu-remote/src/app.rs @@ -192,6 +192,57 @@ mod tests { assert!(msg.contains("not available in remote mode"), "got: {v}"); } + #[tokio::test] + async fn every_excluded_catalog_tool_is_blocked_at_http_boundary() { + for host_only in [ + "kimetsu_bridge_status", + "kimetsu_skills_search", + "kimetsu_bridge_import", + "kimetsu_bridge_export", + "kimetsu_bridge_sync", + "kimetsu_plugin_install", + ] { + assert!( + !crate::catalog::REMOTE_TOOLS.contains(host_only), + "host filesystem tool {host_only} must never be remotely callable" + ); + } + let tmp = tempfile::tempdir().unwrap(); + let app = build_router(state_with(tmp.path())); + let catalog = kimetsu_chat::dispatch( + "tools/list", + json!({}), + tmp.path(), + &kimetsu_chat::SkillConfig::default(), + None, + ) + .unwrap(); + let mut blocked = 0; + for tool in catalog["tools"].as_array().unwrap() { + let name = tool["name"].as_str().unwrap(); + if crate::catalog::REMOTE_TOOLS.contains(name) { + continue; + } + let response = app.clone().oneshot(post("web", Some("tok_admin"), json!({ + "jsonrpc":"2.0", "id":1, "method":"tools/call", + "params":{"name":name, "arguments":{"selection":"../outside", "target":"codex", "force":true}} + }))).await.unwrap(); + let result = body_json(response).await; + assert!( + result["error"]["message"] + .as_str() + .unwrap_or_default() + .contains("not available in remote mode"), + "{name}: {result}" + ); + blocked += 1; + } + assert!(blocked >= 8, "must exercise the host-only catalog"); + for directory in [".codex", ".claude", ".cursor"] { + assert!(!tmp.path().join("web").join(directory).exists()); + } + } + #[tokio::test] async fn per_repo_token_cannot_write_shared_user_memory() { let tmp = tempfile::tempdir().unwrap(); diff --git a/crates/kimetsu-remote/src/repo.rs b/crates/kimetsu-remote/src/repo.rs index c55d4e4..9a195d1 100644 --- a/crates/kimetsu-remote/src/repo.rs +++ b/crates/kimetsu-remote/src/repo.rs @@ -22,13 +22,25 @@ pub fn sanitize_repo_id(raw: &str) -> Result { if s.contains("..") { return Err("repo id may not contain '..'".to_string()); } + if s.ends_with('.') { + return Err("repo id may not end with '.'".to_string()); + } if !s .chars() .all(|c| c.is_ascii_alphanumeric() || c == '.' || c == '_' || c == '-') { return Err("repo id may contain only letters, digits, '.', '_', '-'".to_string()); } - Ok(s.to_ascii_lowercase()) + let id = s.to_ascii_lowercase(); + let stem = id.split('.').next().unwrap_or_default(); + if matches!(stem, "con" | "prn" | "aux" | "nul") + || (stem.len() == 4 + && (stem.starts_with("com") || stem.starts_with("lpt")) + && matches!(stem.as_bytes()[3], b'1'..=b'9')) + { + return Err("repo id may not use a reserved device name".to_string()); + } + Ok(id) } /// Resolve a repo id to its brain root `/`, asserting the result @@ -39,6 +51,26 @@ pub fn resolve_brain_root(data_dir: &Path, repo: &str) -> Result { + if metadata.file_type().is_symlink() || !metadata.is_dir() { + return Err("repo root must be a directory, not a link".to_string()); + } + let expected = data_dir + .canonicalize() + .map_err(|e| format!("resolve data dir: {e}"))? + .join(&id); + let actual = root + .canonicalize() + .map_err(|e| format!("resolve repo root: {e}"))?; + // Also catches junctions and aliases into a different tenant under data_dir. + if actual != expected { + return Err("repo root is redirected to another directory".to_string()); + } + } + Err(error) if error.kind() == std::io::ErrorKind::NotFound => {} + Err(error) => return Err(format!("inspect repo root: {error}")), + } Ok(root) } @@ -46,6 +78,9 @@ pub fn resolve_brain_root(data_dir: &Path, repo: &str) -> Result Result<(), String> { + kimetsu_core::paths::ProjectPaths::at_root(root) + .validate_state_dir() + .map_err(|e| format!("invalid brain state paths: {e}"))?; if root.join(".kimetsu").join("brain.db").is_file() { return Ok(()); } @@ -77,6 +112,74 @@ mod tests { assert!(sanitize_repo_id(&"x".repeat(129)).is_err()); } + #[test] + fn rejects_windows_path_aliases_on_every_platform() { + for bad in [ + "web.", "web...", "CON", "con.txt", "nul", "aux", "prn", "COM1", "lpt9.log", + ] { + assert!( + sanitize_repo_id(bad).is_err(), + "must reject filesystem alias {bad}" + ); + } + assert!(sanitize_repo_id("console").is_ok()); + assert!(sanitize_repo_id("com10").is_ok()); + } + + #[cfg(unix)] + #[test] + fn rejects_repo_symlinks_and_existing_state_symlinks() { + use std::os::unix::fs::symlink; + let data = tempfile::tempdir().unwrap(); + let outside = tempfile::tempdir().unwrap(); + symlink(outside.path(), data.path().join("escape")).unwrap(); + assert!(resolve_brain_root(data.path(), "escape").is_err()); + std::fs::create_dir(data.path().join("other")).unwrap(); + symlink(data.path().join("other"), data.path().join("alias")).unwrap(); + assert!(resolve_brain_root(data.path(), "alias").is_err()); + + let root = data.path().join("safe"); + std::fs::create_dir(&root).unwrap(); + std::fs::write(outside.path().join("brain.db"), "outside sentinel").unwrap(); + symlink(outside.path(), root.join(".kimetsu")).unwrap(); + assert!(ensure_initialized(&root).is_err()); + assert_eq!( + std::fs::read_to_string(outside.path().join("brain.db")).unwrap(), + "outside sentinel" + ); + } + + #[cfg(windows)] + #[test] + fn rejects_repo_and_state_junctions() { + fn junction(target: &Path, link: &Path) { + let result = std::process::Command::new("cmd") + .args(["/C", "mklink", "/J"]) + .arg(link) + .arg(target) + .output() + .unwrap(); + assert!( + result.status.success(), + "create test junction: {}", + String::from_utf8_lossy(&result.stderr) + ); + } + let data = tempfile::tempdir().unwrap(); + let outside = tempfile::tempdir().unwrap(); + junction(outside.path(), &data.path().join("escape")); + assert!(resolve_brain_root(data.path(), "escape").is_err()); + let root = data.path().join("safe"); + std::fs::create_dir(&root).unwrap(); + std::fs::write(outside.path().join("brain.db"), "outside sentinel").unwrap(); + junction(outside.path(), &root.join(".kimetsu")); + assert!(ensure_initialized(&root).is_err()); + assert_eq!( + std::fs::read_to_string(outside.path().join("brain.db")).unwrap(), + "outside sentinel" + ); + } + #[test] fn resolved_root_stays_in_data_dir() { let data = Path::new("/srv/kbrains"); diff --git a/docs/answerability.md b/docs/answerability.md index e34e85c..5dac79e 100644 --- a/docs/answerability.md +++ b/docs/answerability.md @@ -1,4 +1,4 @@ -# Structured fact answerability (unreleased, opt-in) +# Structured fact answerability (v2.8.0, opt-in) The broker can attach evidence accounting to direct configuration questions: which attributes are supported, missing, or conflicting. This is a bounded syntactic check on retrieved evidence, not a general truth or entailment model. @@ -8,7 +8,7 @@ The broker can attach evidence accounting to direct configuration questions: whi kimetsu config set broker.explicit_fact_guard true ``` -The default is `false`. Use the same command with `false` to disable it. If a warm embed daemon is running an older executable, restart it with the updated build before comparing daemon behavior. This documentation describes unreleased code; installing the current published version does not guarantee this feature is available. +The default is `false`. Use the same command with `false` to disable it. This feature requires v2.8.0 or a build containing these changes. If a warm embed daemon is running an older executable, restart it with the updated build before comparing daemon behavior. An explicit statement such as `Orchid staging gateway port is 7319.` can support the port part of `What are the Orchid staging gateway port and timeout?`. The response reports timeout missing unless matching evidence is retrieved. Production evidence cannot fill a staging request. Distinct eligible values produce a conflict; equivalent durations such as `30 seconds` and `30000 ms` compare using exact rational arithmetic. diff --git a/docs/audits/2026-09-08-release-security.md b/docs/audits/2026-09-08-release-security.md new file mode 100644 index 0000000..00b0b0f --- /dev/null +++ b/docs/audits/2026-09-08-release-security.md @@ -0,0 +1,107 @@ +# v2.8.0 release security review + +Review date: 2026-09-08 UTC. Scope: the Kimetsu repository and PR #45, including +the Rust dependency lockfile, TypeScript SDK lockfile, existing CodeQL findings, +release version consistency, and release-note extraction. This is not a claim +that all possible vulnerabilities have been eliminated. + +## Dependency findings + +- A fresh `cargo audit --json` found zero known Rust vulnerabilities. This PR + already pins `h2` 0.4.16, which fixes + [RUSTSEC-2026-0258](https://rustsec.org/advisories/RUSTSEC-2026-0258.html). +- The SDK's `npm audit --json` found zero vulnerabilities. The CLI and remote npm + manifests are release templates with no third-party runtime dependencies; + their platform-package versions are stamped by the tag-driven workflow. +- GitHub Dependabot reported zero open alerts for `RodCor/kimetsu`. +- The initial audit identified yanked `der` 0.8.0. It is updated to 0.8.2; no + other registry dependency changed in the main repository during this + release-preparation step. +- `paste` 1.0.15 and `rustls-pemfile` 2.2.0 remain transitive dependencies with + informational unmaintained notices, not vulnerability advisories. Existing CI + exceptions name only these notices. They remain maintenance work; this review + does not hide them or replace them with unverified forks. +- The companion benchmark lockfile also moves its Kimetsu path dependencies + to 2.8.0. Its separate audit identified older `h2` and `quinn-proto` + vulnerabilities, `anyhow` and `cxx` unsoundness notices, and yanked `der`. + Updates to 0.4.16, 0.11.15, 1.0.103, 1.0.195, and 0.8.2 respectively leave + zero known vulnerabilities and only the informational `paste` maintenance + notice. These changes belong to + [benchmark PR #4](https://github.com/RodCor/kimetsu-bench/pull/4). + +## CodeQL triage + +The successful CodeQL check on `b8990cf39d18adcaf1507d7a21bc5337945baa7c` still +contained 47 open high-severity findings. A green scan means the scan ran; +it does not establish that the alert list is empty. The branch analysis was +`1736930424`, category `/language:rust`. Querying only `refs/pull/45/merge` returned +no alerts, so this review also checked `refs/heads/codex/brain-hardening` and the +default branch. All 47 SARIF source-to-sink paths were inspected. + +| Alerts | Finding and disposition | +| --- | --- | +| #5–#20, #22–#33, #35–#43, #45–#48 | 41 path findings in bridge/skill handlers. Their SARIF source is the remote HTTP router, but the remote allowlist rejects these host-only tools before `call_tool`. None of these reported remote paths reaches a filesystem sink. Classified as false positives for the reported source-to-sink path. | +| #2 | The CLI prints a stored conversation identifier in its local drift report. `DriftReport.session_id` is not an authentication session token; the identified source is the field itself, not a credential. Classified as a naming-heuristic false positive. | +| #3 | The CLI prints `InitSummary.api_key_env`, a configured variable **name**, when a credential is absent. The producer copies `config.model.api_key_env`; it never stores the resolved secret in that field. Classified as a false positive. | +| #4 | Bedrock already used an HTTPS URL, but region interpolation and redirect behavior deserved hardening. Region input is now restricted to a hostname label, model IDs are encoded as a path segment, the client is HTTPS-only, and redirects are disabled. | +| #21, #34, #44 | Repository ID input already rejected path separators and traversal. This review additionally rejects Windows device names/trailing-dot aliases, redirected repository roots, and pre-existing redirected state paths before the initialization fast path. | + +The 41 host-tool findings are backed by +`every_excluded_catalog_tool_is_blocked_at_http_boundary`, which exercises the +actual HTTP router with an administrator token, asserts the critical host tools +cannot enter the remote catalog, and verifies the excluded calls return the +remote-mode denial without creating host configuration directories. No scanner +rule or language is disabled. + +This is a source review and regression-test disposition, not a report that +GitHub has closed the alerts. No alerts were dismissed through the API during +this review. The updated branch needs a fresh CodeQL scan, and the reviewed +false positives still need a maintainer disposition in GitHub before the alert +list can be described as cleared. + +Path checks protect against existing symlinks/junctions and filesystem aliases. +Configured repository IDs that use reserved device names or trailing dots now +need an unambiguous name on every operating system. +The service data directory must remain under operator control; these checks do +not claim protection against a local actor racing filesystem mutations between +validation and use. The local CLI intentionally retains explicit filesystem +operations requested by its user. + +## Release preparation + +- All seven workspace packages and every existing inter-crate version pin move + from 2.7.0 to 2.8.0 using `scripts/bump-version.sh`. +- The SDK keeps its independent 0.1.0 version; npm binary wrappers keep their + 0.0.0 templates because the release workflow stamps them from the tag. +- Changelog extraction now accepts both `## vX.Y.Z` and `## vX.Y.Z: Title`, and + an empty extraction fails the release job. The actual workflow's awk program + was checked against the 2.8.0, 2.7.0, and 2.6.1 sections. +- The fact guard stays opt-in and existing model defaults stay unchanged. The + benchmark artifacts retain their original measured versions and fingerprints. +- No tag, merge, package publication, or release workflow dispatch is performed + as part of preparing this PR. + +## Verification + +The Windows path-alias regression failed against the original implementation +(`web.` was accepted), before the fix. Focused agent and remote tests pass with +the hardening changes, including Windows junction checks and HTTPS-only request +rejection before connecting. + +The full Windows workspace run with CLI `embeddings`, `pi`, and `openclaw`, plus +remote `tls`, passed **1,500 tests**, with six intentionally ignored and zero +failures. The command was: + +```text +cargo test --workspace --features kimetsu-cli/embeddings,kimetsu-cli/pi,kimetsu-cli/openclaw,kimetsu-remote/tls --locked --offline -j 1 -- --test-threads=1 +``` + +`KIMETSU_USER_BRAIN=0` and `KIMETSU_EMBED_DAEMON=0` isolated the test run from the +user brain and background embed daemon. The newly built CLI and remote binaries +both report 2.8.0. Cross-platform CI and the new CodeQL analysis must run on the +updated PR head before release. + +Formatting and workspace/all-target Clippy with `-D warnings` pass for both +the release feature set above and `--no-default-features`. Validation used Rust/Cargo 1.97.0 and cargo-audit +0.22.2. The manifest/lockfile consistency check covered all seven workspace +packages and every existing inter-crate version pin. From 785c3df9f7da079451363b85f747da87a401de78 Mon Sep 17 00:00:00 2001 From: RodCor Date: Mon, 7 Sep 2026 23:25:18 -0300 Subject: [PATCH 34/34] Link benchmark security follow-up after harness merge --- docs/audits/2026-09-08-release-security.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/audits/2026-09-08-release-security.md b/docs/audits/2026-09-08-release-security.md index 00b0b0f..f917d4d 100644 --- a/docs/audits/2026-09-08-release-security.md +++ b/docs/audits/2026-09-08-release-security.md @@ -27,7 +27,10 @@ that all possible vulnerabilities have been eliminated. Updates to 0.4.16, 0.11.15, 1.0.103, 1.0.195, and 0.8.2 respectively leave zero known vulnerabilities and only the informational `paste` maintenance notice. These changes belong to - [benchmark PR #4](https://github.com/RodCor/kimetsu-bench/pull/4). + [benchmark PR #5](https://github.com/RodCor/kimetsu-bench/pull/5), following the + merge of the benchmark implementation in PR #4. The patched lockfile passed + all 132 benchmark Rust tests against this Kimetsu 2.8.0 checkout. The benchmark + default branch's Dependabot alert #1 remains open until this follow-up is merged. ## CodeQL triage